From 9a6b4add71cf3fe59c70ea8e084bedf29bcd52c4 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Aug 2015 11:32:19 +1000 Subject: [PATCH 001/815] Add test case for ordered list with empty item (#13) Underlying bug was already fixed by 5a2b3917805eeaf453347443f3e7ba64aa9eba08. --- .../src/test/java/org/commonmark/test/SpecialInputTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 7dc5fd1f7..0596fed19 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -60,4 +60,9 @@ public void looseListInBlockQuote() { assertRendering("> *\n>\n> * a", "
\n\n
\n"); } + @Test + public void orderedListMarkerOnly() { + assertRendering("2.", "
    \n
  1. \n
\n"); + } + } From 0fde8ae3187f62078436ed885532d6cff2083189 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Aug 2015 13:01:55 +1000 Subject: [PATCH 002/815] Add example with visitor to README (#8) --- README.md | 22 +++++++++++++++++-- .../org/commonmark/test/UsageExampleTest.java | 22 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 101d847c2..3226fa23d 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ be followed. Usage ----- -Basic example: +#### Parse and render to HTML ```java import org.commonmark.html.HtmlRenderer; -import org.commonmark.node.Node; +import org.commonmark.node.*; import org.commonmark.parser.Parser; Parser parser = Parser.builder().build(); @@ -56,6 +56,24 @@ builder objects. Note that this library doesn't try to sanitize HTML; that is the responsibility of the caller. +#### Use a visitor to process parsed nodes + +```java +Node node = parser.parse("..."); +MyVisitor visitor = new MyVisitor(); +node.accept(visitor); + +class MyVisitor extends AbstractVisitor { + @Override + public void visit(Paragraph paragraph) { + // Do something with paragraph (override other methods for other nodes): + System.out.println(paragraph); + // Descend into children: + visitChildren(paragraph); + } +} +``` + Extensions ---------- diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 30b923257..54c180aae 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -1,7 +1,9 @@ package org.commonmark.test; import org.commonmark.html.HtmlRenderer; +import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; import org.commonmark.parser.Parser; import org.junit.Test; @@ -10,11 +12,29 @@ public class UsageExampleTest { @Test - public void one() { + public void parseAndRender() { Parser parser = Parser.builder().build(); Node document = parser.parse("This is *Sparta*"); HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); assertEquals("

This is Sparta

\n", renderer.render(document)); } + @Test + public void visitor() { + Parser parser = Parser.builder().build(); + Node node = parser.parse("..."); + MyVisitor visitor = new MyVisitor(); + node.accept(visitor); + } + + class MyVisitor extends AbstractVisitor { + @Override + public void visit(Paragraph paragraph) { + // Do something with paragraph (override other methods for other nodes): + System.out.println(paragraph); + // Descend into children: + visitChildren(paragraph); + } + } + } From 12e658f854c28445c3ddad34bdd5a72e5c7d4e45 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Aug 2015 13:57:32 +1000 Subject: [PATCH 003/815] Add overview and package-info files for Javadoc --- .../org/commonmark/html/package-info.java | 4 ++++ .../org/commonmark/node/package-info.java | 4 ++++ .../java/org/commonmark/package-info.java | 10 +++++++++ .../commonmark/parser/block/package-info.java | 4 ++++ .../org/commonmark/parser/package-info.java | 4 ++++ commonmark/src/main/javadoc/overview.html | 22 +++++++++++++++++++ 6 files changed, 48 insertions(+) create mode 100644 commonmark/src/main/java/org/commonmark/html/package-info.java create mode 100644 commonmark/src/main/java/org/commonmark/node/package-info.java create mode 100644 commonmark/src/main/java/org/commonmark/package-info.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/block/package-info.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/package-info.java create mode 100644 commonmark/src/main/javadoc/overview.html diff --git a/commonmark/src/main/java/org/commonmark/html/package-info.java b/commonmark/src/main/java/org/commonmark/html/package-info.java new file mode 100644 index 000000000..e18834e7c --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/package-info.java @@ -0,0 +1,4 @@ +/** + * HTML rendering (see {@link org.commonmark.html.HtmlRenderer}) + */ +package org.commonmark.html; diff --git a/commonmark/src/main/java/org/commonmark/node/package-info.java b/commonmark/src/main/java/org/commonmark/node/package-info.java new file mode 100644 index 000000000..e9fee1aba --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/package-info.java @@ -0,0 +1,4 @@ +/** + * AST node types (see {@link org.commonmark.node.Node}) and visitors (see {@link org.commonmark.node.AbstractVisitor}) + */ +package org.commonmark.node; diff --git a/commonmark/src/main/java/org/commonmark/package-info.java b/commonmark/src/main/java/org/commonmark/package-info.java new file mode 100644 index 000000000..7ac2dad76 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/package-info.java @@ -0,0 +1,10 @@ +/** + * Root package of commonmark-java + *

+ *

    + *
  • {@link org.commonmark.parser} for parsing input text to AST nodes
  • + *
  • {@link org.commonmark.node} for AST node types and visitors
  • + *
  • {@link org.commonmark.html} for HTML rendering
  • + *
+ */ +package org.commonmark; diff --git a/commonmark/src/main/java/org/commonmark/parser/block/package-info.java b/commonmark/src/main/java/org/commonmark/parser/block/package-info.java new file mode 100644 index 000000000..095d4d565 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/block/package-info.java @@ -0,0 +1,4 @@ +/** + * Types for extending block parsing + */ +package org.commonmark.parser.block; diff --git a/commonmark/src/main/java/org/commonmark/parser/package-info.java b/commonmark/src/main/java/org/commonmark/parser/package-info.java new file mode 100644 index 000000000..2afb3b96d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/package-info.java @@ -0,0 +1,4 @@ +/** + * Parsing input text to AST nodes (see {@link org.commonmark.parser.Parser}) + */ +package org.commonmark.parser; diff --git a/commonmark/src/main/javadoc/overview.html b/commonmark/src/main/javadoc/overview.html new file mode 100644 index 000000000..77c5944bf --- /dev/null +++ b/commonmark/src/main/javadoc/overview.html @@ -0,0 +1,22 @@ + + +Java implementation of CommonMark for parsing markdown and rendering to HTML (core library) +

Example:

+

+    import org.commonmark.html.HtmlRenderer;
+    import org.commonmark.node.*;
+    import org.commonmark.parser.Parser;
+
+    Parser parser = Parser.builder().build();
+    Node document = parser.parse("This is *Sparta*");
+    HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build();
+    renderer.render(document);  // "<p>This is <em>Sparta</em></p>\n"
+
+

See the following packages for details:

+
    +
  • {@link org.commonmark.parser} for parsing input text to AST nodes
  • +
  • {@link org.commonmark.node} for AST node types and visitors
  • +
  • {@link org.commonmark.html} for HTML rendering
  • +
+ + From 3dfc3c5bf2a47baa30aa3fb06cbb237f306f05a5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Aug 2015 14:01:52 +1000 Subject: [PATCH 004/815] Add more Javadoc on main API classes --- .../org/commonmark/html/HtmlRenderer.java | 74 +++++++++++++++---- .../java/org/commonmark/parser/Parser.java | 39 ++++++++-- 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index ce001a451..8b049ed18 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -6,6 +6,15 @@ import java.util.*; +/** + * Renders a tree of nodes to HTML. + *

+ * Start with the {@link #builder} method to configure the renderer. Example: + *


+ * HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build();
+ * renderer.render(node);
+ * 
+ */ public class HtmlRenderer { private static final Map NO_ATTRIBUTES = Collections.emptyMap(); @@ -24,6 +33,11 @@ private HtmlRenderer(Builder builder) { this.attributeProviders = builder.attributeProviders; } + /** + * Create a new builder for configuring an {@link HtmlRenderer}. + * + * @return a builder + */ public static Builder builder() { return new Builder(); } @@ -33,6 +47,11 @@ public void render(Node node, Appendable output) { node.accept(rendererVisitor); } + /** + * Render the tree of nodes to HTML. + * @param node the root node + * @return the rendered HTML + */ public String render(Node node) { StringBuilder sb = new StringBuilder(); render(node, sb); @@ -51,26 +70,45 @@ private String optionallyPercentEncodeUrl(String url) { } } - // default options: - // softbreak: '\n', // by default, soft breaks are rendered as newlines in - // HTML - // set to "
" to make them hard breaks - // set to " " if you want to ignore line wrapping in source + /** + * Builder for configuring an {@link HtmlRenderer}. See methods for default configuration. + */ public static class Builder { + private static final boolean ESCAPE_HTML_DEFAULT = false; + private static final boolean PERCENT_ENCODE_URLS_DEFAULT = false; + private String softbreak = "\n"; - private boolean escapeHtml = false; - private boolean percentEncodeUrls = false; + private boolean escapeHtml = ESCAPE_HTML_DEFAULT; + private boolean percentEncodeUrls = PERCENT_ENCODE_URLS_DEFAULT; private List customHtmlRenderers = new ArrayList<>(); private List attributeProviders = new ArrayList<>(); + /** + * @return the configured {@link HtmlRenderer} + */ + public HtmlRenderer build() { + return new HtmlRenderer(this); + } + + /** + * The HTML to use for rendering a softbreak, defaults to {@code "\n"} (meaning the rendered result doesn't have + * a line break). + *

+ * Set it to {@code "
"} (or {@code "
"} to make them hard breaks. + *

+ * Set it to {@code " "} to ignore line wrapping in the source. + * + * @param softbreak HTML for softbreak + * @return {@code this} + */ public Builder softbreak(String softbreak) { this.softbreak = softbreak; return this; } /** - * Whether {@link HtmlTag} and {@link HtmlBlock} should be escaped. + * Whether {@link HtmlTag} and {@link HtmlBlock} should be escaped, defaults to {@value #ESCAPE_HTML_DEFAULT}. *

* Note that {@link HtmlTag} is only a tag itself, not the text between an opening tag and a closing tag. So markup * in the text will be parsed as normal and is not affected by this option. @@ -84,7 +122,9 @@ public Builder escapeHtml(boolean escapeHtml) { } /** - * Whether URLs of link or images should be percent-encoded. If enabled, the following is done: + * Whether URLs of link or images should be percent-encoded, defaults to {@value #PERCENT_ENCODE_URLS_DEFAULT}. + *

+ * If enabled, the following is done: *

    *
  • Existing percent-encoded parts are preserved (e.g. "%20" is kept as "%20")
  • *
  • Reserved characters such as "/" are preserved, except for "[" and "]" (see encodeURI in JS)
  • @@ -92,7 +132,7 @@ public Builder escapeHtml(boolean escapeHtml) { *
  • Other characters such umlauts are percent-encoded
  • *
* - * @param percentEncodeUrls true to percent-encode, false for leaving as-is; default is false + * @param percentEncodeUrls true to percent-encode, false for leaving as-is * @return {@code this} */ public Builder percentEncodeUrls(boolean percentEncodeUrls) { @@ -100,6 +140,12 @@ public Builder percentEncodeUrls(boolean percentEncodeUrls) { return this; } + /** + * Add an attribute provider for adding/changing HTML attributes to the rendered tags. + * + * @param attributeProvider the attribute provider to add + * @return {@code this} + */ public Builder attributeProvider(AttributeProvider attributeProvider) { this.attributeProviders.add(attributeProvider); return this; @@ -112,7 +158,7 @@ public Builder customHtmlRenderer(CustomHtmlRenderer customHtmlRenderer) { /** * @param extensions extensions to use on this HTML renderer - * @return this + * @return {@code this} */ public Builder extensions(Iterable extensions) { for (Extension extension : extensions) { @@ -123,14 +169,10 @@ public Builder extensions(Iterable extensions) { } return this; } - - public HtmlRenderer build() { - return new HtmlRenderer(this); - } } /** - * Extension for HTML renderer. + * Extension for {@link HtmlRenderer}. */ public interface HtmlRendererExtension extends Extension { void extend(Builder rendererBuilder); diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 7d46dfb0d..3fe25d5b6 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -13,6 +13,15 @@ import java.util.List; import java.util.Map; +/** + * Parses input text to a tree of nodes. + *

+ * Start with the {@link #builder} method, configure the parser and build it. Example: + *


+ * Parser parser = Parser.builder().build();
+ * Node document = parser.parse("input text");
+ * 
+ */ public class Parser { private final List blockParserFactories; @@ -29,12 +38,17 @@ private Parser(Builder builder) { this.postProcessors = builder.postProcessors; } + /** + * Create a new builder for configuring a {@link Parser}. + * + * @return a builder + */ public static Builder builder() { return new Builder(); } /** - * Parse the specified input text into a AST (tree of nodes). + * Parse the specified input text into a tree of nodes. *

* Note that this method is thread-safe (a new parser state is used for each invocation). * @@ -47,7 +61,16 @@ public Node parse(String input) { Node document = documentParser.parse(input); return postProcess(document); } - + + /** + * Parse the specified reader into a tree of nodes. The caller is responsible for closing the reader. + *

+ * Note that this method is thread-safe (a new parser state is used for each invocation). + * + * @param input the reader to parse + * @return the root node + * @throws IOException when reading throws an exception + */ public Node parseReader(Reader input) throws IOException { InlineParserImpl inlineParser = new InlineParserImpl(specialCharacters, delimiterCharacters, delimiterProcessors); DocumentParser documentParser = new DocumentParser(blockParserFactories, inlineParser); @@ -61,19 +84,25 @@ private Node postProcess(Node document) { } return document; } - + + /** + * Builder for configuring a {@link Parser}. + */ public static class Builder { private final List blockParserFactories = new ArrayList<>(); private final List delimiterProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); + /** + * @return the configured {@link Parser} + */ public Parser build() { return new Parser(this); } /** * @param extensions extensions to use on this parser - * @return this + * @return {@code this} */ public Builder extensions(Iterable extensions) { for (Extension extension : extensions) { @@ -102,7 +131,7 @@ public Builder postProcessor(PostProcessor postProcessor) { } /** - * Extension for parser. + * Extension for {@link Parser}. */ public interface ParserExtension extends Extension { void extend(Builder parserBuilder); From 713a9e4f87402a02ed9823e38fc368f285e21898 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Aug 2015 14:10:25 +1000 Subject: [PATCH 005/815] Remove unused code --- .../src/main/java/org/commonmark/ext/gfm/tables/TableRow.java | 1 - .../test/java/org/commonmark/integration/PegDownBenchmark.java | 1 - .../java/org/commonmark/internal/IndentedCodeBlockParser.java | 1 - .../src/main/java/org/commonmark/internal/util/Parsing.java | 2 -- .../src/main/java/org/commonmark/parser/block/BlockStart.java | 2 -- commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java | 1 - 6 files changed, 8 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java index c1305ff0b..a3e32ce17 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java @@ -1,7 +1,6 @@ package org.commonmark.ext.gfm.tables; import org.commonmark.node.CustomNode; -import org.commonmark.node.Visitor; public class TableRow extends CustomNode { } 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 9af06e6af..c42c5ad2a 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java @@ -1,7 +1,6 @@ package org.commonmark.integration; import org.commonmark.spec.SpecReader; -import org.openjdk.jmh.Main; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; diff --git a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java index 3444f95e6..066ae1587 100644 --- a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java @@ -52,7 +52,6 @@ public static class Factory extends AbstractBlockParserFactory { public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { // An indented code block cannot interrupt a paragraph. if (state.getIndent() >= INDENT && !state.isBlank() && !(state.getActiveBlockParser().getBlock() instanceof Paragraph)) { - int nextNonSpace = state.getNextNonSpaceIndex(); return BlockStart.of(new IndentedCodeBlockParser()).atColumn(state.getColumn() + INDENT); } else { return BlockStart.none(); 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 43c888dfa..379173d71 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -2,8 +2,6 @@ public class Parsing { - private static final String[] TAB_SPACES = new String[]{" ", " ", " ", " "}; - 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]+"; 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 da8f3b751..d9e7a2b49 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockStart.java @@ -2,8 +2,6 @@ import org.commonmark.internal.BlockStartImpl; -import java.util.Collections; - /** * Result object for starting parsing of a block, see static methods for constructors. */ diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index 32b0efdd2..cfd9f354b 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -3,7 +3,6 @@ import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.spec.SpecReader; -import org.openjdk.jmh.Main; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; From f9b098b5c4b72f49162096548a56f957d442ea28 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Aug 2015 14:12:00 +1000 Subject: [PATCH 006/815] Remove version of maven-compiler-plugin (inherited from parent now) --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index cd490c1cb..be562968c 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.3 7 7 From e5e1a0925c641b81a8917f6e827e2692846afa72 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Aug 2015 16:16:28 +1000 Subject: [PATCH 007/815] Extend pom description of commonmark artifact --- commonmark/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/pom.xml b/commonmark/pom.xml index be18858ad..b7c895137 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -9,7 +9,7 @@ commonmark commonmark-java core - Core of commonmark-java + Core of commonmark-java (implementation of CommonMark for parsing markdown and rendering to HTML) From b85b58e67073592c4434d5680f5b95b046b674ca Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Aug 2015 17:00:37 +1000 Subject: [PATCH 008/815] Make benchmark runnable with mvn --- .../integration/PegDownBenchmark.java | 6 ++++- commonmark/pom.xml | 25 +++++++++++++++++++ .../org/commonmark/test/SpecBenchmark.java | 7 +++++- etc/benchmark.sh | 4 +++ pom.xml | 7 +++--- 5 files changed, 44 insertions(+), 5 deletions(-) create mode 100755 etc/benchmark.sh 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 c42c5ad2a..7348a32f3 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 @@ -5,6 +5,7 @@ import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.CommandLineOptions; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import org.pegdown.Extensions; @@ -21,7 +22,10 @@ public class PegDownBenchmark { private static final PegDownProcessor PROCESSOR = new PegDownProcessor(Extensions.FENCED_CODE_BLOCKS); public static void main(String[] args) throws Exception { - Options options = new OptionsBuilder().include(PegDownBenchmark.class.getName() + ".*").build(); + Options options = new OptionsBuilder() + .parent(new CommandLineOptions(args)) + .include(PegDownBenchmark.class.getName() + ".*") + .build(); new Runner(options).run(); } diff --git a/commonmark/pom.xml b/commonmark/pom.xml index b7c895137..8dc0a4800 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -46,4 +46,29 @@ + + + benchmark + + exec:exec + + + org.codehaus.mojo + exec-maven-plugin + 1.4.0 + + java + test + + -classpath + + org.commonmark.test.SpecBenchmark + + + + + + + + diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index cfd9f354b..0ea063c65 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -3,10 +3,12 @@ import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.spec.SpecReader; +import org.openjdk.jmh.Main; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.CommandLineOptions; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; @@ -22,7 +24,10 @@ public class SpecBenchmark { private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); public static void main(String[] args) throws Exception { - Options options = new OptionsBuilder().include(SpecBenchmark.class.getName() + ".*").build(); + Options options = new OptionsBuilder() + .parent(new CommandLineOptions(args)) + .include(SpecBenchmark.class.getName() + ".*") + .build(); new Runner(options).run(); } diff --git a/etc/benchmark.sh b/etc/benchmark.sh new file mode 100755 index 000000000..89d3cfb4c --- /dev/null +++ b/etc/benchmark.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd $(dirname $0)/.. +mvn -pl commonmark -Pbenchmark -DskipTests clean package exec:exec diff --git a/pom.xml b/pom.xml index be562968c..7fa6857a6 100644 --- a/pom.xml +++ b/pom.xml @@ -40,7 +40,8 @@ 7 7 - -proc:none + + false @@ -85,12 +86,12 @@ org.openjdk.jmh jmh-core - 1.10.3 + 1.10.4 org.openjdk.jmh jmh-generator-annprocess - 1.10.3 + 1.10.4 From 672798a8f84d5a263256633757f4561fea478011 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 10 Aug 2015 15:38:50 +0200 Subject: [PATCH 009/815] Ignore Eclipse project files --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index b1ce96c86..a156931f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Eclipse +.project +.classpath +.settings/ + # IntelliJ IDEA .idea *.iml From f3bc76fffdc7e8008eb38b6d15aff6fb085e292e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 10 Aug 2015 15:46:04 +0200 Subject: [PATCH 010/815] Configure Eclipse formatter and newlines --- .../.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 insertions(+) create mode 100644 commonmark-ext-autolink/.settings/org.eclipse.core.runtime.prefs create mode 100644 commonmark-ext-autolink/.settings/org.eclipse.jdt.core.prefs create mode 100644 commonmark-ext-gfm-strikethrough/.settings/org.eclipse.core.runtime.prefs create mode 100644 commonmark-ext-gfm-strikethrough/.settings/org.eclipse.jdt.core.prefs create mode 100644 commonmark-ext-gfm-tables/.settings/org.eclipse.core.runtime.prefs create mode 100644 commonmark-ext-gfm-tables/.settings/org.eclipse.jdt.core.prefs create mode 100644 commonmark-integration-test/.settings/org.eclipse.core.runtime.prefs create mode 100644 commonmark-integration-test/.settings/org.eclipse.jdt.core.prefs create mode 100644 commonmark/.settings/org.eclipse.core.runtime.prefs create 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 new file mode 100644 index 000000000..5a0ad22d2 --- /dev/null +++ b/commonmark-ext-autolink/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +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 new file mode 100644 index 000000000..3c0d27c8f --- /dev/null +++ b/commonmark-ext-autolink/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,290 @@ +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 new file mode 100644 index 000000000..5a0ad22d2 --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +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 new file mode 100644 index 000000000..3c0d27c8f --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,290 @@ +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 new file mode 100644 index 000000000..5a0ad22d2 --- /dev/null +++ b/commonmark-ext-gfm-tables/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +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 new file mode 100644 index 000000000..3c0d27c8f --- /dev/null +++ b/commonmark-ext-gfm-tables/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,290 @@ +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 new file mode 100644 index 000000000..5a0ad22d2 --- /dev/null +++ b/commonmark-integration-test/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +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 new file mode 100644 index 000000000..3c0d27c8f --- /dev/null +++ b/commonmark-integration-test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,290 @@ +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 new file mode 100644 index 000000000..5a0ad22d2 --- /dev/null +++ b/commonmark/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +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 new file mode 100644 index 000000000..3c0d27c8f --- /dev/null +++ b/commonmark/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,290 @@ +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 f72bf519b65f5c85495d17e4f21b7c0580b58d81 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 18 Aug 2015 15:11:43 +1000 Subject: [PATCH 011/815] Set pushChanges to false for maven-release-plugin Pushing the release tag and master before publishing it to the Maven repository is not ideal, so let's do it manually. --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 7fa6857a6..b4de67637 100644 --- a/pom.xml +++ b/pom.xml @@ -145,9 +145,19 @@ org.sonatype.plugins nexus-staging-maven-plugin + false + + org.apache.maven.plugins + maven-release-plugin + + + false + + From 0934812f39a2bb9fe5826a604a52a22c7880e14a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Aug 2015 11:26:23 +1000 Subject: [PATCH 012/815] Don't use @value in Javadoc Doesn't seem to work in either Doclava or Java 8's javadoc. --- .../main/java/org/commonmark/html/HtmlRenderer.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index 8b049ed18..a18928640 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -75,12 +75,9 @@ private String optionallyPercentEncodeUrl(String url) { */ public static class Builder { - private static final boolean ESCAPE_HTML_DEFAULT = false; - private static final boolean PERCENT_ENCODE_URLS_DEFAULT = false; - private String softbreak = "\n"; - private boolean escapeHtml = ESCAPE_HTML_DEFAULT; - private boolean percentEncodeUrls = PERCENT_ENCODE_URLS_DEFAULT; + private boolean escapeHtml = false; + private boolean percentEncodeUrls = false; private List customHtmlRenderers = new ArrayList<>(); private List attributeProviders = new ArrayList<>(); @@ -108,7 +105,7 @@ public Builder softbreak(String softbreak) { } /** - * Whether {@link HtmlTag} and {@link HtmlBlock} should be escaped, defaults to {@value #ESCAPE_HTML_DEFAULT}. + * Whether {@link HtmlTag} and {@link HtmlBlock} should be escaped, defaults to {@code false}. *

* Note that {@link HtmlTag} is only a tag itself, not the text between an opening tag and a closing tag. So markup * in the text will be parsed as normal and is not affected by this option. @@ -122,7 +119,7 @@ public Builder escapeHtml(boolean escapeHtml) { } /** - * Whether URLs of link or images should be percent-encoded, defaults to {@value #PERCENT_ENCODE_URLS_DEFAULT}. + * Whether URLs of link or images should be percent-encoded, defaults to {@code false}. *

* If enabled, the following is done: *

    From 2196a22a7ca26a2eaae9c6587ccb0e4e4a97bb40 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Aug 2015 11:46:02 +1000 Subject: [PATCH 013/815] Fix extra newline in Javadoc code example --- commonmark/src/main/javadoc/overview.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/commonmark/src/main/javadoc/overview.html b/commonmark/src/main/javadoc/overview.html index 77c5944bf..62bcf4a08 100644 --- a/commonmark/src/main/javadoc/overview.html +++ b/commonmark/src/main/javadoc/overview.html @@ -2,8 +2,7 @@ Java implementation of CommonMark for parsing markdown and rendering to HTML (core library)

    Example:

    -
    
    -    import org.commonmark.html.HtmlRenderer;
    +
        import org.commonmark.html.HtmlRenderer;
         import org.commonmark.node.*;
         import org.commonmark.parser.Parser;
     
    
    From 542a93c096640f74aa5865cd1fdaab24996c71e7 Mon Sep 17 00:00:00 2001
    From: Robin Stocker 
    Date: Thu, 20 Aug 2015 11:29:16 +1000
    Subject: [PATCH 014/815] Set localCheckout to false for maven-release-plugin
    
    ---
     pom.xml | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/pom.xml b/pom.xml
    index b4de67637..f6526930f 100644
    --- a/pom.xml
    +++ b/pom.xml
    @@ -154,8 +154,10 @@
                             maven-release-plugin
                             
                                 
    +                                 don't push the tag and master automatically (which happens before publishing). Also
    +                                 requires "localCheckout" for release:perform to work. -->
                                 false
    +                            true
                             
                         
                     
    
    From 3a2766e19cc1a58f238f95feafdb14f53912d10d Mon Sep 17 00:00:00 2001
    From: Robin Stocker 
    Date: Thu, 20 Aug 2015 11:43:51 +1000
    Subject: [PATCH 015/815] Update parent pom version to 3.0.95
    
    This may enable replacing the atlassian-public workaround with a
    different workaround that is less bad.
    ---
     pom.xml | 134 ++++++++++++++++++--------------------------------------
     1 file changed, 43 insertions(+), 91 deletions(-)
    
    diff --git a/pom.xml b/pom.xml
    index f6526930f..6e85948e7 100644
    --- a/pom.xml
    +++ b/pom.xml
    @@ -5,7 +5,7 @@
         
             com.atlassian.pom
             central-pom
    -        3.0.91
    +        3.0.95
         
     
         pom
    @@ -33,18 +33,48 @@
         
     
         
    -        
    -            
    -                org.apache.maven.plugins
    -                maven-compiler-plugin
    -                
    -                    7
    -                    7
    -                    
    -                    false
    -                
    -            
    -        
    +        
    +            
    +                
    +                    org.apache.maven.plugins
    +                    maven-compiler-plugin
    +                    
    +                        7
    +                        7
    +                        
    +                        false
    +                    
    +                
    +                
    +                    org.apache.maven.plugins
    +                    maven-javadoc-plugin
    +                    
    +                        *.internal,*.internal.*
    +                    
    +                
    +                
    +                    org.apache.maven.plugins
    +                    maven-release-plugin
    +                    
    +                        
    +                        false
    +                        true
    +                    
    +                
    +                
    +                    org.sonatype.plugins
    +                    nexus-staging-maven-plugin
    +                    
    +                    1.6.6
    +                    
    +                        
    +                        false
    +                    
    +                
    +            
    +        
         
     
         
    @@ -96,75 +126,6 @@
             
         
     
    -    
    -        
    -            release
    -            
    -                
    -                    
    -                        org.apache.maven.plugins
    -                        maven-source-plugin
    -                        
    -                            
    -                                attach-sources
    -                                
    -                                    jar-no-fork
    -                                
    -                            
    -                        
    -                    
    -                    
    -                        org.apache.maven.plugins
    -                        maven-javadoc-plugin
    -                        
    -                            
    -                                attach-javadocs
    -                                
    -                                    jar
    -                                
    -                            
    -                        
    -                        
    -                            *.internal,*.internal.*
    -                        
    -                    
    -                    
    -                        org.apache.maven.plugins
    -                        maven-gpg-plugin
    -                        
    -                            
    -                                sign-artifacts
    -                                verify
    -                                
    -                                    sign
    -                                
    -                            
    -                        
    -                    
    -                    
    -                        org.sonatype.plugins
    -                        nexus-staging-maven-plugin
    -                        
    -                            
    -                            false
    -                        
    -                    
    -                    
    -                        org.apache.maven.plugins
    -                        maven-release-plugin
    -                        
    -                            
    -                            false
    -                            true
    -                        
    -                    
    -                
    -            
    -        
    -    
    -
         
             
                 BSD 2-Clause License
    @@ -189,13 +150,4 @@
             HEAD
         
     
    -    
    -    
    -        
    -            atlassian-public
    -            Atlassian Public Repository
    -            https://maven.atlassian.com/repository/public
    -        
    -    
    -
     
    
    From 3215ab102c266978a420be8f3dc772f863e897cd Mon Sep 17 00:00:00 2001
    From: Robin Stocker 
    Date: Thu, 20 Aug 2015 14:40:24 +1000
    Subject: [PATCH 016/815] Add Javadoc for extensions
    
    ---
     .../org/commonmark/ext/autolink/AutolinkExtension.java    | 8 ++++++++
     commonmark-ext-autolink/src/main/javadoc/overview.html    | 6 ++++++
     commonmark-ext-gfm-strikethrough/pom.xml                  | 2 +-
     .../ext/gfm/strikethrough/StrikethroughExtension.java     | 8 ++++++++
     .../src/main/javadoc/overview.html                        | 6 ++++++
     commonmark-ext-gfm-tables/pom.xml                         | 2 +-
     .../org/commonmark/ext/gfm/tables/TablesExtension.java    | 8 ++++++++
     commonmark-ext-gfm-tables/src/main/javadoc/overview.html  | 6 ++++++
     8 files changed, 44 insertions(+), 2 deletions(-)
     create mode 100644 commonmark-ext-autolink/src/main/javadoc/overview.html
     create mode 100644 commonmark-ext-gfm-strikethrough/src/main/javadoc/overview.html
     create mode 100644 commonmark-ext-gfm-tables/src/main/javadoc/overview.html
    
    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 0dc7d0e84..7b4ef233a 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
    @@ -3,6 +3,14 @@
     import org.commonmark.Extension;
     import org.commonmark.parser.Parser;
     
    +/**
    + * Extension for automatically turning plain URLs and email addresses into links.
    + * 

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

    + */ public class AutolinkExtension implements Parser.ParserExtension { private AutolinkExtension() { diff --git a/commonmark-ext-autolink/src/main/javadoc/overview.html b/commonmark-ext-autolink/src/main/javadoc/overview.html new file mode 100644 index 000000000..b268c7bc0 --- /dev/null +++ b/commonmark-ext-autolink/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for automatically turning plain URLs and email addresses into links +

    See {@link org.commonmark.ext.autolink.AutolinkExtension}

    + + diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 9ad3a1657..4d48acae1 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -9,7 +9,7 @@ commonmark-ext-gfm-strikethrough commonmark-java extension for strikethrough - commonmark-java extension for GFM strikethrough using ~~~ (GitHub Flavored Markdown) + commonmark-java extension for GFM strikethrough using ~~ (GitHub Flavored Markdown) 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 4660c2d7a..21a19a6c7 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 @@ -4,6 +4,14 @@ import org.commonmark.parser.Parser; import org.commonmark.html.HtmlRenderer; +/** + * Extension for GFM strikethrough using ~~ (GitHub Flavored Markdown). + *

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

    + */ public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { private StrikethroughExtension() { diff --git a/commonmark-ext-gfm-strikethrough/src/main/javadoc/overview.html b/commonmark-ext-gfm-strikethrough/src/main/javadoc/overview.html new file mode 100644 index 000000000..a80cdd82b --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for GFM strikethrough using ~~ (GitHub Flavored Markdown) +

    See {@link org.commonmark.ext.gfm.strikethrough.StrikethroughExtension}

    + + diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 131e0dd60..e89e73e6b 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -9,7 +9,7 @@ commonmark-ext-gfm-tables commonmark-java extension for tables - commonmark-java extension for GFM tables using | (GitHub Flavored Markdown) + commonmark-java extension for GFM tables using "|" pipes (GitHub Flavored Markdown) 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 b042b0c3b..1dec67aea 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 @@ -4,6 +4,14 @@ import org.commonmark.parser.Parser; import org.commonmark.html.HtmlRenderer; +/** + * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). + *

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

    + */ public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { private TablesExtension() { diff --git a/commonmark-ext-gfm-tables/src/main/javadoc/overview.html b/commonmark-ext-gfm-tables/src/main/javadoc/overview.html new file mode 100644 index 000000000..9b4a9ac44 --- /dev/null +++ b/commonmark-ext-gfm-tables/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for GFM tables using "|" pipes (GitHub Flavored Markdown) +

    See {@link org.commonmark.ext.gfm.tables.TablesExtension}

    + + From 6f2cd85743d7e2a865180927d46a24aca37b440f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Aug 2015 14:53:11 +1000 Subject: [PATCH 017/815] Move some classes to internal packages in extensions, add Javadoc --- .../commonmark/ext/autolink/AutolinkExtension.java | 4 ++++ .../{ => internal}/AutolinkPostProcessor.java | 2 +- .../ext/gfm/strikethrough/Strikethrough.java | 3 +++ .../gfm/strikethrough/StrikethroughExtension.java | 5 +++++ .../StrikethroughDelimiterProcessor.java | 3 ++- .../{ => internal}/StrikethroughHtmlRenderer.java | 3 ++- .../org/commonmark/ext/gfm/tables/TableBlock.java | 3 +++ .../org/commonmark/ext/gfm/tables/TableBody.java | 3 +++ .../org/commonmark/ext/gfm/tables/TableCell.java | 13 +++++++++++++ .../org/commonmark/ext/gfm/tables/TableHead.java | 3 +++ .../org/commonmark/ext/gfm/tables/TableRow.java | 3 +++ .../commonmark/ext/gfm/tables/TablesExtension.java | 5 +++++ .../gfm/tables/{ => internal}/TableBlockParser.java | 3 ++- .../tables/{ => internal}/TableHtmlRenderer.java | 3 ++- 14 files changed, 51 insertions(+), 5 deletions(-) rename commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/{ => internal}/AutolinkPostProcessor.java (98%) rename commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/{ => internal}/StrikethroughDelimiterProcessor.java (93%) rename commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/{ => internal}/StrikethroughHtmlRenderer.java (87%) rename commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/{ => internal}/TableBlockParser.java (98%) rename commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/{ => internal}/TableHtmlRenderer.java (97%) 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 7b4ef233a..606bbb532 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,6 +1,7 @@ package org.commonmark.ext.autolink; import org.commonmark.Extension; +import org.commonmark.ext.autolink.internal.AutolinkPostProcessor; import org.commonmark.parser.Parser; /** @@ -10,6 +11,9 @@ * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, * {@link org.commonmark.html.HtmlRenderer.Builder#extensions(Iterable)}). *

    + *

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

    */ public class AutolinkExtension implements Parser.ParserExtension { diff --git a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkPostProcessor.java b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java similarity index 98% rename from commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkPostProcessor.java rename to commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java index bfbb7cf3a..33a7c321a 100644 --- a/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/AutolinkPostProcessor.java +++ b/commonmark-ext-autolink/src/main/java/org/commonmark/ext/autolink/internal/AutolinkPostProcessor.java @@ -1,4 +1,4 @@ -package org.commonmark.ext.autolink; +package org.commonmark.ext.autolink.internal; import org.commonmark.node.*; import org.commonmark.parser.PostProcessor; 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 25b618f02..b4ecfce2f 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 @@ -2,5 +2,8 @@ import org.commonmark.node.CustomNode; +/** + * A strikethrough node containing text and other inline nodes nodes as children. + */ public class Strikethrough extends CustomNode { } 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 21a19a6c7..c07756332 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,6 +1,8 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.Extension; +import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughDelimiterProcessor; +import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughHtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.html.HtmlRenderer; @@ -11,6 +13,9 @@ * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, * {@link org.commonmark.html.HtmlRenderer.Builder#extensions(Iterable)}). *

    + *

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

    */ public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughDelimiterProcessor.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java similarity index 93% rename from commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughDelimiterProcessor.java rename to commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java index 40019a55a..14a847e2d 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughDelimiterProcessor.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughDelimiterProcessor.java @@ -1,5 +1,6 @@ -package org.commonmark.ext.gfm.strikethrough; +package org.commonmark.ext.gfm.strikethrough.internal; +import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.node.Node; import org.commonmark.node.Text; import org.commonmark.parser.DelimiterProcessor; diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughHtmlRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlRenderer.java similarity index 87% rename from commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughHtmlRenderer.java rename to commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlRenderer.java index 650a075ec..b89efc13a 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/StrikethroughHtmlRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlRenderer.java @@ -1,5 +1,6 @@ -package org.commonmark.ext.gfm.strikethrough; +package org.commonmark.ext.gfm.strikethrough.internal; +import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.html.CustomHtmlRenderer; import org.commonmark.html.HtmlWriter; import org.commonmark.node.Node; diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlock.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlock.java index 0e060b8b3..c46fc27ef 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlock.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlock.java @@ -2,5 +2,8 @@ import org.commonmark.node.CustomBlock; +/** + * Table block containing a {@link TableHead} and optionally a {@link TableBody}. + */ public class TableBlock extends CustomBlock { } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBody.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBody.java index f0dd9b227..ddc80deb3 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBody.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBody.java @@ -2,5 +2,8 @@ import org.commonmark.node.CustomNode; +/** + * Body part of a {@link TableBlock} containing {@link TableRow TableRows}. + */ public class TableBody extends CustomNode { } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java index cb2ea66a3..61880c6c3 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableCell.java @@ -2,11 +2,17 @@ import org.commonmark.node.CustomNode; +/** + * Table cell of a {@link TableRow} containing inline nodes. + */ public class TableCell extends CustomNode { private boolean header; private Alignment alignment; + /** + * @return whether the cell is a header or not + */ public boolean isHeader() { return header; } @@ -15,6 +21,9 @@ public void setHeader(boolean header) { this.header = header; } + /** + * @return the cell alignment + */ public Alignment getAlignment() { return alignment; } @@ -23,7 +32,11 @@ public void setAlignment(Alignment alignment) { this.alignment = alignment; } + /** + * How the cell is aligned horizontally. + */ public enum Alignment { LEFT, CENTER, RIGHT } + } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHead.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHead.java index 4de7ff9b1..96a95e620 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHead.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHead.java @@ -2,5 +2,8 @@ import org.commonmark.node.CustomNode; +/** + * Head part of a {@link TableBlock} containing {@link TableRow TableRows}. + */ public class TableHead extends CustomNode { } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java index a3e32ce17..1325875d0 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableRow.java @@ -2,5 +2,8 @@ import org.commonmark.node.CustomNode; +/** + * Table row of a {@link TableHead} or {@link TableBody} containing {@link TableCell TableCells}. + */ public class TableRow extends CustomNode { } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index 1dec67aea..84e58f391 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -1,6 +1,8 @@ package org.commonmark.ext.gfm.tables; import org.commonmark.Extension; +import org.commonmark.ext.gfm.tables.internal.TableBlockParser; +import org.commonmark.ext.gfm.tables.internal.TableHtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.html.HtmlRenderer; @@ -11,6 +13,9 @@ * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, * {@link org.commonmark.html.HtmlRenderer.Builder#extensions(Iterable)}). *

    + *

    + * The parsed tables are turned into {@link TableBlock} blocks. + *

    */ public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java similarity index 98% rename from commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlockParser.java rename to commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 5d366f346..7d4fe2110 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableBlockParser.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java @@ -1,5 +1,6 @@ -package org.commonmark.ext.gfm.tables; +package org.commonmark.ext.gfm.tables.internal; +import org.commonmark.ext.gfm.tables.*; import org.commonmark.node.Block; import org.commonmark.node.Node; import org.commonmark.parser.InlineParser; diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHtmlRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlRenderer.java similarity index 97% rename from commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHtmlRenderer.java rename to commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlRenderer.java index aacd45689..dbe6eb61a 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TableHtmlRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlRenderer.java @@ -1,5 +1,6 @@ -package org.commonmark.ext.gfm.tables; +package org.commonmark.ext.gfm.tables.internal; +import org.commonmark.ext.gfm.tables.*; import org.commonmark.html.CustomHtmlRenderer; import org.commonmark.html.HtmlWriter; import org.commonmark.node.Node; From 0700a122a8ed9c0ff139e3f90d9bc2a30b30e7c8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Aug 2015 16:08:16 +1000 Subject: [PATCH 018/815] Make Javadoc links from extensions to core work Uses javadoc.io for now, which provides free Javadoc hosting for artifacts from Central. --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index 6e85948e7..e3ea0d4d1 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,14 @@ maven-javadoc-plugin *.internal,*.internal.* + + false + + + http://static.javadoc.io/com.atlassian.commonmark/commonmark/${project.version}/ + ${project.basedir}/../commonmark/target/site/apidocs/ + + From 177ad21c40ebb7bf3d85bc45032814e3d763d3c2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Aug 2015 16:32:42 +1000 Subject: [PATCH 019/815] Extract property for core javadoc location The location is different when running `mvn javadoc:javadoc` vs `mvn install`, so enable changing it using a property. --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3ea0d4d1..a9589addb 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ UTF-8 + ${project.basedir}/../commonmark/target/apidocs/ @@ -55,7 +56,7 @@ http://static.javadoc.io/com.atlassian.commonmark/commonmark/${project.version}/ - ${project.basedir}/../commonmark/target/site/apidocs/ + ${commonmark.javadoc.location} From 153eaae28849bd39c3d9350f5f287005424f81b1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Aug 2015 16:40:29 +1000 Subject: [PATCH 020/815] [maven-release-plugin] prepare release commonmark-parent-0.2.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark/pom.xml | 4 ++-- pom.xml | 17 ++++++++--------- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 26248b6a9..dca2601ab 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.1.1-SNAPSHOT + 0.2.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 4d48acae1..71b27fa4d 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.1.1-SNAPSHOT + 0.2.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index e89e73e6b..bea58e2dc 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.1.1-SNAPSHOT + 0.2.0 commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 1657fa715..cd64cf846 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.1.1-SNAPSHOT + 0.2.0 commonmark-integration-test diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 8dc0a4800..8192bdeab 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.1.1-SNAPSHOT + 0.2.0 commonmark @@ -61,7 +61,7 @@ test -classpath - + org.commonmark.test.SpecBenchmark diff --git a/pom.xml b/pom.xml index a9589addb..682950cba 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 com.atlassian.pom @@ -12,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.1.1-SNAPSHOT + 0.2.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -92,27 +91,27 @@ com.atlassian.commonmark commonmark - 0.1.1-SNAPSHOT + 0.2.0 com.atlassian.commonmark commonmark-ext-autolink - 0.1.1-SNAPSHOT + 0.2.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.1.1-SNAPSHOT + 0.2.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.1.1-SNAPSHOT + 0.2.0 com.atlassian.commonmark commonmark - 0.1.1-SNAPSHOT + 0.2.0 test-jar @@ -156,7 +155,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.2.0 From 810e7be7f902c8d4780b65a88e540ff39dec75a0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Aug 2015 16:40:29 +1000 Subject: [PATCH 021/815] [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-integration-test/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index dca2601ab..5edb792a4 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.0 + 0.2.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 71b27fa4d..e82d955c3 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.0 + 0.2.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index bea58e2dc..935a5533e 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.0 + 0.2.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index cd64cf846..0b69e1d41 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.0 + 0.2.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 8192bdeab..9c20f88e0 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.0 + 0.2.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 682950cba..17a9c8e07 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.0 + 0.2.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -91,27 +91,27 @@ com.atlassian.commonmark commonmark - 0.2.0 + 0.2.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.2.0 + 0.2.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.2.0 + 0.2.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.2.0 + 0.2.1-SNAPSHOT com.atlassian.commonmark commonmark - 0.2.0 + 0.2.1-SNAPSHOT test-jar @@ -155,7 +155,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.2.0 + HEAD From 7ea7d78cbb7018d1d15e7f812d932450776f8465 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 20 Aug 2015 16:59:44 +1000 Subject: [PATCH 022/815] Set autoRelease to false in a way that works :) --- pom.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 17a9c8e07..1babc0dea 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,8 @@ UTF-8 ${project.basedir}/../commonmark/target/apidocs/ + + false @@ -76,10 +78,6 @@ nexus-staging-maven-plugin 1.6.6 - - - false - From dc19102aa5a69ef7119f40439a3326612213da3e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 21 Aug 2015 11:36:28 +1000 Subject: [PATCH 023/815] README: Add link to javadoc.io (#4) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3226fa23d..9bd37c21b 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,11 @@ class MyVisitor extends AbstractVisitor { } ``` +### API documentation + +Javadocs are available online on +[javadoc.io](http://www.javadoc.io/doc/com.atlassian.commonmark/commonmark). + Extensions ---------- From 18b655d461cd8ed0082b4b973e297483b2b833e8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 21 Aug 2015 11:39:12 +1000 Subject: [PATCH 024/815] README: Bump version to 0.2.0 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9bd37c21b..62f3a02b2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.1.0 + 0.2.0 ``` @@ -95,7 +95,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.1.0 + 0.2.0 ``` From df87babf2932c7b27bea0b37788df2a7e62157da Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 22 Sep 2015 17:38:10 +1000 Subject: [PATCH 025/815] Fix compilation without install (#19) By moving out the test utilities to a separate module, `mvn compile` works. It used to fail because other modules had dependencies of type test-jar on the base module, which is resolved from the repository. The way it is now is apparently the preferred way according to this: https://maven.apache.org/plugins/maven-jar-plugin/examples/create-test-jar.html --- commonmark-ext-autolink/pom.xml | 8 +-- .../commonmark/ext/autolink/AutolinkTest.java | 12 +++- commonmark-ext-gfm-strikethrough/pom.xml | 8 +-- .../gfm/strikethrough/StrikethroughTest.java | 11 +++- commonmark-ext-gfm-tables/pom.xml | 8 +-- .../commonmark/ext/gfm/tables/TablesTest.java | 12 +++- commonmark-integration-test/pom.xml | 10 +--- .../integration/SpecIntegrationTest.java | 25 +++++---- commonmark-test-util/pom.xml | 22 ++++++++ .../java/org/commonmark/spec/SpecExample.java | 1 + .../java/org/commonmark/spec/SpecReader.java | 10 ++-- .../commonmark/test/RenderingTestCase.java | 22 ++++++++ .../org/commonmark/test/SpecTestCase.java | 37 +++++++++++++ .../src/main}/resources/spec.txt | 0 commonmark/pom.xml | 21 +------ .../test/CoreRenderingTestCase.java | 15 +++++ .../java/org/commonmark/test/ParserTest.java | 19 +++---- .../org/commonmark/test/PathologicalTest.java | 2 +- .../commonmark/test/RenderingTestCase.java | 55 ------------------- .../test/{SpecTest.java => SpecCoreTest.java} | 41 ++++---------- .../org/commonmark/test/SpecialInputTest.java | 2 +- etc/update-spec.sh | 2 +- pom.xml | 4 +- 23 files changed, 172 insertions(+), 175 deletions(-) create mode 100644 commonmark-test-util/pom.xml rename {commonmark/src/test => commonmark-test-util/src/main}/java/org/commonmark/spec/SpecExample.java (99%) rename {commonmark/src/test => commonmark-test-util/src/main}/java/org/commonmark/spec/SpecReader.java (93%) create mode 100644 commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java create mode 100644 commonmark-test-util/src/main/java/org/commonmark/test/SpecTestCase.java rename {commonmark/src/test => commonmark-test-util/src/main}/resources/spec.txt (100%) create mode 100644 commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java delete mode 100644 commonmark/src/test/java/org/commonmark/test/RenderingTestCase.java rename commonmark/src/test/java/org/commonmark/test/{SpecTest.java => SpecCoreTest.java} (56%) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 5edb792a4..09f76840c 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -22,15 +22,9 @@ 0.2.0
    - - junit - junit - test - com.atlassian.commonmark - commonmark - test-jar + commonmark-test-util test
    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 ff6e8bd90..c78887d1e 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -1,13 +1,20 @@ package org.commonmark.ext.autolink; import org.commonmark.Extension; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; import org.junit.Test; import java.util.Collections; +import java.util.Set; public class AutolinkTest extends RenderingTestCase { + private static final Set EXTENSIONS = Collections.singleton(AutolinkExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + @Test public void oneTextNode() { assertRendering("foo http://one.org/ bar http://two.org/", @@ -47,8 +54,7 @@ public void dontLinkTextWithinLinks() { } @Override - protected Iterable getExtensions() { - return Collections.singleton(AutolinkExtension.create()); + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); } - } diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index e82d955c3..bfc7f3801 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -17,15 +17,9 @@ commonmark
    - - junit - junit - test - com.atlassian.commonmark - commonmark - test-jar + commonmark-test-util 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 5291a05e7..d65c1557d 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java @@ -1,13 +1,20 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.Extension; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; import org.junit.Test; import java.util.Collections; +import java.util.Set; public class StrikethroughTest extends RenderingTestCase { + private static final Set EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + @Test public void oneTildeIsNotEnough() { assertRendering("~foo~", "

    ~foo~

    \n"); @@ -57,7 +64,7 @@ public void insideBlockQuote() { } @Override - protected Iterable getExtensions() { - return Collections.singleton(StrikethroughExtension.create()); + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); } } diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 935a5533e..6708de797 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -17,15 +17,9 @@ commonmark - - junit - junit - test - com.atlassian.commonmark - commonmark - test-jar + commonmark-test-util 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 b19f9700d..5c81c38d0 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,13 +1,20 @@ package org.commonmark.ext.gfm.tables; import org.commonmark.Extension; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; import org.junit.Test; import java.util.Collections; +import java.util.Set; public class TablesTest extends RenderingTestCase { + private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + @Test public void mustHaveHeaderAndSeparator() { assertRendering("Abc|Def", "

    Abc|Def

    \n"); @@ -289,8 +296,7 @@ public void tableEndWithoutEmptyLine() { } @Override - protected Iterable getExtensions() { - return Collections.singleton(TablesExtension.create()); + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); } - } diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 0b69e1d41..804107e42 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -40,8 +40,8 @@ - junit - junit + com.atlassian.commonmark + commonmark-test-util test @@ -54,12 +54,6 @@ jmh-generator-annprocess test - - com.atlassian.commonmark - commonmark - test-jar - test - 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 3a3ce5e2a..be5f9d890 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,19 +4,26 @@ import org.commonmark.ext.autolink.AutolinkExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; import org.commonmark.spec.SpecExample; -import org.commonmark.test.SpecTest; +import org.commonmark.test.SpecTestCase; import org.junit.Test; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * Tests that the spec examples still render the same with all extensions enabled. */ -public class SpecIntegrationTest extends SpecTest { - +public class SpecIntegrationTest extends SpecTestCase { + + private static final List EXTENSIONS = Arrays.asList( + AutolinkExtension.create(), + StrikethroughExtension.create(), + TablesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + // The spec says URL-escaping is optional, but the examples assume that it's enabled. + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).percentEncodeUrls(true).build(); private static final Map OVERRIDDEN_EXAMPLES = getOverriddenExamples(); public SpecIntegrationTest(SpecExample example) { @@ -35,10 +42,8 @@ public void testHtmlRendering() { } @Override - protected Iterable getExtensions() { - return Arrays.asList(AutolinkExtension.create(), - StrikethroughExtension.create(), - TablesExtension.create()); + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); } private static Map getOverriddenExamples() { diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml new file mode 100644 index 000000000..5cf7b975f --- /dev/null +++ b/commonmark-test-util/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + com.atlassian.commonmark + commonmark-parent + 0.2.1-SNAPSHOT + + + commonmark-test-util + commonmark-java test utilities + commonmark-java classes for tests + + + + junit + junit + + + + diff --git a/commonmark/src/test/java/org/commonmark/spec/SpecExample.java b/commonmark-test-util/src/main/java/org/commonmark/spec/SpecExample.java similarity index 99% rename from commonmark/src/test/java/org/commonmark/spec/SpecExample.java rename to commonmark-test-util/src/main/java/org/commonmark/spec/SpecExample.java index 8e1f56219..17ed90162 100644 --- a/commonmark/src/test/java/org/commonmark/spec/SpecExample.java +++ b/commonmark-test-util/src/main/java/org/commonmark/spec/SpecExample.java @@ -1,6 +1,7 @@ package org.commonmark.spec; public class SpecExample { + private final String section; private final int exampleNumber; private final String source; diff --git a/commonmark/src/test/java/org/commonmark/spec/SpecReader.java b/commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java similarity index 93% rename from commonmark/src/test/java/org/commonmark/spec/SpecReader.java rename to commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java index 1a806ed3d..5a2b9dde3 100644 --- a/commonmark/src/test/java/org/commonmark/spec/SpecReader.java +++ b/commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java @@ -1,7 +1,5 @@ package org.commonmark.spec; -import org.commonmark.test.SpecTest; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -31,7 +29,7 @@ private SpecReader(InputStream stream) { } public static List readExamples() { - try (InputStream stream = getStream()) { + try (InputStream stream = getSpecInputStream()) { return new SpecReader(stream).read(); } catch (IOException e) { throw new RuntimeException(e); @@ -49,7 +47,7 @@ public static List readExamplesAsString() { public static String readSpec() { StringBuilder sb = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(getStream(), StandardCharsets.UTF_8))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(getSpecInputStream(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { sb.append(line); @@ -61,8 +59,8 @@ public static String readSpec() { } } - private static InputStream getStream() { - InputStream stream = SpecTest.class.getResourceAsStream("/spec.txt"); + public static InputStream getSpecInputStream() { + InputStream stream = SpecReader.class.getResourceAsStream("/spec.txt"); if (stream == null) { throw new IllegalStateException("Could not load spec.txt classpath resource"); } diff --git a/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java new file mode 100644 index 000000000..1ee19dbb2 --- /dev/null +++ b/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java @@ -0,0 +1,22 @@ +package org.commonmark.test; + +import static org.junit.Assert.assertEquals; + +public abstract class RenderingTestCase { + + protected abstract String render(String source); + + protected void assertRendering(String source, String expectedHtml) { + String html = render(source); + + // include source for better assertion errors + String expected = showTabs(expectedHtml + "\n\n" + source); + String actual = showTabs(html + "\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/test/SpecTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/test/SpecTestCase.java new file mode 100644 index 000000000..c81300cb4 --- /dev/null +++ b/commonmark-test-util/src/main/java/org/commonmark/test/SpecTestCase.java @@ -0,0 +1,37 @@ +package org.commonmark.test; + +import org.commonmark.spec.SpecExample; +import org.commonmark.spec.SpecReader; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(Parameterized.class) +public abstract class SpecTestCase extends RenderingTestCase { + + protected final SpecExample example; + + public SpecTestCase(SpecExample example) { + this.example = example; + } + + @Parameters(name = "{0}") + public static List data() { + List examples = SpecReader.readExamples(); + List data = new ArrayList<>(); + for (SpecExample example : examples) { + data.add(new Object[]{example}); + } + return data; + } + + @Test + public void testHtmlRendering() { + assertRendering(example.getSource(), example.getHtml()); + } + +} diff --git a/commonmark/src/test/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt similarity index 100% rename from commonmark/src/test/resources/spec.txt rename to commonmark-test-util/src/main/resources/spec.txt diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 9c20f88e0..23a834d8b 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -13,8 +13,8 @@ - junit - junit + com.atlassian.commonmark + commonmark-test-util test @@ -29,23 +29,6 @@ - - - - org.apache.maven.plugins - maven-jar-plugin - 2.6 - - - - test-jar - - - - - - - benchmark diff --git a/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java b/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java new file mode 100644 index 000000000..7c89caa2b --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java @@ -0,0 +1,15 @@ +package org.commonmark.test; + +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; + +public class CoreRenderingTestCase extends RenderingTestCase { + + private static final Parser PARSER = Parser.builder().build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index cc2dda1fc..e936b7cfd 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -7,29 +7,24 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import static org.junit.Assert.assertEquals; + +import org.commonmark.spec.SpecReader; import org.junit.Test; public class ParserTest { + @Test public void ioReaderTest() throws IOException { Parser parser = Parser.builder().build(); - InputStream input1 = ParserTest.class.getResourceAsStream("/spec.txt"); + InputStream input1 = SpecReader.getSpecInputStream(); Node document1; try (InputStreamReader reader = new InputStreamReader(input1)) { document1 = parser.parseReader(reader); } - - InputStream input2 = ParserTest.class.getResourceAsStream("/spec.txt"); - StringBuilder sb = new StringBuilder(); - try (InputStreamReader reader = new InputStreamReader(input2)) { - int ch; - while ((ch = reader.read()) != -1){ - sb.append((char)ch); - } - } - - Node document2 = parser.parse(sb.toString()); + + String spec = SpecReader.readSpec(); + Node document2 = parser.parse(spec); HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); assertEquals(renderer.render(document2), renderer.render(document1)); diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index a42582f92..8a5ed2a9d 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -14,7 +14,7 @@ * Pathological input cases (from commonmark.js). */ @FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class PathologicalTest extends RenderingTestCase { +public class PathologicalTest extends CoreRenderingTestCase { private static final int X = 10_000; diff --git a/commonmark/src/test/java/org/commonmark/test/RenderingTestCase.java b/commonmark/src/test/java/org/commonmark/test/RenderingTestCase.java deleted file mode 100644 index 613548ff3..000000000 --- a/commonmark/src/test/java/org/commonmark/test/RenderingTestCase.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.commonmark.test; - -import org.commonmark.Extension; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.junit.Before; - -import java.util.Collections; - -import static org.junit.Assert.assertEquals; - -public abstract class RenderingTestCase { - - protected Parser parser; - protected HtmlRenderer renderer; - - @Before - public void setup() { - Iterable extensions = getExtensions(); - - Parser.Builder parserBuilder = Parser.builder().extensions(extensions); - configureParser(parserBuilder); - parser = parserBuilder.build(); - - HtmlRenderer.Builder rendererBuilder = HtmlRenderer.builder().extensions(extensions); - configureRenderer(rendererBuilder); - renderer = rendererBuilder.build(); - } - - protected Iterable getExtensions() { - return Collections.emptyList(); - } - - protected void configureParser(Parser.Builder parserBuilder) { - } - - protected void configureRenderer(HtmlRenderer.Builder rendererBuilder) { - } - - protected void assertRendering(String source, String expectedHtml) { - Node node = parser.parse(source); - String html = renderer.render(node); - - // include source for better assertion errors - String expected = showTabs(expectedHtml + "\n\n" + source); - String actual = showTabs(html + "\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/src/test/java/org/commonmark/test/SpecTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java similarity index 56% rename from commonmark/src/test/java/org/commonmark/test/SpecTest.java rename to commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index 7d62c22e2..358aaea4d 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -4,46 +4,26 @@ import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; import org.commonmark.node.Text; +import org.commonmark.parser.Parser; import org.commonmark.spec.SpecExample; -import org.commonmark.spec.SpecReader; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import java.util.ArrayList; -import java.util.List; import static org.junit.Assert.fail; -@RunWith(Parameterized.class) -public class SpecTest extends RenderingTestCase { - - protected final SpecExample example; - - public SpecTest(SpecExample example) { - this.example = example; - } +public class SpecCoreTest extends SpecTestCase { - @Parameters(name = "{0}") - public static List data() { - List examples = SpecReader.readExamples(); - List data = new ArrayList<>(); - for (SpecExample example : examples) { - data.add(new Object[]{example}); - } - return data; - } + private static final Parser PARSER = Parser.builder().build(); + // The spec says URL-escaping is optional, but the examples assume that it's enabled. + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); - @Test - public void testHtmlRendering() { - assertRendering(example.getSource(), example.getHtml()); + public SpecCoreTest(SpecExample example) { + super(example); } @Test public void testTextNodesContiguous() { final String source = example.getSource(); - Node node = parser.parse(source); + Node node = PARSER.parse(source); node.accept(new AbstractVisitor() { @Override protected void visitChildren(Node parent) { @@ -69,8 +49,7 @@ protected void visitChildren(Node parent) { } @Override - protected void configureRenderer(HtmlRenderer.Builder rendererBuilder) { - // The spec says URL-escaping is optional, but the examples assume that it's enabled. - rendererBuilder.percentEncodeUrls(true); + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 0596fed19..b207ccae5 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -2,7 +2,7 @@ import org.junit.Test; -public class SpecialInputTest extends RenderingTestCase { +public class SpecialInputTest extends CoreRenderingTestCase { @Test public void empty() { diff --git a/etc/update-spec.sh b/etc/update-spec.sh index 2879fc4a0..8c9021a86 100755 --- a/etc/update-spec.sh +++ b/etc/update-spec.sh @@ -6,4 +6,4 @@ if [ "$#" -ne 1 ]; then fi version=$1 -curl -L "https://raw.githubusercontent.com/jgm/CommonMark/$version/spec.txt" -o commonmark/src/test/resources/spec.txt +curl -L "https://raw.githubusercontent.com/jgm/CommonMark/$version/spec.txt" -o commonmark-test-util/src/main/resources/spec.txt diff --git a/pom.xml b/pom.xml index 1babc0dea..e543a51a5 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,7 @@ commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables commonmark-integration-test + commonmark-test-util @@ -108,9 +109,8 @@ com.atlassian.commonmark - commonmark + commonmark-test-util 0.2.1-SNAPSHOT - test-jar From 1c76a18ebe880cabf374a85fc90f778595eb12ad Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 22 Sep 2015 18:11:47 +1000 Subject: [PATCH 026/815] Allow block parsers from extensions to override core behavior (#18) --- .../commonmark/internal/DocumentParser.java | 3 +- .../commonmark/internal/util/Substring.java | 10 +++ .../java/org/commonmark/parser/Parser.java | 10 +++ .../java/org/commonmark/test/ParserTest.java | 63 ++++++++++++++++--- 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index aeab876d4..60aacf9dc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -57,8 +57,9 @@ public DocumentParser(List blockParserFactories, InlineParse public static List calculateBlockParserFactories(List customBlockParserFactories) { List list = new ArrayList<>(); - list.addAll(DocumentParser.CORE_FACTORIES); + // By having the custom factories come first, extensions are able to change behavior of core syntax. list.addAll(customBlockParserFactories); + list.addAll(DocumentParser.CORE_FACTORIES); return list; } diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Substring.java b/commonmark/src/main/java/org/commonmark/internal/util/Substring.java index 426c24e06..920ff679e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Substring.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Substring.java @@ -41,4 +41,14 @@ public CharSequence subSequence(int start, int end) { public String toString() { return base.substring(beginIndex, endIndex); } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj == this || (obj instanceof CharSequence && toString().equals(obj)); + } } diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 3fe25d5b6..886990860 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -114,6 +114,16 @@ public Builder extensions(Iterable extensions) { return this; } + /** + * Adds a custom block parser factory. + *

    + * Note that custom factories are applied before the built-in factories. This is so that + * extensions can change how some syntax is parsed that would otherwise be handled by built-in factories. + * "With great power comes great responsibility." + * + * @param blockParserFactory a block parser factory implementation + * @return {@code this} + */ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { blockParserFactories.add(blockParserFactory); return this; diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index e936b7cfd..951dfda8b 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,22 +1,28 @@ package org.commonmark.test; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import org.commonmark.html.HtmlRenderer; -import org.commonmark.node.Node; +import org.commonmark.node.*; import org.commonmark.parser.Parser; -import static org.junit.Assert.assertEquals; - +import org.commonmark.parser.block.*; import org.commonmark.spec.SpecReader; +import org.hamcrest.CoreMatchers; import org.junit.Test; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + public class ParserTest { @Test public void ioReaderTest() throws IOException { Parser parser = Parser.builder().build(); - + InputStream input1 = SpecReader.getSpecInputStream(); Node document1; try (InputStreamReader reader = new InputStreamReader(input1)) { @@ -25,8 +31,49 @@ public void ioReaderTest() throws IOException { String spec = SpecReader.readSpec(); Node document2 = parser.parse(spec); - + HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); assertEquals(renderer.render(document2), renderer.render(document1)); } + + @Test + public void customBlockParserFactory() { + Parser parser = Parser.builder().customBlockParserFactory(new DashBlockParserFactory()).build(); + + // The dashes would normally be a HorizontalRule + 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)); + } + + 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().equals("---")) { + return BlockStart.of(new DashBlockParser()); + } + return BlockStart.none(); + } + } } From 2893dae135c00d967756d38762ba6d52be62c7c7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 22 Sep 2015 19:01:38 +1000 Subject: [PATCH 027/815] README: Use word counting as example for Visitor (#15) --- README.md | 22 ++++++++++------ .../org/commonmark/test/UsageExampleTest.java | 25 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 62f3a02b2..eb53255da 100644 --- a/README.md +++ b/README.md @@ -59,17 +59,23 @@ of the caller. #### Use a visitor to process parsed nodes ```java -Node node = parser.parse("..."); -MyVisitor visitor = new MyVisitor(); +Node node = parser.parse("Example\n=======\n\nSome more text"); +WordCountVisitor visitor = new WordCountVisitor(); node.accept(visitor); +visitor.wordCount; // 4 + +class WordCountVisitor extends AbstractVisitor { + int wordCount = 0; -class MyVisitor extends AbstractVisitor { @Override - public void visit(Paragraph paragraph) { - // Do something with paragraph (override other methods for other nodes): - System.out.println(paragraph); - // Descend into children: - visitChildren(paragraph); + public void visit(Text text) { + // This is called for all Text nodes. Override other visit methods for other node types. + + // Count words (this is just an example, don't actually do it this way for various reasons). + wordCount += text.getLiteral().split("\\W+").length; + + // Descend into children (could be omitted in this case because Text nodes don't have children). + visitChildren(text); } } ``` diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 54c180aae..0bc079ea1 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -3,7 +3,7 @@ import org.commonmark.html.HtmlRenderer; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; -import org.commonmark.node.Paragraph; +import org.commonmark.node.Text; import org.commonmark.parser.Parser; import org.junit.Test; @@ -22,18 +22,25 @@ public void parseAndRender() { @Test public void visitor() { Parser parser = Parser.builder().build(); - Node node = parser.parse("..."); - MyVisitor visitor = new MyVisitor(); + Node node = parser.parse("Example\n=======\n\nSome more text"); + WordCountVisitor visitor = new WordCountVisitor(); node.accept(visitor); + assertEquals(4, visitor.wordCount); } - class MyVisitor extends AbstractVisitor { + class WordCountVisitor extends AbstractVisitor { + + int wordCount = 0; + @Override - public void visit(Paragraph paragraph) { - // Do something with paragraph (override other methods for other nodes): - System.out.println(paragraph); - // Descend into children: - visitChildren(paragraph); + public void visit(Text text) { + // This is called for all Text nodes. Override other visit methods for other node types. + + // Count words (this is just an example, don't actually do it this way for various reasons). + wordCount += text.getLiteral().split("\\W+").length; + + // Descend into children (could be omitted in this case because Text nodes don't have children). + visitChildren(text); } } From dfa0d800280538c44e30e4f94ec540be05f78a8a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Sep 2015 15:05:00 +1000 Subject: [PATCH 028/815] Add more tests for line endings --- .../src/test/java/org/commonmark/test/SpecialInputTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index b207ccae5..55c28f767 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -32,6 +32,7 @@ public void crLfAtEndShouldBeParsed() { @Test public void mixedLineSeparators() { assertRendering("- a\n- b\r- c\r\n- d", "

      \n
    • a
    • \n
    • b
    • \n
    • c
    • \n
    • d
    • \n
    \n"); + assertRendering("a\n\nb\r\rc\r\n\r\nd\n\re", "

    a

    \n

    b

    \n

    c

    \n

    d

    \n

    e

    \n"); } @Test From 1bc49f939608de286f8487d756e307ad16fc8fb9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Sep 2015 15:05:34 +1000 Subject: [PATCH 029/815] Disallow list item starting with multiple blank lines (spec 0.22, #14) See jgm/CommonMark#332. --- .../src/main/java/org/commonmark/internal/ListItemParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java index dfdae39b9..2ae0a767f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java @@ -33,7 +33,7 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState state) { - if (state.isBlank()) { + if (state.isBlank() && block.getFirstChild() != null) { return BlockContinue.atIndex(state.getNextNonSpaceIndex()); } From 4b2c70c8b2e2c251e458f2a8d844fcbd31f66121 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Sep 2015 15:06:46 +1000 Subject: [PATCH 030/815] Update tag names of block starts (spec 0.22, #14) --- .../src/main/java/org/commonmark/internal/HtmlBlockParser.java | 3 ++- 1 file changed, 2 insertions(+), 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 12b17cc9f..0e5f4fafd 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -40,11 +40,12 @@ public class HtmlBlockParser extends AbstractBlockParser { "dd|details|dialog|dir|div|dl|dt|" + "fieldset|figcaption|figure|footer|form|frame|frameset|" + "h1|head|header|hr|html|" + + "iframe|" + "legend|li|link|" + "main|menu|menuitem|meta|" + "nav|noframes|" + "ol|optgroup|option|" + - "p|param|pre|" + + "p|param|" + "section|source|summary|" + "table|tbody|td|tfoot|th|thead|title|tr|track|" + "ul" + From 0051e3df151d680085c5132a347120785c28bf01 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Sep 2015 15:08:09 +1000 Subject: [PATCH 031/815] Update to spec 0.22 (#14) See previous commits for necessary changes. --- .../src/main/resources/spec.txt | 156 ++++++++++++------ 1 file changed, 109 insertions(+), 47 deletions(-) diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index bdb9569d2..ff44e4afa 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.21 -date: +version: 0.22 +date: 2015-08-23 license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -204,16 +204,22 @@ In the examples, the `→` character is used to represent tabs. Any sequence of [character]s is a valid CommonMark document. -A [character](@character) is a unicode code point. +A [character](@character) is a Unicode code point. Although some +code points (for example, combining accents) do not correspond to +characters in an intuitive sense, all code points count as characters +for purposes of this spec. + This spec does not specify an encoding; it thinks of lines as composed -of characters rather than bytes. A conforming parser may be limited +of [character]s rather than bytes. A conforming parser may be limited to a certain encoding. A [line](@line) is a sequence of zero or more [character]s +other than newline (`U+000A`) or carriage return (`U+000D`), followed by a [line ending] or by the end of file. -A [line ending](@line-ending) is a newline (`U+000A`), carriage return -(`U+000D`), or carriage return + newline. +A [line ending](@line-ending) is a newline (`U+000A`), a carriage return +(`U+000D`) not followed by a newline, or a carriage return and a +following newline. A line containing no characters, or a line containing only spaces (`U+0020`) or tabs (`U+0009`), is called a [blank line](@blank-line). @@ -227,17 +233,17 @@ form feed (`U+000C`), or carriage return (`U+000D`). [Whitespace](@whitespace) is a sequence of one or more [whitespace character]s. -A [unicode whitespace character](@unicode-whitespace-character) is -any code point in the unicode `Zs` class, or a tab (`U+0009`), +A [Unicode whitespace character](@unicode-whitespace-character) is +any code point in the Unicode `Zs` class, or a tab (`U+0009`), carriage return (`U+000D`), newline (`U+000A`), or form feed (`U+000C`). [Unicode whitespace](@unicode-whitespace) is a sequence of one -or more [unicode whitespace character]s. +or more [Unicode whitespace character]s. A [space](@space) is `U+0020`. -A [non-whitespace character](@non-space-character) is any character +A [non-whitespace character](@non-whitespace-character) is any character that is not a [whitespace character]. An [ASCII punctuation character](@ascii-punctuation-character) @@ -247,7 +253,7 @@ is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, A [punctuation character](@punctuation-character) is an [ASCII punctuation character] or anything in -the unicode classes `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. +the Unicode classes `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. ## Tabs @@ -300,6 +306,15 @@ by spaces with a tab stop of 4 characters. . +. + foo +→bar +. +
    foo
    +bar
    +
    +. + ## Insecure characters @@ -562,8 +577,8 @@ If you want a horizontal rule in a list item, use a different bullet: An [ATX header](@atx-header) 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 `#` characters. The opening sequence -of `#` characters cannot be followed directly by a +closing sequence of any number of unescaped `#` characters. +The opening sequence of `#` characters cannot be followed directly by a [non-whitespace character]. The optional closing sequence of `#`s must be preceded by a [space] and may be followed by spaces only. The opening `#` character may be indented 0-3 spaces. The raw contents of the @@ -695,8 +710,7 @@ Spaces are allowed after the closing sequence:

    foo

    . -A sequence of `#` characters with a -[non-whitespace character] following it +A sequence of `#` characters with anything but [space]s following it is not a closing sequence, but counts as part of the contents of the header: @@ -1646,22 +1660,23 @@ followed by one of the strings (case-insensitive) `address`, `caption`, `center`, `col`, `colgroup`, `dd`, `details`, `dialog`, `dir`, `div`, `dl`, `dt`, `fieldset`, `figcaption`, `figure`, `footer`, `form`, `frame`, `frameset`, `h1`, `head`, `header`, `hr`, -`html`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, `meta`, -`nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, `pre`, -`section`, `source`, `title`, `summary`, `table`, `tbody`, `td`, +`html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, +`meta`, `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, +`section`, `source`, `summary`, `table`, `tbody`, `td`, `tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed by [whitespace], the end of the line, the string `>`, or the string `/>`.\ **End condition:** line is followed by a [blank line]. -7. **Start condition:** line begins with an [open tag] -(with any [tag name]) followed only by [whitespace] or the end -of the line.\ +7. **Start condition:** line begins with a complete [open tag] +or [closing tag] (with any [tag name] other than `script`, +`style`, or `pre`) followed only by [whitespace] +or the end of the line.\ **End condition:** line is followed by a [blank line]. All types of [HTML blocks] except type 7 may interrupt a paragraph. Blocks of type 7 may not interrupt a paragraph. -(This restricted is intended to prevent unwanted interpretation +(This restriction is intended to prevent unwanted interpretation of long tags inside a wrapped paragraph as starting HTML blocks.) Some simple examples follow. Here are some basic HTML blocks @@ -1861,6 +1876,14 @@ In type 7 blocks, the [tag name] can be anything: . +. + +*bar* +. + +*bar* +. + These rules are designed to allow us to work with tags that can function as either block-level or inline-level tags. The `` tag is a nice example. We can surround content with @@ -2831,8 +2854,8 @@ foo

    . Laziness only applies to lines that would have been continuations of -paragraphs had they been prepended with `>`. For example, the -`>` cannot be omitted in the second line of +paragraphs had they been prepended with [block quote marker]s. +For example, the `> ` cannot be omitted in the second line of ``` markdown > foo @@ -2851,7 +2874,7 @@ without changing the meaning:
    . -Similarly, if we omit the `>` in the second line of +Similarly, if we omit the `> ` in the second line of ``` markdown > - foo @@ -2874,7 +2897,7 @@ then the block quote ends after the first line:
. -For the same reason, we can't omit the `>` in front of +For the same reason, we can't omit the `> ` in front of subsequent lines of an indented or fenced code block: . @@ -2901,6 +2924,30 @@ foo
. +Note that in the following case, we have a paragraph +continuation line: + +. +> foo + - bar +. +
+

foo +- bar

+
+. + +To see why, note that in + +```markdown +> foo +> - bar +``` + +the `- bar` is indented too far to start a list, and can't +be an indented code block because indented code blocks cannot +interrupt paragraphs, so it is a [paragraph continuation line]. + A block quote can be empty: . @@ -3605,6 +3652,21 @@ Here are some list items that start with a blank line but are not empty: . +A list item can begin with at most one blank line. +In the following example, `foo` is not part of the list +item: + +. +- + + foo +. +
    +
  • +
+

foo

+. + Here is an empty bullet list item: . @@ -4849,17 +4911,17 @@ foo With the goal of making this standard as HTML-agnostic as possible, all valid HTML entities (except in code blocks and code spans) -are recognized as such and converted into unicode characters before +are recognized as such and converted into Unicode characters before they are stored in the AST. This means that renderers to formats other than HTML need not be HTML-entity aware. HTML renderers may either escape -unicode characters as entities or leave them as they are. (However, +Unicode characters as entities or leave them as they are. (However, `"`, `&`, `<`, and `>` must always be rendered as entities.) -[Named entities](@name-entities) consist of `&` -+ any of the valid HTML5 entity names + `;`. The +[Named entities](@name-entities) consist of `&` + any of the valid +HTML5 entity names + `;`. The [following document](https://html.spec.whatwg.org/multipage/entities.json) is used as an authoritative source of the valid entity names and their -corresponding codepoints. +corresponding code points. .   & © Æ Ď @@ -4874,9 +4936,9 @@ corresponding codepoints. [Decimal entities](@decimal-entities) consist of `&#` + a string of 1--8 arabic digits + `;`. Again, these entities need to be recognised and transformed into their corresponding -unicode codepoints. Invalid unicode codepoints will be replaced by -the "unknown codepoint" character (`U+FFFD`). For security reasons, -the codepoint `U+0000` will also be replaced by `U+FFFD`. +Unicode code points. Invalid Unicode code points will be replaced by +the "unknown code point" character (`U+FFFD`). For security reasons, +the code point `U+0000` will also be replaced by `U+FFFD`. . # Ӓ Ϡ � � @@ -4884,10 +4946,10 @@ the codepoint `U+0000` will also be replaced by `U+FFFD`.

# Ӓ Ϡ � �

. -[Hexadecimal entities](@hexadecimal-entities) -consist of `&#` + either `X` or `x` + a string of 1-8 hexadecimal digits -+ `;`. They will also be parsed and turned into the corresponding -unicode codepoints in the AST. +[Hexadecimal entities](@hexadecimal-entities) consist of `&#` + either +`X` or `x` + a string of 1-8 hexadecimal digits + `;`. They will also +be parsed and turned into the corresponding Unicode code points in the +AST. . " ആ ಫ @@ -5179,18 +5241,18 @@ followed by a `*` character, or a sequence of one or more `_` characters that is not preceded or followed by a `_` character. A [left-flanking delimiter run](@left-flanking-delimiter-run) is -a [delimiter run] that is (a) not followed by [unicode whitespace], +a [delimiter run] that is (a) not followed by [Unicode whitespace], and (b) either not followed by a [punctuation character], or -preceded by [unicode whitespace] or a [punctuation character]. +preceded by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of -the line count as unicode whitespace. +the line count as Unicode whitespace. A [right-flanking delimiter run](@right-flanking-delimiter-run) is -a [delimiter run] that is (a) not preceded by [unicode whitespace], +a [delimiter run] that is (a) not preceded by [Unicode whitespace], and (b) either not preceded by a [punctuation character], or -followed by [unicode whitespace] or a [punctuation character]. +followed by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of -the line count as unicode whitespace. +the line count as Unicode whitespace. Here are some examples of delimiter runs. @@ -6511,8 +6573,8 @@ just a backslash: URL-escaping should be left alone inside the destination, as all URL-escaped characters are also valid URL characters. HTML entities in -the destination will be parsed into the corresponding unicode -codepoints, as usual, and optionally URL-escaped when written as HTML. +the destination will be parsed into the corresponding Unicode +code points, as usual, and optionally URL-escaped when written as HTML. . [link](foo%20bä) @@ -6721,7 +6783,7 @@ characters inside the square brackets. One label [matches](@matches) another just in case their normalized forms are equal. To normalize a -label, perform the *unicode case fold* and collapse consecutive internal +label, perform the *Unicode case fold* and collapse consecutive internal [whitespace] to a single space. If there are multiple matching reference link definitions, the one that comes first in the document is used. (It is desirable in such cases to emit a warning.) From 29476d65179ae483e6ddb59726ac54aaa2133c0c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Sep 2015 15:14:25 +1000 Subject: [PATCH 032/815] README: Add link to spec.txt for finding out implemented spec version --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index eb53255da..754119165 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ 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. +See the [spec.txt](commonmark-test-util/src/main/resources/spec.txt) file if +you're wondering which version of the spec is currently implemented. + [![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) From 3f13409895dfe8cb2d3ac2a5f910a1c6dbff5117 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 28 Sep 2015 17:38:25 +1000 Subject: [PATCH 033/815] Bump base pom, which should allow us to get rid of workaround --- pom.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index e543a51a5..c97f9277d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.atlassian.pom central-pom - 3.0.95 + 3.0.98 pom @@ -74,12 +74,6 @@ true - - org.sonatype.plugins - nexus-staging-maven-plugin - - 1.6.6 - From b08674758514c733e9dad86d3121553ce4b2c96f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 15 Oct 2015 15:51:56 +1100 Subject: [PATCH 034/815] [maven-release-plugin] prepare release commonmark-parent-0.3.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 5 ++--- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 09f76840c..a1214616d 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.1-SNAPSHOT + 0.3.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index bfc7f3801..022d444cf 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.1-SNAPSHOT + 0.3.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 6708de797..10b9bcb59 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.1-SNAPSHOT + 0.3.0 commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 804107e42..fa8331104 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.1-SNAPSHOT + 0.3.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 5cf7b975f..a39c052e3 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -1,11 +1,10 @@ - + 4.0.0 com.atlassian.commonmark commonmark-parent - 0.2.1-SNAPSHOT + 0.3.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 23a834d8b..be54376cd 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.1-SNAPSHOT + 0.3.0 commonmark diff --git a/pom.xml b/pom.xml index c97f9277d..8c1940b12 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.2.1-SNAPSHOT + 0.3.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.2.1-SNAPSHOT + 0.3.0 com.atlassian.commonmark commonmark-ext-autolink - 0.2.1-SNAPSHOT + 0.3.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.2.1-SNAPSHOT + 0.3.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.2.1-SNAPSHOT + 0.3.0 com.atlassian.commonmark commonmark-test-util - 0.2.1-SNAPSHOT + 0.3.0 @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.3.0 From d40efa19ab070b42312c2cb9f33d9f4000851fb5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 15 Oct 2015 15:51:56 +1100 Subject: [PATCH 035/815] [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-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index a1214616d..2945d51ad 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.0 + 0.3.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 022d444cf..30ed431be 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.0 + 0.3.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 10b9bcb59..7da0e9a60 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.0 + 0.3.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index fa8331104..a0a7e6562 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.0 + 0.3.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index a39c052e3..2daf60908 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.0 + 0.3.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index be54376cd..9379b06ef 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.0 + 0.3.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 8c1940b12..0c30c7cd9 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.0 + 0.3.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.3.0 + 0.3.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.3.0 + 0.3.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.3.0 + 0.3.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.3.0 + 0.3.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.3.0 + 0.3.1-SNAPSHOT @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.3.0 + HEAD From 539212d918c87c65f37018c8ee06fa881f7effdb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 15 Oct 2015 16:20:18 +1100 Subject: [PATCH 036/815] README: Bump version to 0.3.0 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 754119165..68e277b05 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.2.0 + 0.3.0 ``` @@ -104,7 +104,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.2.0 + 0.3.0 ``` From 6e9805e6e899a2123581e392778cdfc0b11ba342 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 Dec 2015 13:05:27 +1100 Subject: [PATCH 037/815] Use input, index instead of subject, pos Makes it consistent with document parser. Also remove some unnecessary this. --- .../commonmark/internal/InlineParserImpl.java | 233 +++++++++--------- 1 file changed, 113 insertions(+), 120 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index ea145c595..5d079ebb2 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -82,8 +82,10 @@ public class InlineParserImpl implements InlineParser { private Map referenceMap = new HashMap<>(); private Node block; - private String subject; - private int pos; + + private String input; + private int index; + /** * Stack of delimiters (emphasis, strong emphasis). */ @@ -147,8 +149,8 @@ private static void addDelimiterProcessors(Iterable delimite @Override public void parse(String content, Node block) { this.block = block; - this.subject = content.trim(); - this.pos = 0; + this.input = content.trim(); + this.index = 0; this.delimiter = null; this.bracketDelimiterBottom = null; @@ -167,49 +169,45 @@ public void parse(String content, Node block) { * @return how many characters were parsed as a reference, {@code 0} if none */ public int parseReference(String s) { - this.subject = s; - this.pos = 0; - String rawLabel; + this.input = s; + this.index = 0; String dest; String title; int matchChars; - int startPos = this.pos; + int startIndex = index; // label: - matchChars = this.parseLinkLabel(); + matchChars = parseLinkLabel(); if (matchChars == 0) { return 0; - } else { - rawLabel = this.subject.substring(0, matchChars); } + String rawLabel = input.substring(0, matchChars); + // colon: - if (this.peek() == ':') { - this.pos++; - } else { - this.pos = startPos; + if (peek() != ':') { return 0; } + index++; // link url - this.spnl(); + spnl(); - dest = this.parseLinkDestination(); + dest = parseLinkDestination(); if (dest == null || dest.length() == 0) { - this.pos = startPos; return 0; } - int beforeTitle = this.pos; - this.spnl(); - title = this.parseLinkTitle(); + int beforeTitle = index; + spnl(); + title = parseLinkTitle(); if (title == null) { // rewind before spaces - this.pos = beforeTitle; + index = beforeTitle; } boolean atLineEnd = true; - if (this.pos != this.subject.length() && this.match(LINE_END) == null) { + if (index != input.length() && match(LINE_END) == null) { if (title == null) { atLineEnd = false; } else { @@ -218,20 +216,18 @@ public int parseReference(String s) { // discard the title title = null; // rewind before spaces - this.pos = beforeTitle; + index = beforeTitle; // and instead check if the link URL is at the line end - atLineEnd = this.match(LINE_END) != null; + atLineEnd = match(LINE_END) != null; } } if (!atLineEnd) { - this.pos = startPos; return 0; } String normalizedLabel = Escaping.normalizeReference(rawLabel); if (normalizedLabel.isEmpty()) { - this.pos = startPos; return 0; } @@ -239,7 +235,7 @@ public int parseReference(String s) { Link link = new Link(dest, title); referenceMap.put(normalizedLabel, link); } - return this.pos - startPos; + return index - startIndex; } private void appendText(CharSequence text) { @@ -273,40 +269,40 @@ private void flushTextNode() { } /** - * Parse the next inline element in subject, advancing subject position. + * Parse the next inline element in subject, advancing input index. * On success, add the result to block's children and return true. * On failure, return false. */ private boolean parseInline() { boolean res; - char c = this.peek(); + char c = peek(); if (c == '\0') { return false; } switch (c) { case '\n': - res = this.parseNewline(); + res = parseNewline(); break; case '\\': - res = this.parseBackslash(); + res = parseBackslash(); break; case '`': - res = this.parseBackticks(); + res = parseBackticks(); break; case '[': - res = this.parseOpenBracket(); + res = parseOpenBracket(); break; case '!': - res = this.parseBang(); + res = parseBang(); break; case ']': - res = this.parseCloseBracket(); + res = parseCloseBracket(); break; case '<': - res = this.parseAutolink() || this.parseHtmlTag(); + res = parseAutolink() || parseHtmlTag(); break; case '&': - res = this.parseEntity(); + res = parseEntity(); break; default: boolean isDelimiter = delimiterCharacters.get(c); @@ -314,12 +310,12 @@ private boolean parseInline() { DelimiterProcessor inlineDelimiter = delimiterProcessors.get(c); res = parseDelimiters(inlineDelimiter); } else { - res = this.parseString(); + res = parseString(); } break; } if (!res) { - this.pos += 1; + index++; // When we get here, it's only for a single special character that turned out to not have a special meaning. // So we shouldn't have a single surrogate here, hence it should be ok to turn it into a String. String literal = String.valueOf(c); @@ -330,18 +326,17 @@ private boolean parseInline() { } /** - * If re matches at current position in the subject, advance position in subject and return the match; otherwise - * return null. + * If RE matches at current index in the input, advance index and return the match; otherwise return null. */ private String match(Pattern re) { - if (pos >= subject.length()) { + if (index >= input.length()) { return null; } - Matcher matcher = re.matcher(subject); - matcher.region(pos, subject.length()); + Matcher matcher = re.matcher(input); + matcher.region(index, input.length()); boolean m = matcher.find(); if (m) { - pos = matcher.end(); + index = matcher.end(); return matcher.group(); } else { return null; @@ -349,11 +344,11 @@ private String match(Pattern re) { } /** - * Returns the char at the current subject position, or {@code '\0'} in case there are no more characters. + * Returns the char at the current input index, or {@code '\0'} in case there are no more characters. */ private char peek() { - if (this.pos < this.subject.length()) { - return this.subject.charAt(this.pos); + if (index < input.length()) { + return input.charAt(index); } else { return '\0'; } @@ -363,7 +358,7 @@ private char peek() { * Parse zero or more space characters, including at most one newline. */ private boolean spnl() { - this.match(SPNL); + match(SPNL); return true; } @@ -371,7 +366,7 @@ private boolean spnl() { * Parse a newline. If it was preceded by two spaces, return a hard line break; otherwise a soft line break. */ private boolean parseNewline() { - this.pos += 1; // assume we're at a \n + index++; // assume we're at a \n // We're gonna add a new node in any case and we need to check the last text node, so flush outstanding text. flushTextNode(); @@ -393,8 +388,8 @@ private boolean parseNewline() { } // gobble leading spaces in next line - while (pos < subject.length() && subject.charAt(pos) == ' ') { - pos++; + while (index < input.length() && input.charAt(index) == ' ') { + index++; } return true; } @@ -404,14 +399,13 @@ private boolean parseNewline() { * (if the backslash is followed by a newline), or a literal backslash to the block's children. */ private boolean parseBackslash() { - String subj = this.subject; - pos++; + index++; if (peek() == '\n') { appendNode(new HardLineBreak()); - pos++; - } else if (pos < subj.length() && ESCAPABLE.matcher(subj.substring(pos, pos + 1)).matches()) { - appendText(subj, pos, pos + 1); - pos++; + index++; + } else if (index < input.length() && ESCAPABLE.matcher(input.substring(index, index + 1)).matches()) { + appendText(input, index, index + 1); + index++; } else { appendText("\\"); } @@ -422,16 +416,16 @@ private boolean parseBackslash() { * Attempt to parse backticks, adding either a backtick code span or a literal sequence of backticks. */ private boolean parseBackticks() { - String ticks = this.match(TICKS_HERE); + String ticks = match(TICKS_HERE); if (ticks == null) { return false; } - int afterOpenTicks = this.pos; + int afterOpenTicks = index; String matched; - while ((matched = this.match(TICKS)) != null) { + while ((matched = match(TICKS)) != null) { if (matched.equals(ticks)) { Code node = new Code(); - String content = this.subject.substring(afterOpenTicks, this.pos - ticks.length()); + String content = input.substring(afterOpenTicks, index - ticks.length()); String literal = WHITESPACE.matcher(content.trim()).replaceAll(" "); node.setLiteral(literal); appendNode(node); @@ -439,7 +433,7 @@ private boolean parseBackticks() { } } // If we got here, we didn't match a closing backtick sequence. - this.pos = afterOpenTicks; + index = afterOpenTicks; appendText(ticks); return true; } @@ -448,18 +442,18 @@ private boolean parseBackticks() { * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ private boolean parseDelimiters(DelimiterProcessor inlineDelimiter) { - DelimiterRun res = this.scanDelims(inlineDelimiter); + DelimiterRun res = scanDelims(inlineDelimiter); if (res == null) { return false; } int numDelims = res.count; - int startPos = this.pos; + int startIndex = index; - this.pos += numDelims; - Text node = appendSeparateText(this.subject.substring(startPos, this.pos)); + index += numDelims; + Text node = appendSeparateText(input.substring(startIndex, index)); // Add entry to stack for this opener - this.delimiter = new Delimiter(node, this.delimiter, startPos); + this.delimiter = new Delimiter(node, this.delimiter, startIndex); this.delimiter.delimiterChar = inlineDelimiter.getDelimiterChar(); this.delimiter.numDelims = numDelims; this.delimiter.canOpen = res.canOpen; @@ -475,13 +469,13 @@ private boolean parseDelimiters(DelimiterProcessor inlineDelimiter) { * Add open bracket to delimiter stack and add a text node to block's children. */ private boolean parseOpenBracket() { - int startPos = this.pos; - this.pos += 1; + int startIndex = index; + index++; Text node = appendSeparateText("["); // Add entry to stack for this opener - this.delimiter = new Delimiter(node, this.delimiter, startPos); + this.delimiter = new Delimiter(node, this.delimiter, startIndex); this.delimiter.delimiterChar = '['; this.delimiter.numDelims = 1; this.delimiter.canOpen = true; @@ -499,15 +493,15 @@ private boolean parseOpenBracket() { * Otherwise just add a text node. */ private boolean parseBang() { - int startPos = this.pos; - this.pos += 1; - if (this.peek() == '[') { - this.pos += 1; + int startIndex = index; + index++; + if (peek() == '[') { + index++; Text node = appendSeparateText("!["); // Add entry to stack for this opener - this.delimiter = new Delimiter(node, this.delimiter, startPos + 1); + this.delimiter = new Delimiter(node, this.delimiter, startIndex + 1); this.delimiter.delimiterChar = '!'; this.delimiter.numDelims = 1; this.delimiter.canOpen = true; @@ -527,8 +521,8 @@ private boolean parseBang() { * plain [ character, to block's children. If there is a matching delimiter, remove it from the delimiter stack. */ private boolean parseCloseBracket() { - this.pos += 1; - int startPos = this.pos; + index++; + int startIndex = index; boolean containsBracket = false; // look through stack of delimiters for a [ or ![ @@ -566,38 +560,38 @@ private boolean parseCloseBracket() { boolean isLinkOrImage = false; // Inline link? - if (this.peek() == '(') { - this.pos++; - this.spnl(); - if ((dest = this.parseLinkDestination()) != null) { - this.spnl(); + if (peek() == '(') { + index++; + spnl(); + if ((dest = parseLinkDestination()) != null) { + spnl(); // title needs a whitespace before - if (WHITESPACE_CHAR.matcher(this.subject.substring(this.pos - 1, this.pos)).matches()) { - title = this.parseLinkTitle(); - this.spnl(); + if (WHITESPACE_CHAR.matcher(input.substring(index - 1, index)).matches()) { + title = parseLinkTitle(); + spnl(); } - if (this.subject.charAt(this.pos) == ')') { - this.pos += 1; + if (input.charAt(index) == ')') { + index++; isLinkOrImage = true; } } } else { // maybe reference link // See if there's a link label - this.spnl(); + spnl(); - int beforeLabel = this.pos; - int labelLength = this.parseLinkLabel(); + int beforeLabel = index; + int labelLength = parseLinkLabel(); String ref = null; if (labelLength > 2) { - ref = this.subject.substring(beforeLabel, beforeLabel + labelLength); + ref = input.substring(beforeLabel, beforeLabel + labelLength); } else if (!containsBracket) { // Empty or missing second label can only be a reference if there's no unescaped bracket in it. - ref = this.subject.substring(opener.index, startPos); + ref = input.substring(opener.index, startIndex); } if (labelLength == 0) { // If shortcut reference link, rewind before spaces we skipped. - this.pos = startPos; + index = startIndex; } if (ref != null) { @@ -651,7 +645,7 @@ private boolean parseCloseBracket() { // We could remove the opener now, but that would complicate text node merging. // E.g. `[link] (/uri)` isn't a link because of the space, so we want to keep appending text. opener.matched = true; - this.pos = startPos; + index = startIndex; return true; } } @@ -660,7 +654,7 @@ private boolean parseCloseBracket() { * Attempt to parse link destination, returning the string or null if no match. */ private String parseLinkDestination() { - String res = this.match(LINK_DESTINATION_BRACES); + String res = match(LINK_DESTINATION_BRACES); if (res != null) { // chop off surrounding <..>: if (res.length() == 2) { return ""; @@ -668,7 +662,7 @@ private String parseLinkDestination() { return Escaping.unescapeString(res.substring(1, res.length() - 1)); } } else { - res = this.match(LINK_DESTINATION); + res = match(LINK_DESTINATION); if (res != null) { return Escaping.unescapeString(res); } else { @@ -681,7 +675,7 @@ private String parseLinkDestination() { * Attempt to parse link title (sans quotes), returning the string or null if no match. */ private String parseLinkTitle() { - String title = this.match(LINK_TITLE); + String title = match(LINK_TITLE); if (title != null) { // chop off quotes from title and unescape: return Escaping.unescapeString(title.substring(1, title.length() - 1)); @@ -694,7 +688,7 @@ private String parseLinkTitle() { * Attempt to parse a link label, returning number of characters parsed. */ private int parseLinkLabel() { - String m = this.match(LINK_LABEL); + String m = match(LINK_LABEL); return m == null ? 0 : m.length(); } @@ -703,13 +697,13 @@ private int parseLinkLabel() { */ private boolean parseAutolink() { String m; - if ((m = this.match(EMAIL_AUTOLINK)) != null) { + if ((m = match(EMAIL_AUTOLINK)) != null) { String dest = m.substring(1, m.length() - 1); Link node = new Link("mailto:" + dest, null); node.appendChild(new Text(dest)); appendNode(node); return true; - } else if ((m = this.match(AUTOLINK)) != null) { + } else if ((m = match(AUTOLINK)) != null) { String dest = m.substring(1, m.length() - 1); Link node = new Link(dest, null); node.appendChild(new Text(dest)); @@ -724,7 +718,7 @@ private boolean parseAutolink() { * Attempt to parse a raw HTML tag. */ private boolean parseHtmlTag() { - String m = this.match(HTML_TAG); + String m = match(HTML_TAG); if (m != null) { HtmlTag node = new HtmlTag(); node.setLiteral(m); @@ -740,7 +734,7 @@ private boolean parseHtmlTag() { */ private boolean parseEntity() { String m; - if ((m = this.match(ENTITY_HERE)) != null) { + if ((m = match(ENTITY_HERE)) != null) { appendText(Html5Entities.entityToString(m)); return true; } else { @@ -752,16 +746,16 @@ private boolean parseEntity() { * Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string. */ private boolean parseString() { - int begin = pos; - int length = subject.length(); - while (pos != length) { - if (specialCharacters.get(subject.charAt(pos))) { + int begin = index; + int length = input.length(); + while (index != length) { + if (specialCharacters.get(input.charAt(index))) { break; } - pos++; + index++; } - if (begin != pos) { - appendText(subject, begin, pos); + if (begin != index) { + appendText(input, begin, index); return true; } else { return false; @@ -775,24 +769,24 @@ private boolean parseString() { * @return information about delimiter run, or {@code null} */ private DelimiterRun scanDelims(DelimiterProcessor inlineDelimiter) { - int startPos = this.pos; + int startIndex = index; int delimiterCount = 0; char delimiterChar = inlineDelimiter.getDelimiterChar(); - while (this.peek() == delimiterChar) { + while (peek() == delimiterChar) { delimiterCount++; - this.pos++; + index++; } if (delimiterCount < inlineDelimiter.getMinDelimiterCount()) { - this.pos = startPos; + index = startIndex; return null; } - String before = startPos == 0 ? "\n" : - this.subject.substring(startPos - 1, startPos); + String before = startIndex == 0 ? "\n" : + input.substring(startIndex - 1, startIndex); - char charAfter = this.peek(); + char charAfter = peek(); String after = charAfter == '\0' ? "\n" : String.valueOf(charAfter); @@ -815,7 +809,7 @@ private DelimiterRun scanDelims(DelimiterProcessor inlineDelimiter) { canClose = rightFlanking; } - this.pos = startPos; + index = startIndex; return new DelimiterRun(delimiterCount, canOpen, canClose); } @@ -963,5 +957,4 @@ private void removeDelimiter(Delimiter delim) { delim.next.previous = delim.previous; } } - } From 12f91cd19e8412036fe92e8f1bea422014fea214 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 Dec 2015 13:09:19 +1100 Subject: [PATCH 038/815] Use peek() --- .../src/main/java/org/commonmark/internal/InlineParserImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 5d079ebb2..699d7577a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -388,7 +388,7 @@ private boolean parseNewline() { } // gobble leading spaces in next line - while (index < input.length() && input.charAt(index) == ' ') { + while (peek() == ' ') { index++; } return true; From 48f05b358741f65ceb3a2b012539c5dfb8006dcd Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 Dec 2015 13:10:22 +1100 Subject: [PATCH 039/815] Fix StringIndexOutOfBoundsException with unclosed inline link (#27) An example input that caused it: [example.com](http:\\example.com The code was missing a bounds check. By using peek() we get that, as in other places. Also add an integration test that tries to trigger out of bounds by using substrings of the spec examples. --- .../integration/BoundsIntegrationTest.java | 61 +++++++++++++++++++ .../commonmark/internal/InlineParserImpl.java | 2 +- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java 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 new file mode 100644 index 000000000..e0c382be5 --- /dev/null +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/BoundsIntegrationTest.java @@ -0,0 +1,61 @@ +package org.commonmark.integration; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.spec.SpecExample; +import org.commonmark.spec.SpecReader; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertNotNull; + +/** + * Tests various substrings of the spec examples to check for out of bounds exceptions. + */ +@RunWith(Parameterized.class) +public class BoundsIntegrationTest { + + private static final Parser PARSER = Parser.builder().build(); + + protected final String input; + + public BoundsIntegrationTest(String input) { + this.input = input; + } + + @Parameterized.Parameters(name = "{0}") + public static List data() { + List examples = SpecReader.readExamples(); + List data = new ArrayList<>(); + for (SpecExample example : examples) { + data.add(new Object[]{example.getSource()}); + } + return data; + } + + @Test + public void testSubstrings() { + // Check possibly truncated block/inline starts + for (int i = 1; i < input.length() - 1; i++) { + parse(input.substring(i)); + } + // Check possibly truncated block/inline ends + for (int i = input.length() - 1; i > 1; i--) { + parse(input.substring(0, i)); + } + } + + private void parse(String input) { + try { + Node parsed = PARSER.parse(input); + // Parsing should always return a node + assertNotNull(parsed); + } catch (Exception e) { + throw new AssertionError("Parsing failed, input: " + input, e); + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 699d7577a..de1574d4d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -570,7 +570,7 @@ private boolean parseCloseBracket() { title = parseLinkTitle(); spnl(); } - if (input.charAt(index) == ')') { + if (peek() == ')') { index++; isLinkOrImage = true; } From a37c3d2a83285a5bfda865a57aa393a41e8b8272 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 Dec 2015 13:29:10 +1100 Subject: [PATCH 040/815] README: Don't use escapeHtml(true) in example This keeps the output closer to the spec. --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 68e277b05..958cad28d 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,17 @@ import org.commonmark.parser.Parser; Parser parser = Parser.builder().build(); Node document = parser.parse("This is *Sparta*"); -HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); +HtmlRenderer renderer = HtmlRenderer.builder().build(); renderer.render(document); // "

This is Sparta

\n" ``` -This uses the parser and renderer with default options, except for escaping raw -HTML tags and blocks. For all the available options, see other methods on the -builder objects. +This uses the parser and renderer with default options. Both builders have +methods for configuring their behavior, e.g. calling `escapeHtml(true)` on +`HtmlRenderer` will escape raw HTML tags and blocks. For all available +options, see methods on the builders. -Note that this library doesn't try to sanitize HTML; that is the responsibility -of the caller. +Note that this library doesn't try to sanitize the resulting HTML; that is +the responsibility of the caller. #### Use a visitor to process parsed nodes From d7cd926840d6edbc3c4b35ebb78938fb009a8144 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 Dec 2015 13:43:51 +1100 Subject: [PATCH 041/815] [maven-release-plugin] prepare release commonmark-parent-0.3.1 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 2945d51ad..99b7d6b62 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1-SNAPSHOT + 0.3.1 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 30ed431be..c1759f3e3 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1-SNAPSHOT + 0.3.1 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 7da0e9a60..fd75a3323 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1-SNAPSHOT + 0.3.1 commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index a0a7e6562..fd605f13f 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1-SNAPSHOT + 0.3.1 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 2daf60908..15bebbf01 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1-SNAPSHOT + 0.3.1 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 9379b06ef..c23d175d4 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1-SNAPSHOT + 0.3.1 commonmark diff --git a/pom.xml b/pom.xml index 0c30c7cd9..31ca0f46d 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1-SNAPSHOT + 0.3.1 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.3.1-SNAPSHOT + 0.3.1 com.atlassian.commonmark commonmark-ext-autolink - 0.3.1-SNAPSHOT + 0.3.1 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.3.1-SNAPSHOT + 0.3.1 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.3.1-SNAPSHOT + 0.3.1 com.atlassian.commonmark commonmark-test-util - 0.3.1-SNAPSHOT + 0.3.1 @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.3.1 From bd54c349f09b0c2c12117b58c1de0b023d4fa401 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 Dec 2015 13:43:51 +1100 Subject: [PATCH 042/815] [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-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 99b7d6b62..fdca2685a 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1 + 0.3.2-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index c1759f3e3..4a3ebc3ea 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1 + 0.3.2-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index fd75a3323..07f07bc8d 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1 + 0.3.2-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index fd605f13f..fb93db6d5 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1 + 0.3.2-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 15bebbf01..bc31b9e02 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1 + 0.3.2-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index c23d175d4..2a4859e26 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1 + 0.3.2-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 31ca0f46d..dc0c3878a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.1 + 0.3.2-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.3.1 + 0.3.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.3.1 + 0.3.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.3.1 + 0.3.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.3.1 + 0.3.2-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.3.1 + 0.3.2-SNAPSHOT @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.3.1 + HEAD From 838c98e10840daf9e2846edbe798b0f9b20a9a66 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 8 Dec 2015 12:53:43 +1100 Subject: [PATCH 043/815] Add more bounds checks to Substring Otherwise it's possible to access more of the underlying string than the substring covers. Discovered in #24. Not sure if Substring is really worth it at this point. --- .../commonmark/internal/util/Substring.java | 26 +++++- .../org/commonmark/test/SubstringTest.java | 91 +++++++++++++++++++ 2 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/SubstringTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Substring.java b/commonmark/src/main/java/org/commonmark/internal/util/Substring.java index 920ff679e..cf8907bdf 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Substring.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Substring.java @@ -14,12 +14,21 @@ public static CharSequence of(String base, int beginIndex, int endIndex) { } private Substring(String base, int beginIndex, int endIndex) { + if (beginIndex < 0) { + throw new StringIndexOutOfBoundsException("beginIndex must be at least 0"); + } + if (endIndex < 0) { + throw new StringIndexOutOfBoundsException("endIndex must be at least 0"); + } + if (endIndex < beginIndex) { + throw new StringIndexOutOfBoundsException("endIndex must not be less than beginIndex"); + } + if (endIndex > base.length()) { + throw new StringIndexOutOfBoundsException("endIndex must not be greater than length"); + } this.base = base; this.beginIndex = beginIndex; this.endIndex = endIndex; - if (endIndex > base.length()) { - throw new IndexOutOfBoundsException("endIndex must not be greater than length"); - } } @Override @@ -29,11 +38,20 @@ public int length() { @Override public char charAt(int index) { + if (index < 0 || beginIndex + index >= endIndex) { + throw new StringIndexOutOfBoundsException("String index out of range: " + index); + } return base.charAt(index + beginIndex); } @Override public CharSequence subSequence(int start, int end) { + if (start < 0 || beginIndex + start > endIndex) { + throw new StringIndexOutOfBoundsException("String index out of range: " + start); + } + if (end < 0 || beginIndex + end > endIndex) { + throw new StringIndexOutOfBoundsException("String index out of range: " + end); + } return new Substring(base, beginIndex + start, beginIndex + end); } @@ -49,6 +67,6 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return obj == this || (obj instanceof CharSequence && toString().equals(obj)); + return obj == this || (obj instanceof CharSequence && toString().equals(obj.toString())); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SubstringTest.java b/commonmark/src/test/java/org/commonmark/test/SubstringTest.java new file mode 100644 index 000000000..cae24c376 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/SubstringTest.java @@ -0,0 +1,91 @@ +package org.commonmark.test; + +import org.commonmark.internal.util.Substring; +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class SubstringTest { + + private final CharSequence substring = Substring.of("abcdefghi", 3, 6); + + @Test + public void testConstructEmpty() { + assertEquals("", Substring.of("ab", 0, 0).toString()); + assertEquals("", Substring.of("ab", 1, 1).toString()); + assertEquals("", Substring.of("ab", 2, 2).toString()); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testConstructBeginIndexNegative() { + Substring.of("abc", -1, 0); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testConstructEndIndexNegative() { + Substring.of("abc", 0, -1); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testConstructEndIndexLessThanBegin() { + Substring.of("abc", 1, 0); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testConstructEndIndexGreaterThanLength() { + Substring.of("abc", 1, 4); + } + + @Test + public void testLength() { + assertEquals(3, substring.length()); + } + + @Test + public void testCharAt() { + assertEquals('d', substring.charAt(0)); + assertEquals('e', substring.charAt(1)); + assertEquals('f', substring.charAt(2)); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testCharAtOutOfBoundsLeft() { + substring.charAt(-1); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testCharAtOutOfBoundsRight() { + substring.charAt(3); + } + + @Test + public void testSubSequence() { + assertEquals("d", substring.subSequence(0, 1).toString()); + assertEquals("e", substring.subSequence(1, 2).toString()); + assertEquals("f", substring.subSequence(2, 3).toString()); + assertEquals("def", substring.subSequence(0, 3).toString()); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testSubSequenceOutOfBoundsLeft() { + substring.subSequence(-1, 2); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testSubSequenceOutOfBoundsRight() { + substring.subSequence(1, 4); + } + + @Test + public void testHashCodeEquals() { + CharSequence a = Substring.of("abcdefghi", 3, 6); + CharSequence b = Substring.of("123def456", 3, 6); + CharSequence other = Substring.of("123de", 3, 5); + assertEquals(a, b); + assertEquals(b, a); + assertNotEquals(a, other); + assertNotEquals(other, a); + assertEquals(a.hashCode(), b.hashCode()); + } +} From eb82da7d21dd06cbec7b1df8ba0edf1078771d0b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 7 Jan 2016 12:28:03 +1100 Subject: [PATCH 044/815] [maven-release-plugin] prepare release commonmark-parent-0.3.2 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index fdca2685a..361145ec9 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2-SNAPSHOT + 0.3.2 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 4a3ebc3ea..049ec9c9a 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2-SNAPSHOT + 0.3.2 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 07f07bc8d..1c255dc68 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2-SNAPSHOT + 0.3.2 commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index fb93db6d5..e4c77ffd8 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2-SNAPSHOT + 0.3.2 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index bc31b9e02..871ebf639 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2-SNAPSHOT + 0.3.2 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 2a4859e26..c6a81a0ae 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2-SNAPSHOT + 0.3.2 commonmark diff --git a/pom.xml b/pom.xml index dc0c3878a..7e8be058c 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2-SNAPSHOT + 0.3.2 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.3.2-SNAPSHOT + 0.3.2 com.atlassian.commonmark commonmark-ext-autolink - 0.3.2-SNAPSHOT + 0.3.2 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.3.2-SNAPSHOT + 0.3.2 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.3.2-SNAPSHOT + 0.3.2 com.atlassian.commonmark commonmark-test-util - 0.3.2-SNAPSHOT + 0.3.2 @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.3.2 From 5f49950612a30823c3c2a2a97eddbd4ea55a465e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 7 Jan 2016 12:28:03 +1100 Subject: [PATCH 045/815] [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-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 361145ec9..858393434 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2 + 0.3.3-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 049ec9c9a..ba097614f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2 + 0.3.3-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 1c255dc68..3e6fb18b9 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2 + 0.3.3-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index e4c77ffd8..9c51db5e5 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2 + 0.3.3-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 871ebf639..ae1f74c9b 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2 + 0.3.3-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index c6a81a0ae..6f1658257 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2 + 0.3.3-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 7e8be058c..e9fc02299 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.2 + 0.3.3-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.3.2 + 0.3.3-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.3.2 + 0.3.3-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.3.2 + 0.3.3-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.3.2 + 0.3.3-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.3.2 + 0.3.3-SNAPSHOT @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.3.2 + HEAD From de9089c1ac786e8ad39a660d1640c8da22bdfc0a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 16 Jan 2016 17:39:40 +1100 Subject: [PATCH 046/815] Don't allow whitespace before link label (spec 0.23, #28) --- .../main/java/org/commonmark/internal/InlineParserImpl.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index de1574d4d..0cc9dbdac 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -578,8 +578,6 @@ private boolean parseCloseBracket() { } else { // maybe reference link // See if there's a link label - spnl(); - int beforeLabel = index; int labelLength = parseLinkLabel(); String ref = null; @@ -589,10 +587,6 @@ private boolean parseCloseBracket() { // Empty or missing second label can only be a reference if there's no unescaped bracket in it. ref = input.substring(opener.index, startIndex); } - if (labelLength == 0) { - // If shortcut reference link, rewind before spaces we skipped. - index = startIndex; - } if (ref != null) { Link link = referenceMap.get(Escaping.normalizeReference(ref)); From 0a75bf32a3291e16d03c693a070934d48ec3e71b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 16 Jan 2016 17:45:46 +1100 Subject: [PATCH 047/815] Rename HtmlTag to HtmlInline to follow reference impl (api break) It's also more accurate, as it also includes comments and CDATA. --- .../main/java/org/commonmark/html/HtmlRenderer.java | 12 ++++++------ .../org/commonmark/internal/InlineParserImpl.java | 8 ++++---- .../java/org/commonmark/node/AbstractVisitor.java | 4 ++-- .../node/{HtmlTag.java => HtmlInline.java} | 4 ++-- .../src/main/java/org/commonmark/node/Visitor.java | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) rename commonmark/src/main/java/org/commonmark/node/{HtmlTag.java => HtmlInline.java} (77%) diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index a18928640..e210fa917 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -105,10 +105,10 @@ public Builder softbreak(String softbreak) { } /** - * Whether {@link HtmlTag} and {@link HtmlBlock} should be escaped, defaults to {@code false}. + * Whether {@link HtmlInline} and {@link HtmlBlock} should be escaped, defaults to {@code false}. *

- * Note that {@link HtmlTag} is only a tag itself, not the text between an opening tag and a closing tag. So markup - * in the text will be parsed as normal and is not affected by this option. + * Note that {@link HtmlInline} is only a tag itself, not the text between an opening tag and a closing tag. So + * markup in the text will be parsed as normal and is not affected by this option. * * @param escapeHtml true for escaping, false for preserving raw HTML * @return {@code this} @@ -347,11 +347,11 @@ public void visit(Code code) { } @Override - public void visit(HtmlTag htmlTag) { + public void visit(HtmlInline htmlInline) { if (escapeHtml) { - html.raw(escape(htmlTag.getLiteral(), false)); + html.raw(escape(htmlInline.getLiteral(), false)); } else { - html.raw(htmlTag.getLiteral()); + html.raw(htmlInline.getLiteral()); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 0cc9dbdac..33a110c4f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -299,7 +299,7 @@ private boolean parseInline() { res = parseCloseBracket(); break; case '<': - res = parseAutolink() || parseHtmlTag(); + res = parseAutolink() || parseHtmlInline(); break; case '&': res = parseEntity(); @@ -709,12 +709,12 @@ private boolean parseAutolink() { } /** - * Attempt to parse a raw HTML tag. + * Attempt to parse inline HTML. */ - private boolean parseHtmlTag() { + private boolean parseHtmlInline() { String m = match(HTML_TAG); if (m != null) { - HtmlTag node = new HtmlTag(); + HtmlInline node = new HtmlInline(); node.setLiteral(m); appendNode(node); return true; diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index c7ecbe150..ab43793c4 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -54,8 +54,8 @@ public void visit(HorizontalRule horizontalRule) { } @Override - public void visit(HtmlTag htmlTag) { - visitChildren(htmlTag); + public void visit(HtmlInline htmlInline) { + visitChildren(htmlInline); } @Override diff --git a/commonmark/src/main/java/org/commonmark/node/HtmlTag.java b/commonmark/src/main/java/org/commonmark/node/HtmlInline.java similarity index 77% rename from commonmark/src/main/java/org/commonmark/node/HtmlTag.java rename to commonmark/src/main/java/org/commonmark/node/HtmlInline.java index 4271e743c..291fcde3c 100644 --- a/commonmark/src/main/java/org/commonmark/node/HtmlTag.java +++ b/commonmark/src/main/java/org/commonmark/node/HtmlInline.java @@ -3,9 +3,9 @@ /** * Inline HTML element. * - * @see CommonMark Spec + * @see CommonMark Spec */ -public class HtmlTag extends Node { +public class HtmlInline extends Node { private String literal; diff --git a/commonmark/src/main/java/org/commonmark/node/Visitor.java b/commonmark/src/main/java/org/commonmark/node/Visitor.java index 3f0b4fb6c..5dc4d0dcc 100644 --- a/commonmark/src/main/java/org/commonmark/node/Visitor.java +++ b/commonmark/src/main/java/org/commonmark/node/Visitor.java @@ -25,7 +25,7 @@ public interface Visitor { void visit(HorizontalRule horizontalRule); - void visit(HtmlTag htmlTag); + void visit(HtmlInline htmlInline); void visit(HtmlBlock htmlBlock); From ed7c628ac9af876014e3fbcce700451b32b9bb41 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 16 Jan 2016 18:02:48 +1100 Subject: [PATCH 048/815] Rename Header to Heading to follow reference impl (api break) --- .../org/commonmark/html/HtmlRenderer.java | 8 +++--- .../commonmark/internal/DocumentParser.java | 2 +- .../{HeaderParser.java => HeadingParser.java} | 26 +++++++++---------- .../org/commonmark/node/AbstractVisitor.java | 4 +-- .../node/{Header.java => Heading.java} | 2 +- .../java/org/commonmark/node/Visitor.java | 2 +- 6 files changed, 22 insertions(+), 22 deletions(-) rename commonmark/src/main/java/org/commonmark/internal/{HeaderParser.java => HeadingParser.java} (68%) rename commonmark/src/main/java/org/commonmark/node/{Header.java => Heading.java} (88%) diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index e210fa917..b208d5d14 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -191,11 +191,11 @@ public void visit(Document document) { } @Override - public void visit(Header header) { - String htag = "h" + header.getLevel(); + public void visit(Heading heading) { + String htag = "h" + heading.getLevel(); html.line(); - html.tag(htag, getAttrs(header)); - visitChildren(header); + html.tag(htag, getAttrs(heading)); + visitChildren(heading); html.tag('/' + htag); html.line(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 60aacf9dc..43272a365 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -14,7 +14,7 @@ public class DocumentParser implements ParserState { private static List CORE_FACTORIES = Arrays.asList( new BlockQuoteParser.Factory(), - new HeaderParser.Factory(), + new HeadingParser.Factory(), new FencedCodeBlockParser.Factory(), new HtmlBlockParser.Factory(), new HorizontalRuleParser.Factory(), diff --git a/commonmark/src/main/java/org/commonmark/internal/HeaderParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java similarity index 68% rename from commonmark/src/main/java/org/commonmark/internal/HeaderParser.java rename to commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 81ef32bb7..dcbd2bd3c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeaderParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -1,23 +1,23 @@ package org.commonmark.internal; import org.commonmark.node.Block; -import org.commonmark.node.Header; +import org.commonmark.node.Heading; import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class HeaderParser extends AbstractBlockParser { +public class HeadingParser extends AbstractBlockParser { - private static Pattern ATX_HEADER = Pattern.compile("^#{1,6}(?: +|$)"); + private static Pattern ATX_HEADING = Pattern.compile("^#{1,6}(?: +|$)"); private static Pattern ATX_TRAILING = Pattern.compile("(^| ) *#+ *$"); - private static Pattern SETEXT_HEADER = Pattern.compile("^(?:=+|-+) *$"); + private static Pattern SETEXT_HEADING = Pattern.compile("^(?:=+|-+) *$"); - private final Header block = new Header(); + private final Heading block = new Heading(); private final String content; - public HeaderParser(int level, String content) { + public HeadingParser(int level, String content) { block.setLevel(level); this.content = content; } @@ -29,7 +29,7 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState parserState) { - // a header can never container > 1 line, so fail to match + // In both ATX and Setext headings, once we have the heading markup, there's nothing more to parse. return BlockContinue.none(); } @@ -49,22 +49,22 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar int nextNonSpace = state.getNextNonSpaceIndex(); CharSequence paragraphStartLine = matchedBlockParser.getParagraphStartLine(); Matcher matcher; - if ((matcher = ATX_HEADER.matcher(line.subSequence(nextNonSpace, line.length()))).find()) { - // ATX header + if ((matcher = ATX_HEADING.matcher(line.subSequence(nextNonSpace, line.length()))).find()) { + // ATX heading int newOffset = nextNonSpace + matcher.group(0).length(); int level = matcher.group(0).trim().length(); // number of #s // remove trailing ###s: String content = ATX_TRAILING.matcher(line.subSequence(newOffset, line.length())).replaceAll(""); - return BlockStart.of(new HeaderParser(level, content)) + return BlockStart.of(new HeadingParser(level, content)) .atIndex(line.length()); } else if (paragraphStartLine != null && - ((matcher = SETEXT_HEADER.matcher(line.subSequence(nextNonSpace, line.length()))).find())) { - // setext header line + ((matcher = SETEXT_HEADING.matcher(line.subSequence(nextNonSpace, line.length()))).find())) { + // setext heading line int level = matcher.group(0).charAt(0) == '=' ? 1 : 2; String content = paragraphStartLine.toString(); - return BlockStart.of(new HeaderParser(level, content)) + return BlockStart.of(new HeadingParser(level, content)) .atIndex(line.length()) .replaceActiveBlockParser(); } else { diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index ab43793c4..ebb95ea6d 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -44,8 +44,8 @@ public void visit(HardLineBreak hardLineBreak) { } @Override - public void visit(Header header) { - visitChildren(header); + public void visit(Heading heading) { + visitChildren(heading); } @Override diff --git a/commonmark/src/main/java/org/commonmark/node/Header.java b/commonmark/src/main/java/org/commonmark/node/Heading.java similarity index 88% rename from commonmark/src/main/java/org/commonmark/node/Header.java rename to commonmark/src/main/java/org/commonmark/node/Heading.java index 0402e15cb..41f3b2504 100644 --- a/commonmark/src/main/java/org/commonmark/node/Header.java +++ b/commonmark/src/main/java/org/commonmark/node/Heading.java @@ -1,6 +1,6 @@ package org.commonmark.node; -public class Header extends Block { +public class Heading extends Block { private int level; diff --git a/commonmark/src/main/java/org/commonmark/node/Visitor.java b/commonmark/src/main/java/org/commonmark/node/Visitor.java index 5dc4d0dcc..5efeccd6c 100644 --- a/commonmark/src/main/java/org/commonmark/node/Visitor.java +++ b/commonmark/src/main/java/org/commonmark/node/Visitor.java @@ -21,7 +21,7 @@ public interface Visitor { void visit(HardLineBreak hardLineBreak); - void visit(Header header); + void visit(Heading heading); void visit(HorizontalRule horizontalRule); From e0db43ff4bcfcd2e2993a8db7a7eb8854539f099 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 16 Jan 2016 18:06:01 +1100 Subject: [PATCH 049/815] Rename HorizontalRule to ThematicBreak to follow reference impl (api break) --- .../main/java/org/commonmark/html/HtmlRenderer.java | 4 ++-- .../java/org/commonmark/internal/DocumentParser.java | 2 +- ...ontalRuleParser.java => ThematicBreakParser.java} | 12 ++++++------ .../java/org/commonmark/node/AbstractVisitor.java | 4 ++-- .../node/{HorizontalRule.java => ThematicBreak.java} | 2 +- .../src/main/java/org/commonmark/node/Visitor.java | 2 +- .../test/java/org/commonmark/test/ParserTest.java | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) rename commonmark/src/main/java/org/commonmark/internal/{HorizontalRuleParser.java => ThematicBreakParser.java} (66%) rename commonmark/src/main/java/org/commonmark/node/{HorizontalRule.java => ThematicBreak.java} (73%) diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index b208d5d14..25e18a2ce 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -260,9 +260,9 @@ public void visit(HtmlBlock htmlBlock) { } @Override - public void visit(HorizontalRule horizontalRule) { + public void visit(ThematicBreak thematicBreak) { html.line(); - html.tag("hr", getAttrs(horizontalRule), true); + html.tag("hr", getAttrs(thematicBreak), true); html.line(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 43272a365..556a75965 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -17,7 +17,7 @@ public class DocumentParser implements ParserState { new HeadingParser.Factory(), new FencedCodeBlockParser.Factory(), new HtmlBlockParser.Factory(), - new HorizontalRuleParser.Factory(), + new ThematicBreakParser.Factory(), new ListBlockParser.Factory(), new IndentedCodeBlockParser.Factory()); diff --git a/commonmark/src/main/java/org/commonmark/internal/HorizontalRuleParser.java b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java similarity index 66% rename from commonmark/src/main/java/org/commonmark/internal/HorizontalRuleParser.java rename to commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java index 0bc8422e2..f02e2e90e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HorizontalRuleParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java @@ -1,16 +1,16 @@ package org.commonmark.internal; import org.commonmark.node.Block; -import org.commonmark.node.HorizontalRule; +import org.commonmark.node.ThematicBreak; import org.commonmark.parser.block.*; import java.util.regex.Pattern; -public class HorizontalRuleParser extends AbstractBlockParser { +public class ThematicBreakParser extends AbstractBlockParser { - private static Pattern H_RULE = Pattern.compile("^(?:(?:\\* *){3,}|(?:_ *){3,}|(?:- *){3,}) *$"); + private static Pattern PATTERN = Pattern.compile("^(?:(?:\\* *){3,}|(?:_ *){3,}|(?:- *){3,}) *$"); - private final HorizontalRule block = new HorizontalRule(); + private final ThematicBreak block = new ThematicBreak(); @Override public Block getBlock() { @@ -32,8 +32,8 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } int nextNonSpace = state.getNextNonSpaceIndex(); CharSequence line = state.getLine(); - if (H_RULE.matcher(line.subSequence(nextNonSpace, line.length())).matches()) { - return BlockStart.of(new HorizontalRuleParser()).atIndex(line.length()); + if (PATTERN.matcher(line.subSequence(nextNonSpace, line.length())).matches()) { + return BlockStart.of(new ThematicBreakParser()).atIndex(line.length()); } else { return BlockStart.none(); } diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index ebb95ea6d..381c72b66 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -49,8 +49,8 @@ public void visit(Heading heading) { } @Override - public void visit(HorizontalRule horizontalRule) { - visitChildren(horizontalRule); + public void visit(ThematicBreak thematicBreak) { + visitChildren(thematicBreak); } @Override diff --git a/commonmark/src/main/java/org/commonmark/node/HorizontalRule.java b/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java similarity index 73% rename from commonmark/src/main/java/org/commonmark/node/HorizontalRule.java rename to commonmark/src/main/java/org/commonmark/node/ThematicBreak.java index 6d2f2ec21..f81abaa31 100644 --- a/commonmark/src/main/java/org/commonmark/node/HorizontalRule.java +++ b/commonmark/src/main/java/org/commonmark/node/ThematicBreak.java @@ -1,6 +1,6 @@ package org.commonmark.node; -public class HorizontalRule extends Block { +public class ThematicBreak extends Block { @Override public void accept(Visitor visitor) { diff --git a/commonmark/src/main/java/org/commonmark/node/Visitor.java b/commonmark/src/main/java/org/commonmark/node/Visitor.java index 5efeccd6c..8851b7b18 100644 --- a/commonmark/src/main/java/org/commonmark/node/Visitor.java +++ b/commonmark/src/main/java/org/commonmark/node/Visitor.java @@ -23,7 +23,7 @@ public interface Visitor { void visit(Heading heading); - void visit(HorizontalRule horizontalRule); + void visit(ThematicBreak thematicBreak); void visit(HtmlInline htmlInline); diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 951dfda8b..d6a46d58d 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -40,7 +40,7 @@ public void ioReaderTest() throws IOException { public void customBlockParserFactory() { Parser parser = Parser.builder().customBlockParserFactory(new DashBlockParserFactory()).build(); - // The dashes would normally be a HorizontalRule + // The dashes would normally be a ThematicBreak Node document = parser.parse("hey\n\n---\n"); assertThat(document.getFirstChild(), instanceOf(Paragraph.class)); From 4ad942e2bbf06cebd20211a7608467efe7c1aeb1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 16 Jan 2016 18:06:43 +1100 Subject: [PATCH 050/815] Update to spec 0.23 (#28) --- .../src/main/resources/spec.txt | 418 +++++++++++------- 1 file changed, 259 insertions(+), 159 deletions(-) diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index ff44e4afa..c7055a94c 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.22 -date: 2015-08-23 +version: 0.23 +date: 2015-12-29 license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -36,11 +36,11 @@ questions it does not answer: users in real documents. (See [this comment by John Gruber](http://article.gmane.org/gmane.text.markdown.general/1997).) -2. Is a blank line needed before a block quote or header? +2. Is a blank line needed before a block quote or heading? Most implementations do not require the blank line. However, this can lead to unexpected results in hard-wrapped text, and also to ambiguities in parsing (note that some implementations - put the header inside the blockquote, while others do not). + 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).) @@ -85,8 +85,8 @@ questions it does not answer: 10. item 2a ``` -6. Is this one list with a horizontal rule in its second item, - or two lists separated by a horizontal rule? +6. Is this one list with a thematic break in its second item, + or two lists separated by a thematic break? ``` markdown * a @@ -128,8 +128,8 @@ questions it does not answer: - and it can screw things up` ``` -11. Can list items include section headers? (`Markdown.pl` does not - allow this, but does allow blockquotes to include headers.) +11. Can list items include section headings? (`Markdown.pl` does not + allow this, but does allow blockquotes to include headings.) ``` markdown - # Heading @@ -325,9 +325,9 @@ with the replacement character (`U+FFFD`). We can think of a document as a sequence of [blocks](@block)---structural elements like paragraphs, block -quotations, lists, headers, rules, and code blocks. Some blocks (like +quotations, lists, headings, rules, and code blocks. Some blocks (like block quotes and list items) contain other blocks; others (like -headers and paragraphs) contain [inline](@inline) content---text, +headings and paragraphs) contain [inline](@inline) content---text, links, emphasized text, images, code, and so on. ## Precedence @@ -348,7 +348,7 @@ two items, not a list with one item containing a code span: This means that parsing can proceed in two steps: first, the block structure of the document can be discerned; second, text lines inside -paragraphs, headers, and other block constructs can be parsed for inline +paragraphs, headings, and other block constructs can be parsed for inline structure. The second step requires information about link reference definitions that will be available only at the end of the first step. Note that the first step requires processing lines in sequence, @@ -367,12 +367,12 @@ which cannot. This section describes the different kinds of leaf block that make up a Markdown document. -## Horizontal rules +## Thematic breaks A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching `-`, `_`, or `*` characters, each followed optionally by any number of spaces, forms a -[horizontal rule](@horizontal-rule). +[thematic break](@thematic-break). . *** @@ -490,7 +490,7 @@ a------ . It is required that all of the [non-whitespace character]s be the same. -So, this is not a horizontal rule: +So, this is not a thematic break: . *-* @@ -498,7 +498,7 @@ So, this is not a horizontal rule:

-

. -Horizontal rules do not need blank lines before or after: +Thematic breaks do not need blank lines before or after: . - foo @@ -514,7 +514,7 @@ Horizontal rules do not need blank lines before or after: . -Horizontal rules can interrupt a paragraph: +Thematic breaks can interrupt a paragraph: . Foo @@ -527,10 +527,10 @@ bar . If a line of dashes that meets the above conditions for being a -horizontal rule could also be interpreted as the underline of a [setext -header], the interpretation as a -[setext header] takes precedence. Thus, for example, -this is a setext header, not a paragraph followed by a horizontal rule: +thematic break could also be interpreted as the underline of a [setext +heading], the interpretation as a +[setext heading] takes precedence. Thus, for example, +this is a setext heading, not a paragraph followed by a thematic break: . Foo @@ -541,8 +541,8 @@ bar

bar

. -When both a horizontal rule and a list item are possible -interpretations of a line, the horizontal rule takes precedence: +When both a thematic break and a list item are possible +interpretations of a line, the thematic break takes precedence: . * Foo @@ -558,7 +558,7 @@ interpretations of a line, the horizontal rule takes precedence: . -If you want a horizontal rule in a list item, use a different bullet: +If you want a thematic break in a list item, use a different bullet: . - Foo @@ -572,21 +572,21 @@ If you want a horizontal rule in a list item, use a different bullet: . -## ATX headers +## ATX headings -An [ATX header](@atx-header) +An [ATX heading](@atx-heading) consists of a string of characters, parsed as inline content, between an opening sequence of 1--6 unescaped `#` characters and an optional closing sequence of any number of unescaped `#` characters. -The opening sequence of `#` characters cannot be followed directly by a -[non-whitespace character]. The optional closing sequence of `#`s must be +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. The opening `#` character may be indented 0-3 spaces. The raw contents of the -header are stripped of leading and trailing spaces before being parsed -as inline content. The header level is equal to the number of `#` +heading are stripped of leading and trailing spaces before being parsed +as inline content. The heading level is equal to the number of `#` characters in the opening sequence. -Simple headers: +Simple headings: . # foo @@ -604,7 +604,7 @@ Simple headers:
foo
. -More than six `#` characters is not a header: +More than six `#` characters is not a heading: . ####### foo @@ -613,23 +613,31 @@ More than six `#` characters is not a header: . At least one space is required between the `#` characters and the -header's contents, unless the header is empty. Note that many +heading's contents, unless the heading is empty. Note that many implementations currently do not require the space. However, the space was required by the [original ATX implementation](http://www.aaronsw.com/2002/atx/atx.py), and it helps prevent things like the following from being parsed as -headers: +headings: . #5 bolt -#foobar +#hashtag .

#5 bolt

-

#foobar

+

#hashtag

. -This is not a header, because the first `#` is escaped: +A tab will not work: + +. +#→foo +. +

#→foo

+. + +This is not a heading, because the first `#` is escaped: . \## foo @@ -712,7 +720,7 @@ Spaces are allowed after the closing sequence: A sequence of `#` characters with anything but [space]s following it is not a closing sequence, but counts as part of the contents of the -header: +heading: . ### foo ### b @@ -741,7 +749,7 @@ of the closing sequence:

foo #

. -ATX headers need not be separated from surrounding content by blank +ATX headings need not be separated from surrounding content by blank lines, and they can interrupt paragraphs: . @@ -764,7 +772,7 @@ Bar foo

Bar foo

. -ATX headers can be empty: +ATX headings can be empty: . ## @@ -776,33 +784,33 @@ ATX headers can be empty:

. -## Setext headers +## Setext headings -A [setext header](@setext-header) +A [setext heading](@setext-heading) consists of a line of text, containing at least one [non-whitespace character], -with no more than 3 spaces indentation, followed by a [setext header +with no more than 3 spaces indentation, followed by a [setext heading underline]. The line of text must be -one that, were it not followed by the setext header underline, +one that, were it not followed by the setext heading underline, would be interpreted as part of a paragraph: it cannot be -interpretable as a [code fence], [ATX header][ATX headers], -[block quote][block quotes], [horizontal rule][horizontal rules], +interpretable as a [code fence], [ATX heading][ATX headings], +[block quote][block quotes], [thematic break][thematic breaks], [list item][list items], or [HTML block][HTML blocks]. -A [setext header underline](@setext-header-underline) is a sequence of +A [setext heading underline](@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 header underline]. +and not as a [setext heading underline]. -The header is a level 1 header if `=` characters are used in the -[setext header underline], and a level 2 -header if `-` characters are used. The contents of the header are the +The heading is a level 1 heading if `=` characters are used in the +[setext heading underline], and a level 2 +heading if `-` characters are used. The contents of the heading are the result of parsing the first line as Markdown inline content. -In general, a setext header need not be preceded or followed by a +In general, a setext heading need not be preceded or followed by a blank line. However, it cannot interrupt a paragraph, so when a -setext header comes after a paragraph, a blank line is needed between +setext heading comes after a paragraph, a blank line is needed between them. Simple examples: @@ -831,7 +839,7 @@ Foo

Foo

. -The header content can be indented up to three spaces, and need +The heading content can be indented up to three spaces, and need not line up with the underlining: . @@ -866,7 +874,7 @@ Foo
. -The setext header underline can be indented up to three spaces, and +The setext heading underline can be indented up to three spaces, and may have trailing spaces: . @@ -886,7 +894,7 @@ Foo ---

. -The setext header underline cannot contain internal spaces: +The setext heading underline cannot contain internal spaces: . Foo @@ -920,7 +928,7 @@ Foo\ . Since indicators of block structure take precedence over -indicators of inline structure, the following are setext headers: +indicators of inline structure, the following are setext headings: . `Foo @@ -937,7 +945,7 @@ of dashes"/>

of dashes"/>

. -The setext header underline cannot be a [lazy continuation +The setext heading underline cannot be a [lazy continuation line] in a list item or block quote: . @@ -960,7 +968,7 @@ line] in a list item or block quote:
. -A setext header cannot interrupt a paragraph: +A setext heading cannot interrupt a paragraph: . Foo @@ -995,7 +1003,7 @@ Baz

Baz

. -Setext headers cannot be empty: +Setext headings cannot be empty: . @@ -1004,9 +1012,9 @@ Setext headers cannot be empty:

====

. -Setext header text lines must not be interpretable as block +Setext heading text lines must not be interpretable as block constructs other than paragraphs. So, the line of dashes -in these examples gets interpreted as a horizontal rule: +in these examples gets interpreted as a thematic break: . --- @@ -1045,7 +1053,7 @@ in these examples gets interpreted as a horizontal rule:
. -If you want a header with `> foo` as its literal text, you can +If you want a heading with `> foo` as its literal text, you can use backslash escapes: . @@ -1192,17 +1200,17 @@ And indented code can occur immediately before and after other kinds of blocks: . -# Header +# Heading foo -Header +Heading ------ foo ---- . -

Header

+

Heading

foo
 
-

Header

+

Heading

foo
 

@@ -1363,7 +1371,7 @@ aaa . Unclosed code blocks are closed by the end of the document -(or the enclosing [block quote] or [list item]): +(or the enclosing [block quote][block quotes] or [list item][list items]): . ``` @@ -1987,8 +1995,8 @@ p {color:blue;} . If there is no matching end tag, the block will end at the -end of the document (or the enclosing [block quote] or -[list item]): +end of the document (or the enclosing [block quote][block quotes] +or [list item][list items]): . -. +```````````````````````````````` + If there is no matching end tag, the block will end at the end of the document (or the enclosing [block quote][block quotes] or [list item][list items]): -. +```````````````````````````````` example *foo* .

foo

-. +```````````````````````````````` -. + +```````````````````````````````` example *bar* *baz* . *bar*

baz

-. +```````````````````````````````` + Note that anything on the last line after the end tag will be included in the [HTML block]: -. +```````````````````````````````` example 1. *bar* @@ -2064,11 +2302,12 @@ foo 1. *bar* -. +```````````````````````````````` + A comment (type 2): -. +```````````````````````````````` example -. +```````````````````````````````` + A processing instruction (type 3): -. +```````````````````````````````` example '; @@ -2095,19 +2335,21 @@ A processing instruction (type 3): echo '>'; ?> -. +```````````````````````````````` + A declaration (type 4): -. +```````````````````````````````` example . -. +```````````````````````````````` + CDATA (type 5): -. +```````````````````````````````` example -. +```````````````````````````````` + The opening tag can be indented 1-3 spaces, but not 4: -. +```````````````````````````````` example @@ -2145,9 +2388,10 @@ The opening tag can be indented 1-3 spaces, but not 4:
<!-- foo -->
 
-. +```````````````````````````````` -. + +```````````````````````````````` example
@@ -2155,12 +2399,13 @@ The opening tag can be indented 1-3 spaces, but not 4:
<div>
 
-. +```````````````````````````````` + An HTML block of types 1--6 can interrupt a paragraph, and need not be preceded by a blank line. -. +```````````````````````````````` example Foo
bar @@ -2170,12 +2415,13 @@ bar
bar
-. +```````````````````````````````` + However, a following blank line is needed, except at the end of a document, and except for blocks of types 1--5, above: -. +```````````````````````````````` example
bar
@@ -2185,11 +2431,12 @@ bar bar
*foo* -. +```````````````````````````````` + HTML blocks of type 7 cannot interrupt a paragraph: -. +```````````````````````````````` example Foo baz @@ -2197,7 +2444,8 @@ baz

Foo baz

-. +```````````````````````````````` + This rule differs from John Gruber's original Markdown syntax specification, which says: @@ -2229,7 +2477,7 @@ simply separate the Markdown from the HTML using blank lines: Compare: -. +```````````````````````````````` example
*Emphasized* text. @@ -2239,9 +2487,10 @@ Compare:

Emphasized text.

-. +```````````````````````````````` -. + +```````````````````````````````` example
*Emphasized* text.
@@ -2249,7 +2498,8 @@ Compare:
*Emphasized* text.
-. +```````````````````````````````` + Some Markdown implementations have adopted a convention of interpreting content inside tags as text if the open tag has @@ -2262,7 +2512,7 @@ blocks into Markdown documents with 100% reliability. However, *in most cases* this will work fine, because the blank lines in HTML are usually followed by HTML block tags. For example: -. +```````````````````````````````` example @@ -2282,13 +2532,14 @@ Hi
-. +```````````````````````````````` + There are problems, however, if the inner tags are indented *and* separated by spaces, as then they will be interpreted as an indented code block: -. +```````````````````````````````` example @@ -2309,7 +2560,8 @@ an indented code block:
-. +```````````````````````````````` + Fortunately, blank lines are usually not necessary and can be deleted. The exception is inside `
` tags, but as described
@@ -2318,7 +2570,7 @@ lines.
 
 ## Link reference definitions
 
-A [link reference definition](@link-reference-definition)
+A [link reference definition](@)
 consists of a [link label], indented up to three spaces, followed
 by a colon (`:`), optional [whitespace] (including up to one
 [line ending]), a [link destination],
@@ -2326,24 +2578,25 @@ optional [whitespace] (including up to one
 [line ending]), and an optional [link
 title], which if it is present must be separated
 from the [link destination] by [whitespace].
-No further [non-whitespace character]s may occur on the line.
+No further [non-whitespace characters] may occur on the line.
 
 A [link reference definition]
 does not correspond to a structural element of a document.  Instead, it
-defines a label which can be used in [reference link]s
+defines a label which can be used in [reference links]
 and reference-style [images] elsewhere in the document.  [Link
 reference definitions] can come either before or after the links that use
 them.
 
-.
+```````````````````````````````` example
 [foo]: /url "title"
 
 [foo]
 .
 

foo

-. +```````````````````````````````` -. + +```````````````````````````````` example [foo]: /url 'the title' @@ -2351,29 +2604,32 @@ them. [foo] .

foo

-. +```````````````````````````````` -. + +```````````````````````````````` example [Foo*bar\]]:my_(url) 'title (with parens)' [Foo*bar\]] .

Foo*bar]

-. +```````````````````````````````` -. + +```````````````````````````````` example [Foo bar]: - + 'title' [Foo bar] .

Foo bar

-. +```````````````````````````````` + The title may extend over multiple lines: -. +```````````````````````````````` example [foo]: /url ' title line1 @@ -2387,11 +2643,12 @@ title line1 line2 ">foo

-. +```````````````````````````````` + However, it may not contain a [blank line]: -. +```````````````````````````````` example [foo]: /url 'title with blank line' @@ -2401,123 +2658,135 @@ with blank line'

[foo]: /url 'title

with blank line'

[foo]

-. +```````````````````````````````` + The title may be omitted: -. +```````````````````````````````` example [foo]: /url [foo] .

foo

-. +```````````````````````````````` + The link destination may not be omitted: -. +```````````````````````````````` example [foo]: [foo] .

[foo]:

[foo]

-. +```````````````````````````````` + Both title and destination can contain backslash escapes and literal backslashes: -. +```````````````````````````````` example [foo]: /url\bar\*baz "foo\"bar\baz" [foo] .

foo

-. +```````````````````````````````` + A link can come before its corresponding definition: -. +```````````````````````````````` example [foo] [foo]: url .

foo

-. +```````````````````````````````` + If there are several matching definitions, the first one takes precedence: -. +```````````````````````````````` example [foo] [foo]: first [foo]: second .

foo

-. +```````````````````````````````` + As noted in the section on [Links], matching of labels is case-insensitive (see [matches]). -. +```````````````````````````````` example [FOO]: /url [Foo] .

Foo

-. +```````````````````````````````` -. + +```````````````````````````````` example [ΑΓΩ]: /φου [αγω] .

αγω

-. +```````````````````````````````` + Here is a link reference definition with no corresponding link. It contributes nothing to the document. -. +```````````````````````````````` example [foo]: /url . -. +```````````````````````````````` + Here is another one: -. +```````````````````````````````` example [ foo ]: /url bar .

bar

-. +```````````````````````````````` + This is not a link reference definition, because there are -[non-whitespace character]s after the title: +[non-whitespace characters] after the title: -. +```````````````````````````````` example [foo]: /url "title" ok .

[foo]: /url "title" ok

-. +```````````````````````````````` + This is a link reference definition, but it has no title: -. +```````````````````````````````` example [foo]: /url "title" ok .

"title" ok

-. +```````````````````````````````` + This is not a link reference definition, because it is indented four spaces: -. +```````````````````````````````` example [foo]: /url "title" [foo] @@ -2525,12 +2794,13 @@ four spaces:
[foo]: /url "title"
 

[foo]

-. +```````````````````````````````` + This is not a link reference definition, because it occurs inside a code block: -. +```````````````````````````````` example ``` [foo]: /url ``` @@ -2540,11 +2810,12 @@ a code block:
[foo]: /url
 

[foo]

-. +```````````````````````````````` + A [link reference definition] cannot interrupt a paragraph. -. +```````````````````````````````` example Foo [bar]: /baz @@ -2553,12 +2824,13 @@ Foo

Foo [bar]: /baz

[bar]

-. +```````````````````````````````` + However, it can directly follow other block elements, such as headings and thematic breaks, and it need not be followed by a blank line. -. +```````````````````````````````` example # [Foo] [foo]: /url > bar @@ -2567,12 +2839,13 @@ and thematic breaks, and it need not be followed by a blank line.

bar

-. +```````````````````````````````` + -Several [link reference definition]s +Several [link reference definitions] can occur one after another, without intervening blank lines. -. +```````````````````````````````` example [foo]: /foo-url "foo" [bar]: /bar-url "bar" @@ -2585,14 +2858,15 @@ can occur one after another, without intervening blank lines.

foo, bar, baz

-. +```````````````````````````````` -[Link reference definition]s can occur + +[Link reference definitions] can occur inside block containers, like lists and block quotations. They affect the entire document, not just the container in which they are defined: -. +```````````````````````````````` example [foo] > [foo]: /url @@ -2600,13 +2874,14 @@ are defined:

foo

-. +```````````````````````````````` + ## Paragraphs A sequence of non-blank lines that cannot be interpreted as other -kinds of blocks forms a [paragraph](@paragraph). +kinds of blocks forms a [paragraph](@). The contents of the paragraph are the result of parsing the paragraph's raw content as inlines. The paragraph's raw content is formed by concatenating the lines and removing initial and final @@ -2614,18 +2889,19 @@ is formed by concatenating the lines and removing initial and final A simple example with two paragraphs: -. +```````````````````````````````` example aaa bbb .

aaa

bbb

-. +```````````````````````````````` + Paragraphs can contain multiple lines, but no blank lines: -. +```````````````````````````````` example aaa bbb @@ -2636,11 +2912,12 @@ ddd bbb

ccc ddd

-. +```````````````````````````````` + Multiple blank lines between paragraph have no effect: -. +```````````````````````````````` example aaa @@ -2648,22 +2925,24 @@ bbb .

aaa

bbb

-. +```````````````````````````````` + Leading spaces are skipped: -. +```````````````````````````````` example aaa bbb .

aaa bbb

-. +```````````````````````````````` + Lines after the first may be indented any amount, since indented code blocks cannot interrupt paragraphs. -. +```````````````````````````````` example aaa bbb ccc @@ -2671,49 +2950,53 @@ aaa

aaa bbb ccc

-. +```````````````````````````````` + However, the first line may be indented at most three spaces, or an indented code block will be triggered: -. +```````````````````````````````` example aaa bbb .

aaa bbb

-. +```````````````````````````````` -. + +```````````````````````````````` example aaa bbb .
aaa
 

bbb

-. +```````````````````````````````` + Final spaces are stripped before inline parsing, so a paragraph that ends with two or more spaces will not end with a [hard line break]: -. +```````````````````````````````` example aaa bbb .

aaa
bbb

-. +```````````````````````````````` + ## Blank lines -[Blank line]s between block-level elements are ignored, +[Blank lines] between block-level elements are ignored, except for the role they play in determining whether a [list] is [tight] or [loose]. Blank lines at the beginning and end of the document are also ignored. -. +```````````````````````````````` example aaa @@ -2725,7 +3008,8 @@ aaa .

aaa

aaa

-. +```````````````````````````````` + # Container blocks @@ -2750,7 +3034,7 @@ these constructions. (A recipe is provided below in the section entitled ## Block quotes -A [block quote marker](@block-quote-marker) +A [block quote marker](@) consists of 0-3 spaces of initial indent, plus (a) the character `>` together with a following space, or (b) a single character `>` not followed by a space. @@ -2767,7 +3051,7 @@ The following rules define [block quotes]: more lines in which the next [non-whitespace character] after the [block quote marker] is [paragraph continuation text] is a block quote with *Bs* as its content. - [Paragraph continuation text](@paragraph-continuation-text) is text + [Paragraph continuation text](@) is text that will be parsed as part of the content of a paragraph, but does not occur at the beginning of the paragraph. @@ -2778,7 +3062,7 @@ Nothing else counts as a [block quote](#block-quotes). Here is a simple example: -. +```````````````````````````````` example > # Foo > bar > baz @@ -2788,11 +3072,12 @@ Here is a simple example:

bar baz

-. +```````````````````````````````` + The spaces after the `>` characters can be omitted: -. +```````````````````````````````` example ># Foo >bar > baz @@ -2802,11 +3087,12 @@ The spaces after the `>` characters can be omitted:

bar baz

-. +```````````````````````````````` + The `>` characters can be indented 1-3 spaces: -. +```````````````````````````````` example > # Foo > bar > baz @@ -2816,11 +3102,12 @@ The `>` characters can be indented 1-3 spaces:

bar baz

-. +```````````````````````````````` + Four spaces gives us a code block: -. +```````````````````````````````` example > # Foo > bar > baz @@ -2829,12 +3116,13 @@ Four spaces gives us a code block: > bar > baz
-. +```````````````````````````````` + The Laziness clause allows us to omit the `>` before a paragraph continuation line: -. +```````````````````````````````` example > # Foo > bar baz @@ -2844,12 +3132,13 @@ baz

bar baz

-. +```````````````````````````````` + A block quote can contain some lazy and some non-lazy continuation lines: -. +```````````````````````````````` example > bar baz > foo @@ -2859,10 +3148,11 @@ baz baz foo

-. +```````````````````````````````` + Laziness only applies to lines that would have been continuations of -paragraphs had they been prepended with [block quote marker]s. +paragraphs had they been prepended with [block quote markers]. For example, the `> ` cannot be omitted in the second line of ``` markdown @@ -2872,7 +3162,7 @@ For example, the `> ` cannot be omitted in the second line of without changing the meaning: -. +```````````````````````````````` example > foo --- . @@ -2880,7 +3170,8 @@ without changing the meaning:

foo


-. +```````````````````````````````` + Similarly, if we omit the `> ` in the second line of @@ -2891,7 +3182,7 @@ Similarly, if we omit the `> ` in the second line of then the block quote ends after the first line: -. +```````````````````````````````` example > - foo - bar . @@ -2903,12 +3194,13 @@ then the block quote ends after the first line:
  • bar
-. +```````````````````````````````` + For the same reason, we can't omit the `> ` in front of subsequent lines of an indented or fenced code block: -. +```````````````````````````````` example > foo bar . @@ -2918,9 +3210,10 @@ subsequent lines of an indented or fenced code block:
bar
 
-. +```````````````````````````````` -. + +```````````````````````````````` example > ``` foo ``` @@ -2930,12 +3223,13 @@ foo

foo

-. +```````````````````````````````` + Note that in the following case, we have a paragraph continuation line: -. +```````````````````````````````` example > foo - bar . @@ -2943,7 +3237,8 @@ continuation line:

foo - bar

-. +```````````````````````````````` + To see why, note that in @@ -2958,25 +3253,27 @@ interrupt paragraphs, so it is a [paragraph continuation line]. A block quote can be empty: -. +```````````````````````````````` example > .
-. +```````````````````````````````` -. + +```````````````````````````````` example > > > .
-. +```````````````````````````````` + A block quote can have initial or final blank lines: -. +```````````````````````````````` example > > foo > @@ -2984,11 +3281,12 @@ A block quote can have initial or final blank lines:

foo

-. +```````````````````````````````` + A blank line always separates block quotes: -. +```````````````````````````````` example > foo > bar @@ -2999,7 +3297,8 @@ A blank line always separates block quotes:

bar

-. +```````````````````````````````` + (Most current Markdown implementations, including John Gruber's original `Markdown.pl`, will parse this example as a single block quote @@ -3009,7 +3308,7 @@ whether two block quotes or one are wanted.) Consecutiveness means that if we put these block quotes together, we get a single block quote: -. +```````````````````````````````` example > foo > bar . @@ -3017,11 +3316,12 @@ we get a single block quote:

foo bar

-. +```````````````````````````````` + To get a block quote with two paragraphs, use: -. +```````````````````````````````` example > foo > > bar @@ -3030,11 +3330,12 @@ To get a block quote with two paragraphs, use:

foo

bar

-. +```````````````````````````````` + Block quotes can interrupt paragraphs: -. +```````````````````````````````` example foo > bar . @@ -3042,12 +3343,13 @@ foo

bar

-. +```````````````````````````````` + In general, blank lines are not needed before or after block quotes: -. +```````````````````````````````` example > aaa *** > bbb @@ -3059,12 +3361,13 @@ quotes:

bbb

-. +```````````````````````````````` + However, because of laziness, a blank line is needed between a block quote and a following paragraph: -. +```````````````````````````````` example > bar baz . @@ -3072,9 +3375,10 @@ baz

bar baz

-. +```````````````````````````````` -. + +```````````````````````````````` example > bar baz @@ -3083,9 +3387,10 @@ baz

bar

baz

-. +```````````````````````````````` -. + +```````````````````````````````` example > bar > baz @@ -3094,13 +3399,14 @@ baz

bar

baz

-. +```````````````````````````````` + It is a consequence of the Laziness rule that any number of initial `>`s may be omitted on a continuation line of a nested block quote: -. +```````````````````````````````` example > > > foo bar . @@ -3112,9 +3418,10 @@ bar

-. +```````````````````````````````` -. + +```````````````````````````````` example >>> foo > bar >>baz @@ -3128,14 +3435,15 @@ baz

-. +```````````````````````````````` + When including an indented code block in a block quote, remember that the [block quote marker] includes both the `>` and a following space. So *five spaces* are needed after the `>`: -. +```````````````````````````````` example > code > not code @@ -3147,18 +3455,19 @@ the `>`:

not code

-. +```````````````````````````````` + ## List items -A [list marker](@list-marker) is a +A [list marker](@) is a [bullet list marker] or an [ordered list marker]. -A [bullet list marker](@bullet-list-marker) +A [bullet list marker](@) is a `-`, `+`, or `*` character. -An [ordered list marker](@ordered-list-marker) +An [ordered list marker](@) is a sequence of 1--9 arabic digits (`0-9`), followed by either a `.` character or a `)` character. (The reason for the length limit is that with 10 digits we start seeing integer overflows @@ -3179,7 +3488,7 @@ The following rules define [list items]: For example, let *Ls* be the lines -. +```````````````````````````````` example A paragraph with two lines. @@ -3194,13 +3503,14 @@ with two lines.

A block quote.

-. +```````````````````````````````` + And let *M* be the marker `1.`, and *N* = 2. Then rule #1 says that the following is an ordered list item with start number 1, and the same contents as *Ls*: -. +```````````````````````````````` example 1. A paragraph with two lines. @@ -3219,7 +3529,8 @@ with two lines.

-. +```````````````````````````````` + The most important thing to notice is that the position of the text after the list marker determines how much indentation @@ -3232,7 +3543,7 @@ item. Here are some examples showing how far content must be indented to be put under the list item: -. +```````````````````````````````` example - one two @@ -3241,9 +3552,10 @@ put under the list item:
  • one
  • two

    -. +```````````````````````````````` -. + +```````````````````````````````` example - one two @@ -3254,9 +3566,10 @@ put under the list item:

    two

    -. +```````````````````````````````` -. + +```````````````````````````````` example - one two @@ -3266,9 +3579,10 @@ put under the list item:
     two
     
    -. +```````````````````````````````` -. + +```````````````````````````````` example - one two @@ -3279,7 +3593,8 @@ put under the list item:

    two

    -. +```````````````````````````````` + It is tempting to think of this in terms of columns: the continuation blocks must be indented at least to the column of the first @@ -3289,7 +3604,7 @@ is needed. Which column this indentation reaches will depend on how the list item is embedded in other constructions, as shown by this example: -. +```````````````````````````````` example > > 1. one >> >> two @@ -3304,7 +3619,8 @@ this example: -. +```````````````````````````````` + Here `two` occurs in the same column as the list marker `1.`, but is actually contained in the list item, because there is @@ -3315,7 +3631,7 @@ occurs far to the right of the initial text of the list item, `one`, but it is not considered part of the list item, because it is not indented far enough past the blockquote marker: -. +```````````````````````````````` example >>- one >> > > two @@ -3328,25 +3644,27 @@ far enough past the blockquote marker:

    two

    -. +```````````````````````````````` + Note that at least one space is needed between the list marker and any following content, so these are not list items: -. +```````````````````````````````` example -one 2.two .

    -one

    2.two

    -. +```````````````````````````````` + A list item may not contain blocks that are separated by more than one blank line. Thus, two blank lines will end a list, unless the two blanks are contained in a [fenced code block]. -. +```````````````````````````````` example - foo bar @@ -3403,11 +3721,12 @@ bar -. +```````````````````````````````` + A list item may contain any kind of block: -. +```````````````````````````````` example 1. foo ``` @@ -3429,14 +3748,15 @@ A list item may contain any kind of block: -. +```````````````````````````````` + A list item that contains an indented code block will preserve empty lines within the code block verbatim, unless there are two or more empty lines in a row (since as described above, two blank lines end the list): -. +```````````````````````````````` example - Foo bar @@ -3452,9 +3772,10 @@ baz
    -. +```````````````````````````````` -. + +```````````````````````````````` example - Foo bar @@ -3471,49 +3792,55 @@ baz
      baz
     
    -. +```````````````````````````````` + Note that ordered list start numbers must be nine digits or less: -. +```````````````````````````````` example 123456789. ok .
    1. ok
    -. +```````````````````````````````` -. + +```````````````````````````````` example 1234567890. not ok .

    1234567890. not ok

    -. +```````````````````````````````` + A start number may begin with 0s: -. +```````````````````````````````` example 0. ok .
    1. ok
    -. +```````````````````````````````` -. + +```````````````````````````````` example 003. ok .
    1. ok
    -. +```````````````````````````````` + A start number may not be negative: -. +```````````````````````````````` example -1. not ok .

    -1. not ok

    -. +```````````````````````````````` + 2. **Item starting with indented code.** If a sequence of lines *Ls* @@ -3532,7 +3859,7 @@ An indented code block will have to be indented four spaces beyond the edge of the region where text will be included in the list item. In the following case that is 6 spaces: -. +```````````````````````````````` example - foo bar @@ -3544,11 +3871,12 @@ In the following case that is 6 spaces: -. +```````````````````````````````` + And in this case it is 11 spaces: -. +```````````````````````````````` example 10. foo bar @@ -3560,13 +3888,14 @@ And in this case it is 11 spaces: -. +```````````````````````````````` + If the *first* block in the list item is an indented code block, then by rule #2, the contents must be indented *one* space after the list marker: -. +```````````````````````````````` example indented code paragraph @@ -3578,9 +3907,10 @@ paragraph

    paragraph

    more code
     
    -. +```````````````````````````````` -. + +```````````````````````````````` example 1. indented code paragraph @@ -3596,12 +3926,13 @@ paragraph -. +```````````````````````````````` + Note that an additional space indent is interpreted as space inside the code block: -. +```````````````````````````````` example 1. indented code paragraph @@ -3617,7 +3948,8 @@ 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 @@ -3627,16 +3959,17 @@ block. In a case like the following, where the first block begins with a three-space indent, the rules do not allow us to form a list item by indenting the whole thing and prepending a list marker: -. +```````````````````````````````` example foo bar .

    foo

    bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example - foo bar @@ -3645,14 +3978,15 @@ bar
  • foo
  • bar

    -. +```````````````````````````````` + This is not a significant restriction, because when a block begins with 1-3 spaces indent, the indentation can always be removed without a change in interpretation, allowing rule #1 to be applied. So, in the above case: -. +```````````````````````````````` example - foo bar @@ -3663,7 +3997,8 @@ the above case:

    bar

    -. +```````````````````````````````` + 3. **Item starting with a blank line.** If a sequence of lines *Ls* starting with a single [blank line] constitute a (possibly empty) @@ -3679,7 +4014,7 @@ the above case: Here are some list items that start with a blank line but are not empty: -. +```````````````````````````````` example - foo - @@ -3700,13 +4035,14 @@ Here are some list items that start with a blank line but are not empty: -. +```````````````````````````````` + A list item can begin with at most one blank line. In the following example, `foo` is not part of the list item: -. +```````````````````````````````` example - foo @@ -3715,11 +4051,12 @@ item:
  • foo

    -. +```````````````````````````````` + Here is an empty bullet list item: -. +```````````````````````````````` example - foo - - bar @@ -3729,11 +4066,12 @@ Here is an empty bullet list item:
  • bar
  • -. +```````````````````````````````` + It does not matter whether there are spaces following the [list marker]: -. +```````````````````````````````` example - foo - - bar @@ -3743,11 +4081,12 @@ It does not matter whether there are spaces following the [list marker]:
  • bar
  • -. +```````````````````````````````` + Here is an empty ordered list item: -. +```````````````````````````````` example 1. foo 2. 3. bar @@ -3757,17 +4096,19 @@ Here is an empty ordered list item:
  • bar
  • -. +```````````````````````````````` + A list may start or end with an empty list item: -. +```````````````````````````````` example * .
    -. +```````````````````````````````` + 4. **Indentation.** If a sequence of lines *Ls* constitutes a list item @@ -3778,7 +4119,7 @@ A list may start or end with an empty list item: Indented one space: -. +```````````````````````````````` example 1. A paragraph with two lines. @@ -3797,11 +4138,12 @@ with two lines.

    -. +```````````````````````````````` + Indented two spaces: -. +```````````````````````````````` example 1. A paragraph with two lines. @@ -3820,11 +4162,12 @@ with two lines.

    -. +```````````````````````````````` + Indented three spaces: -. +```````````````````````````````` example 1. A paragraph with two lines. @@ -3843,11 +4186,12 @@ with two lines.

    -. +```````````````````````````````` + Four spaces indent gives a code block: -. +```````````````````````````````` example 1. A paragraph with two lines. @@ -3862,7 +4206,8 @@ Four spaces indent gives a code block: > A block quote. -. +```````````````````````````````` + 5. **Laziness.** If a string of lines *Ls* constitute a [list @@ -3872,11 +4217,11 @@ Four spaces indent gives a code block: [paragraph continuation text] is a list item with the same contents and attributes. The unindented lines are called - [lazy continuation line](@lazy-continuation-line)s. + [lazy continuation line](@)s. -Here is an example with [lazy continuation line]s: +Here is an example with [lazy continuation lines]: -. +```````````````````````````````` example 1. A paragraph with two lines. @@ -3895,11 +4240,12 @@ with two lines.

    -. +```````````````````````````````` + Indentation can be partially deleted: -. +```````````````````````````````` example 1. A paragraph with two lines. . @@ -3907,11 +4253,12 @@ Indentation can be partially deleted:
  • A paragraph with two lines.
  • -. +```````````````````````````````` + These examples show how laziness can work in nested structures: -. +```````````````````````````````` example > 1. > Blockquote continued here. . @@ -3925,9 +4272,10 @@ continued here.

    -. +```````````````````````````````` -. + +```````````````````````````````` example > 1. > Blockquote > continued here. . @@ -3941,7 +4289,8 @@ continued here.

    -. +```````````````````````````````` + 6. **That's all.** Nothing that is not counted as a list item by rules @@ -3953,7 +4302,7 @@ in order to be included in the list item. So, in this case we need two spaces indent: -. +```````````````````````````````` example - foo - bar - baz @@ -3969,11 +4318,12 @@ So, in this case we need two spaces indent: -. +```````````````````````````````` + One is not enough: -. +```````````````````````````````` example - foo - bar - baz @@ -3983,11 +4333,12 @@ One is not enough:
  • bar
  • baz
  • -. +```````````````````````````````` + Here we need four, because the list marker is wider: -. +```````````````````````````````` example 10) foo - bar . @@ -3998,11 +4349,12 @@ Here we need four, because the list marker is wider: -. +```````````````````````````````` + Three is not enough: -. +```````````````````````````````` example 10) foo - bar . @@ -4012,11 +4364,12 @@ Three is not enough:
    • bar
    -. +```````````````````````````````` + A list may be the first block in a list item: -. +```````````````````````````````` example - - foo .
      @@ -4026,9 +4379,10 @@ A list may be the first block in a list item:
    -. +```````````````````````````````` -. + +```````````````````````````````` example 1. - 2. foo .
      @@ -4042,11 +4396,12 @@ A list may be the first block in a list item:
    -. +```````````````````````````````` + A list item can contain a heading: -. +```````````````````````````````` example - # Foo - Bar --- @@ -4060,7 +4415,8 @@ A list item can contain a heading:

    Bar

    baz -. +```````````````````````````````` + ### Motivation @@ -4250,39 +4606,39 @@ takes four spaces (a common case), but diverge in other cases. ## Lists -A [list](@list) is a sequence of one or more +A [list](@) is a sequence of one or more list items [of the same type]. The list items may be separated by single [blank lines], but two blank lines end all containing lists. -Two list items are [of the same type](@of-the-same-type) +Two list items are [of the same type](@) if they begin with a [list marker] of the same type. Two list markers are of the same type if (a) they are bullet list markers using the same character (`-`, `+`, or `*`) or (b) they are ordered list numbers with the same delimiter (either `.` or `)`). -A list is an [ordered list](@ordered-list) +A list is an [ordered list](@) if its constituent list items begin with -[ordered list marker]s, and a -[bullet list](@bullet-list) if its constituent list -items begin with [bullet list marker]s. +[ordered list markers], and a +[bullet list](@) if its constituent list +items begin with [bullet list markers]. -The [start number](@start-number) +The [start number](@) of an [ordered list] is determined by the list number of its initial list item. The numbers of subsequent list items are disregarded. -A list is [loose](@loose) if any of its constituent +A list is [loose](@) if any of its constituent list items are separated by blank lines, or if any of its constituent list items directly contain two block-level elements with a blank line -between them. Otherwise a list is [tight](@tight). +between them. Otherwise a list is [tight](@). (The difference in HTML output is that paragraphs in a loose list are wrapped in `

    ` tags, while paragraphs in a tight list are not.) Changing the bullet or ordered list delimiter starts a new list: -. +```````````````````````````````` example - foo - bar + baz @@ -4294,9 +4650,10 @@ Changing the bullet or ordered list delimiter starts a new list:

    • baz
    -. +```````````````````````````````` -. + +```````````````````````````````` example 1. foo 2. bar 3) baz @@ -4308,13 +4665,14 @@ Changing the bullet or ordered list delimiter starts a new list:
    1. baz
    -. +```````````````````````````````` + In CommonMark, a list can interrupt a paragraph. That is, no blank line is needed to separate a paragraph from a following list: -. +```````````````````````````````` example Foo - bar - baz @@ -4324,12 +4682,13 @@ Foo
  • bar
  • baz
  • -. +```````````````````````````````` + `Markdown.pl` does not allow this, through fear of triggering a list via a numeral in a hard-wrapped line: -. +```````````````````````````````` example The number of windows in my house is 14. The number of doors is 6. . @@ -4337,7 +4696,8 @@ The number of windows in my house is
    1. The number of doors is 6.
    -. +```````````````````````````````` + Oddly, `Markdown.pl` *does* allow a blockquote to interrupt a paragraph, even though the same considerations might apply. We think that the two @@ -4354,7 +4714,7 @@ blank lines: Second, we are attracted to a -> [principle of uniformity](@principle-of-uniformity): +> [principle of uniformity](@): > if a chunk of text has a certain > meaning, it will continue to have the same meaning when put into a > container block (such as a list item or blockquote). @@ -4394,7 +4754,7 @@ seems more consistent with established practice with Markdown. There can be blank lines between items, but two blank lines end a list: -. +```````````````````````````````` example - foo - bar @@ -4413,13 +4773,14 @@ a list:
    • baz
    -. +```````````````````````````````` + As illustrated above in the section on [list items], two blank lines between blocks *within* a list item will also end a list: -. +```````````````````````````````` example - foo @@ -4433,11 +4794,12 @@ list:
    • baz
    -. +```````````````````````````````` + Indeed, two blank lines will end *all* containing lists: -. +```````````````````````````````` example - foo - bar - baz @@ -4458,14 +4820,15 @@ Indeed, two blank lines will end *all* containing lists:
      bim
     
    -. +```````````````````````````````` + Thus, two blank lines can be used to separate consecutive lists of the same type, or to separate a list from an indented code block that would otherwise be parsed as a subparagraph of the final list item: -. +```````````````````````````````` example - foo - bar @@ -4481,9 +4844,10 @@ item:
  • baz
  • bim
  • -. +```````````````````````````````` -. + +```````````````````````````````` example - foo notcode @@ -4504,14 +4868,15 @@ item:
    code
     
    -. +```````````````````````````````` + List items need not be indented to the same level. The following list items will be treated as items at the same list level, since none is indented enough to belong to the previous list item: -. +```````````````````````````````` example - a - b - c @@ -4533,9 +4898,10 @@ item:
  • h
  • i
  • -. +```````````````````````````````` -. + +```````````````````````````````` example 1. a 2. b @@ -4553,12 +4919,13 @@ item:

    c

    -. +```````````````````````````````` + This is a loose list, because there is a blank line between two of the list items: -. +```````````````````````````````` example - a - b @@ -4575,11 +4942,12 @@ two of the list items:

    c

    -. +```````````````````````````````` + So is this, with a empty second item: -. +```````````````````````````````` example * a * @@ -4594,13 +4962,14 @@ So is this, with a empty second item:

    c

    -. +```````````````````````````````` + These are loose lists, even though there is no space between the items, because one of the items directly contains two block-level elements with a blank line between them: -. +```````````````````````````````` example - a - b @@ -4619,9 +4988,10 @@ with a blank line between them:

    d

    -. +```````````````````````````````` -. + +```````````````````````````````` example - a - b @@ -4639,11 +5009,12 @@ with a blank line between them:

    d

    -. +```````````````````````````````` + This is a tight list, because the blank lines are in a code block: -. +```````````````````````````````` example - a - ``` b @@ -4662,13 +5033,14 @@ This is a tight list, because the blank lines are in a code block:
  • c
  • -. +```````````````````````````````` + This is a tight list, because the blank line is between two paragraphs of a sublist. So the sublist is loose while the outer list is tight: -. +```````````````````````````````` example - a - b @@ -4686,12 +5058,13 @@ the outer list is tight:
  • d
  • -. +```````````````````````````````` + This is a tight list, because the blank line is inside the block quote: -. +```````````````````````````````` example * a > b > @@ -4705,12 +5078,13 @@ block quote:
  • c
  • -. +```````````````````````````````` + This list is tight, because the consecutive block elements are not separated by blank lines: -. +```````````````````````````````` example - a > b ``` @@ -4728,19 +5102,21 @@ are not separated by blank lines:
  • d
  • -. +```````````````````````````````` + A single-paragraph list is tight: -. +```````````````````````````````` example - a .
    • a
    -. +```````````````````````````````` -. + +```````````````````````````````` example - a - b . @@ -4751,12 +5127,13 @@ A single-paragraph list is tight: -. +```````````````````````````````` + This list is loose, because of the blank line between the two block elements in the list item: -. +```````````````````````````````` example 1. ``` foo ``` @@ -4770,11 +5147,12 @@ two block elements in the list item:

    bar

    -. +```````````````````````````````` + Here the outer list is loose, the inner list tight: -. +```````````````````````````````` example * foo * bar @@ -4789,9 +5167,10 @@ Here the outer list is loose, the inner list tight:

    baz

    -. +```````````````````````````````` -. + +```````````````````````````````` example - a - b - c @@ -4816,7 +5195,8 @@ Here the outer list is loose, the inner list tight: -. +```````````````````````````````` + # Inlines @@ -4824,11 +5204,12 @@ Inlines are parsed sequentially from the beginning of the character stream to the end (left to right, in left-to-right languages). Thus, for example, in -. +```````````````````````````````` example `hi`lo` .

    hilo`

    -. +```````````````````````````````` + `hi` is parsed as code, leaving the backtick at the end as a literal backtick. @@ -4837,25 +5218,27 @@ backtick. Any ASCII punctuation character may be backslash-escaped: -. +```````````````````````````````` example \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~ .

    !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    -. +```````````````````````````````` + Backslashes before other characters are treated as literal backslashes: -. +```````````````````````````````` example \→\A\a\ \3\φ\« .

    \→\A\a\ \3\φ\«

    -. +```````````````````````````````` + Escaped characters are treated as regular characters and do not have their usual Markdown meanings: -. +```````````````````````````````` example \*not emphasized* \
    not a tag \[not a link](/foo) @@ -4873,107 +5256,118 @@ not have their usual Markdown meanings: * not a list # not a heading [foo]: /url "not a reference"

    -. +```````````````````````````````` + If a backslash is itself escaped, the following character is not: -. +```````````````````````````````` example \\*emphasis* .

    \emphasis

    -. +```````````````````````````````` + A backslash at the end of the line is a [hard line break]: -. +```````````````````````````````` example foo\ bar .

    foo
    bar

    -. +```````````````````````````````` + Backslash escapes do not work in code blocks, code spans, autolinks, or raw HTML: -. +```````````````````````````````` example `` \[\` `` .

    \[\`

    -. +```````````````````````````````` -. + +```````````````````````````````` example \[\] .
    \[\]
     
    -. +```````````````````````````````` -. + +```````````````````````````````` example ~~~ \[\] ~~~ .
    \[\]
     
    -. +```````````````````````````````` -. + +```````````````````````````````` example .

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

    -. +```````````````````````````````` -. + +```````````````````````````````` example . -. +```````````````````````````````` + But they work in all other contexts, including URLs and link titles, -link references, and [info string]s in [fenced code block]s: +link references, and [info strings] in [fenced code blocks]: -. +```````````````````````````````` example [foo](/bar\* "ti\*tle") .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo] [foo]: /bar\* "ti\*tle" .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ``` foo\+bar foo ``` .
    foo
     
    -. +```````````````````````````````` + ## Entity and numeric character references All valid HTML entity references and numeric character -references, except those occuring in code blocks, code spans, -and raw HTML, are recognized as such and treated as equivalent to the +references, except those occuring in code blocks and code spans, +are recognized as such and treated as equivalent to the corresponding Unicode characters. Conforming CommonMark parsers need not store information about whether a particular character was represented in the source using a Unicode character or an entity reference. -[Entity references](@entity-references) consist of `&` + any of the valid +[Entity references](@) consist of `&` + any of the valid HTML5 entity names + `;`. The document is used as an authoritative source for the valid entity references and their corresponding code points. -. +```````````````````````````````` example   & © Æ Ď ¾ ℋ ⅆ ∲ ≧̸ @@ -4981,182 +5375,193 @@ references and their corresponding code points.

      & © Æ Ď ¾ ℋ ⅆ ∲ ≧̸

    -. +```````````````````````````````` + [Decimal numeric character -references](@decimal-numeric-character-references) +references](@) consist of `&#` + a string of 1--8 arabic digits + `;`. A numeric character reference is parsed as the corresponding Unicode character. Invalid Unicode code points will be replaced by -the "unknown code point" character (`U+FFFD`). For security reasons, +the REPLACEMENT CHARACTER (`U+FFFD`). For security reasons, the code point `U+0000` will also be replaced by `U+FFFD`. -. +```````````````````````````````` example # Ӓ Ϡ � � .

    # Ӓ Ϡ � �

    -. +```````````````````````````````` + [Hexadecimal numeric character -references](@hexadecimal-numeric-character-references) consist of `&#` + +references](@) consist of `&#` + either `X` or `x` + a string of 1-8 hexadecimal digits + `;`. They too are parsed as the corresponding Unicode character (this time specified with a hexadecimal numeral instead of decimal). -. +```````````````````````````````` example " ആ ಫ .

    " ആ ಫ

    -. +```````````````````````````````` + Here are some nonentities: -. +```````````````````````````````` example   &x; &#; &#x; -&ThisIsWayTooLongToBeAnEntityIsntIt; &hi?; +&ThisIsNotDefined; &hi?; .

    &nbsp &x; &#; &#x; -&ThisIsWayTooLongToBeAnEntityIsntIt; &hi?;

    -. +&ThisIsNotDefined; &hi?;

    +```````````````````````````````` + Although HTML5 does accept some entity references without a trailing semicolon (such as `©`), these are not recognized here, because it makes the grammar too ambiguous: -. +```````````````````````````````` example © .

    &copy

    -. +```````````````````````````````` + Strings that are not on the list of HTML5 named entities are not recognized as entity references either: -. +```````````````````````````````` example &MadeUpEntity; .

    &MadeUpEntity;

    -. +```````````````````````````````` + Entity and numeric character references are recognized in any -context besides code spans or code blocks or raw HTML, including -URLs, [link title]s, and [fenced code block][] [info string]s: +context besides code spans or code blocks, including +URLs, [link titles], and [fenced code block][] [info strings]: -. +```````````````````````````````` example . -. +```````````````````````````````` -. + +```````````````````````````````` example [foo](/föö "föö") .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo] [foo]: /föö "föö" .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ``` föö foo ``` .
    foo
     
    -. +```````````````````````````````` + Entity and numeric character references are treated as literal -text in code spans and code blocks, and in raw HTML: +text in code spans and code blocks: -. +```````````````````````````````` example `föö` .

    f&ouml;&ouml;

    -. +```````````````````````````````` -. + +```````````````````````````````` example föfö .
    f&ouml;f&ouml;
     
    -. +```````````````````````````````` -. - -. - -. ## Code spans -A [backtick string](@backtick-string) +A [backtick string](@) is a string of one or more backtick characters (`` ` ``) that is neither preceded nor followed by a backtick. -A [code span](@code-span) begins with a backtick string and ends with +A [code span](@) begins with a backtick string and ends with a backtick string of equal length. The contents of the code span are the characters between the two backtick strings, with leading and -trailing spaces and [line ending]s removed, and +trailing spaces and [line endings] removed, and [whitespace] collapsed to single spaces. This is a simple code span: -. +```````````````````````````````` example `foo` .

    foo

    -. +```````````````````````````````` + Here two backticks are used, because the code contains a backtick. This example also illustrates stripping of leading and trailing spaces: -. +```````````````````````````````` example `` foo ` bar `` .

    foo ` bar

    -. +```````````````````````````````` + This example shows the motivation for stripping leading and trailing spaces: -. +```````````````````````````````` example ` `` ` .

    ``

    -. +```````````````````````````````` -[Line ending]s are treated like spaces: -. +[Line endings] are treated like spaces: + +```````````````````````````````` example `` foo `` .

    foo

    -. +```````````````````````````````` -Interior spaces and [line ending]s are collapsed into + +Interior spaces and [line endings] are collapsed into single spaces, just as they would be by a browser: -. +```````````````````````````````` example `foo bar baz` .

    foo bar baz

    -. +```````````````````````````````` + Q: Why not just leave the spaces, since browsers will collapse them anyway? A: Because we might be targeting a non-HTML format, and we shouldn't rely on HTML-specific rendering assumptions. (Existing implementations differ in their treatment of internal -spaces and [line ending]s. Some, including `Markdown.pl` and +spaces and [line endings]. Some, including `Markdown.pl` and `showdown`, convert an internal [line ending] into a `
    ` tag. But this makes things difficult for those who like to hard-wrap their paragraphs, since a line break in the midst of a code @@ -5164,20 +5569,22 @@ span will cause an unintended line break in the output. Others just leave internal spaces as they are, which is fine if only HTML is being targeted.) -. +```````````````````````````````` example `foo `` bar` .

    foo `` bar

    -. +```````````````````````````````` + Note that backslash escapes do not work in code spans. All backslashes are treated literally: -. +```````````````````````````````` example `foo\`bar` .

    foo\bar`

    -. +```````````````````````````````` + Backslash escapes are never needed, because one can always choose a string of *n* backtick characters as delimiters, where the code does @@ -5188,67 +5595,75 @@ constructs except HTML tags and autolinks. Thus, for example, this is not parsed as emphasized text, since the second `*` is part of a code span: -. +```````````````````````````````` example *foo`*` .

    *foo*

    -. +```````````````````````````````` + And this is not parsed as a link: -. +```````````````````````````````` example [not a `link](/foo`) .

    [not a link](/foo)

    -. +```````````````````````````````` + Code spans, HTML tags, and autolinks have the same precedence. Thus, this is code: -. +```````````````````````````````` example `
    ` .

    <a href="">`

    -. +```````````````````````````````` + But this is an HTML tag: -. +```````````````````````````````` example
    ` .

    `

    -. +```````````````````````````````` + And this is code: -. +```````````````````````````````` example `` .

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

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

    http://foo.bar.`baz`

    -. +```````````````````````````````` + When a backtick string is not closed by a matching backtick string, we just have literal backticks: -. +```````````````````````````````` example ```foo`` .

    ```foo``

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

    `foo

    -. +```````````````````````````````` + ## Emphasis and strong emphasis @@ -5296,19 +5711,19 @@ no emphasis: foo_bar_baz The rules given below capture all of these patterns, while allowing for efficient parsing strategies that do not backtrack. -First, some definitions. A [delimiter run](@delimiter-run) is either +First, some definitions. A [delimiter run](@) is either a sequence of one or more `*` characters that is not preceded or followed by a `*` character, or a sequence of one or more `_` characters that is not preceded or followed by a `_` character. -A [left-flanking delimiter run](@left-flanking-delimiter-run) is +A [left-flanking delimiter run](@) is a [delimiter run] that is (a) not followed by [Unicode whitespace], and (b) either not followed by a [punctuation character], or preceded by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. -A [right-flanking delimiter run](@right-flanking-delimiter-run) is +A [right-flanking delimiter run](@) is a [delimiter run] that is (a) not preceded by [Unicode whitespace], and (b) either not preceded by a [punctuation character], or followed by [Unicode whitespace] or a [punctuation character]. @@ -5359,7 +5774,7 @@ are a bit more complex than the ones given here.) The following rules define emphasis and strong emphasis: -1. A single `*` character [can open emphasis](@can-open-emphasis) +1. A single `*` character [can open emphasis](@) iff (if and only if) it is part of a [left-flanking delimiter run]. 2. A single `_` character [can open emphasis] iff @@ -5368,7 +5783,7 @@ The following rules define emphasis and strong emphasis: or (b) part of a [right-flanking delimiter run] preceded by punctuation. -3. A single `*` character [can close emphasis](@can-close-emphasis) +3. A single `*` character [can close emphasis](@) iff it is part of a [right-flanking delimiter run]. 4. A single `_` character [can close emphasis] iff @@ -5377,7 +5792,7 @@ The following rules define emphasis and strong emphasis: or (b) part of a [left-flanking delimiter run] followed by punctuation. -5. A double `**` [can open strong emphasis](@can-open-strong-emphasis) +5. A double `**` [can open strong emphasis](@) iff it is part of a [left-flanking delimiter run]. 6. A double `__` [can open strong emphasis] iff @@ -5386,7 +5801,7 @@ The following rules define emphasis and strong emphasis: or (b) part of a [right-flanking delimiter run] preceded by punctuation. -7. A double `**` [can close strong emphasis](@can-close-strong-emphasis) +7. A double `**` [can close strong emphasis](@) iff it is part of a [right-flanking delimiter run]. 8. A double `__` [can close strong emphasis] @@ -5453,141 +5868,157 @@ These rules can be illustrated through a series of examples. Rule 1: -. +```````````````````````````````` example *foo bar* .

    foo bar

    -. +```````````````````````````````` + This is not emphasis, because the opening `*` is followed by whitespace, and hence not part of a [left-flanking delimiter run]: -. +```````````````````````````````` example a * foo bar* .

    a * foo bar*

    -. +```````````````````````````````` + This is not emphasis, because the opening `*` is preceded by an alphanumeric and followed by punctuation, and hence not part of a [left-flanking delimiter run]: -. +```````````````````````````````` example a*"foo"* .

    a*"foo"*

    -. +```````````````````````````````` + Unicode nonbreaking spaces count as whitespace, too: -. +```````````````````````````````` example * a * .

    * a *

    -. +```````````````````````````````` + Intraword emphasis with `*` is permitted: -. +```````````````````````````````` example foo*bar* .

    foobar

    -. +```````````````````````````````` -. + +```````````````````````````````` example 5*6*78 .

    5678

    -. +```````````````````````````````` + Rule 2: -. +```````````````````````````````` example _foo bar_ .

    foo bar

    -. +```````````````````````````````` + This is not emphasis, because the opening `_` is followed by whitespace: -. +```````````````````````````````` example _ foo bar_ .

    _ foo bar_

    -. +```````````````````````````````` + This is not emphasis, because the opening `_` is preceded by an alphanumeric and followed by punctuation: -. +```````````````````````````````` example a_"foo"_ .

    a_"foo"_

    -. +```````````````````````````````` + Emphasis with `_` is not allowed inside words: -. +```````````````````````````````` example foo_bar_ .

    foo_bar_

    -. +```````````````````````````````` -. + +```````````````````````````````` example 5_6_78 .

    5_6_78

    -. +```````````````````````````````` -. + +```````````````````````````````` example пристаням_стремятся_ .

    пристаням_стремятся_

    -. +```````````````````````````````` + Here `_` does not generate emphasis, because the first delimiter run is right-flanking and the second left-flanking: -. +```````````````````````````````` example aa_"bb"_cc .

    aa_"bb"_cc

    -. +```````````````````````````````` + This is emphasis, even though the opening delimiter is both left- and right-flanking, because it is preceded by punctuation: -. +```````````````````````````````` example foo-_(bar)_ .

    foo-(bar)

    -. +```````````````````````````````` + Rule 3: This is not emphasis, because the closing delimiter does not match the opening delimiter: -. +```````````````````````````````` example _foo* .

    _foo*

    -. +```````````````````````````````` + This is not emphasis, because the closing `*` is preceded by whitespace: -. +```````````````````````````````` example *foo bar * .

    *foo bar *

    -. +```````````````````````````````` + A newline also counts as whitespace: -. +```````````````````````````````` example *foo bar * . @@ -5595,34 +6026,38 @@ A newline also counts as whitespace:
    -. +```````````````````````````````` + This is not emphasis, because the second `*` is preceded by punctuation and followed by an alphanumeric (hence it is not part of a [right-flanking delimiter run]: -. +```````````````````````````````` example *(*foo) .

    *(*foo)

    -. +```````````````````````````````` + The point of this restriction is more easily appreciated with this example: -. +```````````````````````````````` example *(*foo*)* .

    (foo)

    -. +```````````````````````````````` + Intraword emphasis with `*` is allowed: -. +```````````````````````````````` example *foo*bar .

    foobar

    -. +```````````````````````````````` + Rule 4: @@ -5630,164 +6065,184 @@ Rule 4: This is not emphasis, because the closing `_` is preceded by whitespace: -. +```````````````````````````````` example _foo bar _ .

    _foo bar _

    -. +```````````````````````````````` + This is not emphasis, because the second `_` is preceded by punctuation and followed by an alphanumeric: -. +```````````````````````````````` example _(_foo) .

    _(_foo)

    -. +```````````````````````````````` + This is emphasis within emphasis: -. +```````````````````````````````` example _(_foo_)_ .

    (foo)

    -. +```````````````````````````````` + Intraword emphasis is disallowed for `_`: -. +```````````````````````````````` example _foo_bar .

    _foo_bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example _пристаням_стремятся .

    _пристаням_стремятся

    -. +```````````````````````````````` -. + +```````````````````````````````` example _foo_bar_baz_ .

    foo_bar_baz

    -. +```````````````````````````````` + This is emphasis, even though the closing delimiter is both left- and right-flanking, because it is followed by punctuation: -. +```````````````````````````````` example _(bar)_. .

    (bar).

    -. +```````````````````````````````` + Rule 5: -. +```````````````````````````````` example **foo bar** .

    foo bar

    -. +```````````````````````````````` + This is not strong emphasis, because the opening delimiter is followed by whitespace: -. +```````````````````````````````` example ** foo bar** .

    ** foo bar**

    -. +```````````````````````````````` + This is not strong emphasis, because the opening `**` is preceded by an alphanumeric and followed by punctuation, and hence not part of a [left-flanking delimiter run]: -. +```````````````````````````````` example a**"foo"** .

    a**"foo"**

    -. +```````````````````````````````` + Intraword strong emphasis with `**` is permitted: -. +```````````````````````````````` example foo**bar** .

    foobar

    -. +```````````````````````````````` + Rule 6: -. +```````````````````````````````` example __foo bar__ .

    foo bar

    -. +```````````````````````````````` + This is not strong emphasis, because the opening delimiter is followed by whitespace: -. +```````````````````````````````` example __ foo bar__ .

    __ foo bar__

    -. +```````````````````````````````` + A newline counts as whitespace: -. +```````````````````````````````` example __ foo bar__ .

    __ foo bar__

    -. +```````````````````````````````` + This is not strong emphasis, because the opening `__` is preceded by an alphanumeric and followed by punctuation: -. +```````````````````````````````` example a__"foo"__ .

    a__"foo"__

    -. +```````````````````````````````` + Intraword strong emphasis is forbidden with `__`: -. +```````````````````````````````` example foo__bar__ .

    foo__bar__

    -. +```````````````````````````````` -. + +```````````````````````````````` example 5__6__78 .

    5__6__78

    -. +```````````````````````````````` -. + +```````````````````````````````` example пристаням__стремятся__ .

    пристаням__стремятся__

    -. +```````````````````````````````` -. + +```````````````````````````````` example __foo, __bar__, baz__ .

    foo, bar, baz

    -. +```````````````````````````````` + This is strong emphasis, even though the opening delimiter is both left- and right-flanking, because it is preceded by punctuation: -. +```````````````````````````````` example foo-__(bar)__ .

    foo-(bar)

    -. +```````````````````````````````` + Rule 7: @@ -5795,11 +6250,12 @@ Rule 7: This is not strong emphasis, because the closing delimiter is preceded by whitespace: -. +```````````````````````````````` example **foo bar ** .

    **foo bar **

    -. +```````````````````````````````` + (Nor can it be interpreted as an emphasized `*foo bar *`, because of Rule 11.) @@ -5807,215 +6263,242 @@ Rule 11.) This is not strong emphasis, because the second `**` is preceded by punctuation and followed by an alphanumeric: -. +```````````````````````````````` example **(**foo) .

    **(**foo)

    -. +```````````````````````````````` + The point of this restriction is more easily appreciated with these examples: -. +```````````````````````````````` example *(**foo**)* .

    (foo)

    -. +```````````````````````````````` -. + +```````````````````````````````` example **Gomphocarpus (*Gomphocarpus physocarpus*, syn. *Asclepias physocarpa*)** .

    Gomphocarpus (Gomphocarpus physocarpus, syn. Asclepias physocarpa)

    -. +```````````````````````````````` -. + +```````````````````````````````` example **foo "*bar*" foo** .

    foo "bar" foo

    -. +```````````````````````````````` + Intraword emphasis: -. +```````````````````````````````` example **foo**bar .

    foobar

    -. +```````````````````````````````` + Rule 8: This is not strong emphasis, because the closing delimiter is preceded by whitespace: -. +```````````````````````````````` example __foo bar __ .

    __foo bar __

    -. +```````````````````````````````` + This is not strong emphasis, because the second `__` is preceded by punctuation and followed by an alphanumeric: -. +```````````````````````````````` example __(__foo) .

    __(__foo)

    -. +```````````````````````````````` + The point of this restriction is more easily appreciated with this example: -. +```````````````````````````````` example _(__foo__)_ .

    (foo)

    -. +```````````````````````````````` + Intraword strong emphasis is forbidden with `__`: -. +```````````````````````````````` example __foo__bar .

    __foo__bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example __пристаням__стремятся .

    __пристаням__стремятся

    -. +```````````````````````````````` -. + +```````````````````````````````` example __foo__bar__baz__ .

    foo__bar__baz

    -. +```````````````````````````````` + This is strong emphasis, even though the closing delimiter is both left- and right-flanking, because it is followed by punctuation: -. +```````````````````````````````` example __(bar)__. .

    (bar).

    -. +```````````````````````````````` + Rule 9: Any nonempty sequence of inline elements can be the contents of an emphasized span. -. +```````````````````````````````` example *foo [bar](/url)* .

    foo bar

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

    foo bar

    -. +```````````````````````````````` + In particular, emphasis and strong emphasis can be nested inside emphasis: -. +```````````````````````````````` example _foo __bar__ baz_ .

    foo bar baz

    -. +```````````````````````````````` -. + +```````````````````````````````` example _foo _bar_ baz_ .

    foo bar baz

    -. +```````````````````````````````` -. + +```````````````````````````````` example __foo_ bar_ .

    foo bar

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

    foo bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example *foo **bar** baz* .

    foo bar baz

    -. +```````````````````````````````` + But note: -. +```````````````````````````````` example *foo**bar**baz* .

    foobarbaz

    -. +```````````````````````````````` + The difference is that in the preceding case, the internal delimiters [can close emphasis], while in the cases with spaces, they cannot. -. +```````````````````````````````` example ***foo** bar* .

    foo bar

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

    foo bar

    -. +```````````````````````````````` + Note, however, that in the following case we get no strong emphasis, because the opening delimiter is closed by the first `*` before `bar`: -. +```````````````````````````````` example *foo**bar*** .

    foobar**

    -. +```````````````````````````````` + Indefinite levels of nesting are possible: -. +```````````````````````````````` example *foo **bar *baz* bim** bop* .

    foo bar baz bim bop

    -. +```````````````````````````````` -. + +```````````````````````````````` example *foo [*bar*](/url)* .

    foo bar

    -. +```````````````````````````````` + There can be no empty emphasis or strong emphasis: -. +```````````````````````````````` example ** is not an empty emphasis .

    ** is not an empty emphasis

    -. +```````````````````````````````` -. + +```````````````````````````````` example **** is not an empty strong emphasis .

    **** is not an empty strong emphasis

    -. +```````````````````````````````` + Rule 10: @@ -6023,431 +6506,492 @@ Rule 10: Any nonempty sequence of inline elements can be the contents of an strongly emphasized span. -. +```````````````````````````````` example **foo [bar](/url)** .

    foo bar

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

    foo bar

    -. +```````````````````````````````` + In particular, emphasis and strong emphasis can be nested inside strong emphasis: -. +```````````````````````````````` example __foo _bar_ baz__ .

    foo bar baz

    -. +```````````````````````````````` -. + +```````````````````````````````` example __foo __bar__ baz__ .

    foo bar baz

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

    foo bar

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

    foo bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example **foo *bar* baz** .

    foo bar baz

    -. +```````````````````````````````` + But note: -. +```````````````````````````````` example **foo*bar*baz** .

    foobarbaz**

    -. +```````````````````````````````` + The difference is that in the preceding case, the internal delimiters [can close emphasis], while in the cases with spaces, they cannot. -. +```````````````````````````````` example ***foo* bar** .

    foo bar

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

    foo bar

    -. +```````````````````````````````` + Indefinite levels of nesting are possible: -. +```````````````````````````````` example **foo *bar **baz** bim* bop** .

    foo bar baz bim bop

    -. +```````````````````````````````` -. + +```````````````````````````````` example **foo [*bar*](/url)** .

    foo bar

    -. +```````````````````````````````` + There can be no empty emphasis or strong emphasis: -. +```````````````````````````````` example __ is not an empty emphasis .

    __ is not an empty emphasis

    -. +```````````````````````````````` -. + +```````````````````````````````` example ____ is not an empty strong emphasis .

    ____ is not an empty strong emphasis

    -. +```````````````````````````````` + Rule 11: -. +```````````````````````````````` example foo *** .

    foo ***

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo *\** .

    foo *

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo *_* .

    foo _

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo ***** .

    foo *****

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo **\*** .

    foo *

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo **_** .

    foo _

    -. +```````````````````````````````` + Note that when delimiters do not match evenly, Rule 11 determines that the excess literal `*` characters will appear outside of the emphasis, rather than inside it: -. +```````````````````````````````` example **foo* .

    *foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example *foo** .

    foo*

    -. +```````````````````````````````` -. + +```````````````````````````````` example ***foo** .

    *foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ****foo* .

    ***foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example **foo*** .

    foo*

    -. +```````````````````````````````` -. + +```````````````````````````````` example *foo**** .

    foo***

    -. +```````````````````````````````` + Rule 12: -. +```````````````````````````````` example foo ___ .

    foo ___

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo _\__ .

    foo _

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo _*_ .

    foo *

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo _____ .

    foo _____

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo __\___ .

    foo _

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo __*__ .

    foo *

    -. +```````````````````````````````` -. + +```````````````````````````````` example __foo_ .

    _foo

    -. +```````````````````````````````` + Note that when delimiters do not match evenly, Rule 12 determines that the excess literal `_` characters will appear outside of the emphasis, rather than inside it: -. +```````````````````````````````` example _foo__ .

    foo_

    -. +```````````````````````````````` -. + +```````````````````````````````` example ___foo__ .

    _foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ____foo_ .

    ___foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example __foo___ .

    foo_

    -. +```````````````````````````````` -. + +```````````````````````````````` example _foo____ .

    foo___

    -. +```````````````````````````````` + Rule 13 implies that if you want emphasis nested directly inside emphasis, you must use different delimiters: -. +```````````````````````````````` example **foo** .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example *_foo_* .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example __foo__ .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example _*foo*_ .

    foo

    -. +```````````````````````````````` + However, strong emphasis within strong emphasis is possible without switching delimiters: -. +```````````````````````````````` example ****foo**** .

    foo

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

    foo

    -. +```````````````````````````````` + Rule 13 can be applied to arbitrarily long sequences of delimiters: -. +```````````````````````````````` example ******foo****** .

    foo

    -. +```````````````````````````````` + Rule 14: -. +```````````````````````````````` example ***foo*** .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example _____foo_____ .

    foo

    -. +```````````````````````````````` + Rule 15: -. +```````````````````````````````` example *foo _bar* baz_ .

    foo _bar baz_

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

    foobar*

    -. +```````````````````````````````` -. + +```````````````````````````````` example *foo __bar *baz bim__ bam* .

    foo bar *baz bim bam

    -. +```````````````````````````````` + Rule 16: -. +```````````````````````````````` example **foo **bar baz** .

    **foo bar baz

    -. +```````````````````````````````` -. + +```````````````````````````````` example *foo *bar baz* .

    *foo bar baz

    -. +```````````````````````````````` + Rule 17: -. +```````````````````````````````` example *[bar*](/url) .

    *bar*

    -. +```````````````````````````````` -. + +```````````````````````````````` example _foo [bar_](/url) .

    _foo bar_

    -. +```````````````````````````````` -. + +```````````````````````````````` example * .

    *

    -. +```````````````````````````````` -. + +```````````````````````````````` example ** .

    **

    -. +```````````````````````````````` -. + +```````````````````````````````` example __ .

    __

    -. +```````````````````````````````` -. + +```````````````````````````````` example *a `*`* .

    a *

    -. +```````````````````````````````` -. + +```````````````````````````````` example _a `_`_ .

    a _

    -. +```````````````````````````````` -. + +```````````````````````````````` example **a .

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

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

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

    -. +```````````````````````````````` + ## Links A link contains [link text] (the visible text), a [link destination] (the URI that is the link destination), and optionally a [link title]. -There are two basic kinds of links in Markdown. In [inline link]s the +There are two basic kinds of links in Markdown. In [inline links] the destination and title are given immediately after the link text. In -[reference link]s the destination and title are defined elsewhere in +[reference links] the destination and title are defined elsewhere in the document. -A [link text](@link-text) consists of a sequence of zero or more +A [link text](@) consists of a sequence of zero or more inline elements enclosed by square brackets (`[` and `]`). The following rules apply: @@ -6460,7 +7004,7 @@ following rules apply: with an open bracket `[`, a sequence of zero or more inlines, and a close bracket `]`. -- Backtick [code span]s, [autolink]s, and raw [HTML tag]s bind more tightly +- Backtick [code spans], [autolinks], and raw [HTML tags] bind more tightly than the brackets in link text. Thus, for example, `` [foo`]` `` could not be a link text, since the second `]` is part of a code span. @@ -6468,11 +7012,11 @@ following rules apply: - The brackets in link text bind more tightly than markers for [emphasis and strong emphasis]. Thus, for example, `*[foo*](url)` is a link. -A [link destination](@link-destination) consists of either +A [link destination](@) consists of either - a sequence of zero or more characters between an opening `<` and a - closing `>` that contains no line breaks or unescaped `<` or `>` - characters, or + closing `>` that contains no spaces, line breaks, or unescaped + `<` or `>` characters, or - a nonempty sequence of characters that does not include ASCII space or control characters, and includes parentheses @@ -6480,7 +7024,7 @@ A [link destination](@link-destination) consists of either a balanced pair of unescaped parentheses that is not itself inside a balanced pair of unescaped parentheses. -A [link title](@link-title) consists of either +A [link title](@) consists of either - a sequence of zero or more characters between straight double-quote characters (`"`), including a `"` character only if it is @@ -6493,10 +7037,10 @@ A [link title](@link-title) consists of either - a sequence of zero or more characters between matching parentheses (`(...)`), including a `)` character only if it is backslash-escaped. -Although [link title]s may span multiple lines, they may not contain +Although [link titles] may span multiple lines, they may not contain a [blank line]. -An [inline link](@inline-link) consists of a [link text] followed immediately +An [inline link](@) consists of a [link text] followed immediately by a left parenthesis `(`, optional [whitespace], an optional [link destination], an optional [link title] separated from the link destination by [whitespace], optional [whitespace], and a right @@ -6510,108 +7054,125 @@ above. Here is a simple inline link: -. +```````````````````````````````` example [link](/uri "title") .

    link

    -. +```````````````````````````````` + The title may be omitted: -. +```````````````````````````````` example [link](/uri) .

    link

    -. +```````````````````````````````` + Both the title and the destination may be omitted: -. +```````````````````````````````` example [link]() .

    link

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link](<>) .

    link

    -. +```````````````````````````````` -If the destination contains spaces, it must be enclosed in pointy -braces: -. +The destination cannot contain spaces or line breaks, +even if enclosed in pointy brackets: + +```````````````````````````````` example [link](/my uri) .

    [link](/my uri)

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link]() . -

    link

    -. +

    [link](</my uri>)

    +```````````````````````````````` -The destination cannot contain line breaks, even with pointy braces: -. +```````````````````````````````` example [link](foo bar) .

    [link](foo bar)

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link]() .

    [link]()

    +```````````````````````````````` + +Parentheses inside the link destination may be escaped: + +```````````````````````````````` example +[link](\(foo\)) . +

    link

    +```````````````````````````````` One level of balanced parentheses is allowed without escaping: -. +```````````````````````````````` example [link]((foo)and(bar)) .

    link

    -. +```````````````````````````````` However, if you have parentheses within parentheses, you need to escape or use the `<...>` form: -. +```````````````````````````````` example [link](foo(and(bar))) .

    [link](foo(and(bar)))

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link](foo(and\(bar\))) .

    link

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link]() .

    link

    -. +```````````````````````````````` + Parentheses and other symbols can also be escaped, as usual in Markdown: -. +```````````````````````````````` example [link](foo\)\:) .

    link

    -. +```````````````````````````````` + A link can contain fragment identifiers and queries: -. +```````````````````````````````` example [link](#fragment) [link](http://example.com#fragment) @@ -6621,16 +7182,18 @@ A link can contain fragment identifiers and queries:

    link

    link

    link

    -. +```````````````````````````````` + Note that a backslash before a non-escapable character is just a backslash: -. +```````````````````````````````` example [link](foo\bar) .

    link

    -. +```````````````````````````````` + URL-escaping should be left alone inside the destination, as all URL-escaped characters are also valid URL characters. Entity and @@ -6641,25 +7204,27 @@ does not enforce any particular policy for rendering URLs in HTML or other formats. Renderers may make different decisions about how to escape or normalize URLs in the output. -. +```````````````````````````````` example [link](foo%20bä) .

    link

    -. +```````````````````````````````` + Note that, because titles can often be parsed as destinations, if you try to omit the destination and keep the title, you'll get unexpected results: -. +```````````````````````````````` example [link]("title") .

    link

    -. +```````````````````````````````` + Titles may be in single quotes, double quotes, or parentheses: -. +```````````````````````````````` example [link](/url "title") [link](/url 'title') [link](/url (title)) @@ -6667,32 +7232,36 @@ Titles may be in single quotes, double quotes, or parentheses:

    link link link

    -. +```````````````````````````````` + Backslash escapes and entity and numeric character references may be used in titles: -. +```````````````````````````````` example [link](/url "title \""") .

    link

    -. +```````````````````````````````` + Nested balanced quotes are not allowed without escaping: -. +```````````````````````````````` example [link](/url "title "and" title") .

    [link](/url "title "and" title")

    -. +```````````````````````````````` + But it is easy to work around this by using a different quote type: -. +```````````````````````````````` example [link](/url 'title "and" title') .

    link

    -. +```````````````````````````````` + (Note: `Markdown.pl` did allow double quotes inside a double-quoted title, and its test suite included a test demonstrating this. @@ -6711,144 +7280,161 @@ the same way in inline links and link reference definitions.) [Whitespace] is allowed around the destination and title: -. +```````````````````````````````` example [link]( /uri "title" ) .

    link

    -. +```````````````````````````````` + But it is not allowed between the link text and the following parenthesis: -. +```````````````````````````````` example [link] (/uri) .

    [link] (/uri)

    -. +```````````````````````````````` + The link text may contain balanced brackets, but not unbalanced ones, unless they are escaped: -. +```````````````````````````````` example [link [foo [bar]]](/uri) .

    link [foo [bar]]

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link] bar](/uri) .

    [link] bar](/uri)

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link [bar](/uri) .

    [link bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link \[bar](/uri) .

    link [bar

    -. +```````````````````````````````` + The link text may contain inline content: -. +```````````````````````````````` example [link *foo **bar** `#`*](/uri) .

    link foo bar #

    -. +```````````````````````````````` -. + +```````````````````````````````` example [![moon](moon.jpg)](/uri) .

    moon

    -. +```````````````````````````````` + However, links may not contain other links, at any level of nesting. -. +```````````````````````````````` example [foo [bar](/uri)](/uri) .

    [foo bar](/uri)

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo *[bar [baz](/uri)](/uri)*](/uri) .

    [foo [bar baz](/uri)](/uri)

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![[[foo](uri1)](uri2)](uri3) .

    [foo](uri2)

    -. +```````````````````````````````` + These cases illustrate the precedence of link text grouping over emphasis grouping: -. +```````````````````````````````` example *[foo*](/uri) .

    *foo*

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo *bar](baz*) .

    foo *bar

    -. +```````````````````````````````` + Note that brackets that *aren't* part of links do not take precedence: -. +```````````````````````````````` example *foo [bar* baz] .

    foo [bar baz]

    -. +```````````````````````````````` + These cases illustrate the precedence of HTML tags, code spans, and autolinks over link grouping: -. +```````````````````````````````` example [foo .

    [foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo`](/uri)` .

    [foo](/uri)

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo .

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

    -. +```````````````````````````````` -There are three kinds of [reference link](@reference-link)s: + +There are three kinds of [reference link](@)s: [full](#full-reference-link), [collapsed](#collapsed-reference-link), and [shortcut](#shortcut-reference-link). -A [full reference link](@full-reference-link) +A [full reference link](@) consists of a [link text] immediately followed by a [link label] that [matches] a [link reference definition] elsewhere in the document. -A [link label](@link-label) begins with a left bracket (`[`) and ends +A [link label](@) begins with a left bracket (`[`) and ends with the first right bracket (`]`) that is not backslash-escaped. Between these brackets there must be at least one [non-whitespace character]. Unescaped square bracket characters are not allowed in -[link label]s. A link label can have at most 999 +[link labels]. A link label can have at most 999 characters inside the square brackets. -One label [matches](@matches) +One label [matches](@) another just in case their normalized forms are equal. To normalize a label, perform the *Unicode case fold* and collapse consecutive internal [whitespace] to a single space. If there are multiple @@ -6861,165 +7447,181 @@ matching [link reference definition]. Here is a simple example: -. +```````````````````````````````` example [foo][bar] [bar]: /url "title" .

    foo

    -. +```````````````````````````````` + The rules for the [link text] are the same as with -[inline link]s. Thus: +[inline links]. Thus: The link text may contain balanced brackets, but not unbalanced ones, unless they are escaped: -. +```````````````````````````````` example [link [foo [bar]]][ref] [ref]: /uri .

    link [foo [bar]]

    -. +```````````````````````````````` -. + +```````````````````````````````` example [link \[bar][ref] [ref]: /uri .

    link [bar

    -. +```````````````````````````````` + The link text may contain inline content: -. +```````````````````````````````` example [link *foo **bar** `#`*][ref] [ref]: /uri .

    link foo bar #

    -. +```````````````````````````````` -. + +```````````````````````````````` example [![moon](moon.jpg)][ref] [ref]: /uri .

    moon

    -. +```````````````````````````````` + However, links may not contain other links, at any level of nesting. -. +```````````````````````````````` example [foo [bar](/uri)][ref] [ref]: /uri .

    [foo bar]ref

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo *bar [baz][ref]*][ref] [ref]: /uri .

    [foo bar baz]ref

    -. +```````````````````````````````` + -(In the examples above, we have two [shortcut reference link]s +(In the examples above, we have two [shortcut reference links] instead of one [full reference link].) The following cases illustrate the precedence of link text grouping over emphasis grouping: -. +```````````````````````````````` example *[foo*][ref] [ref]: /uri .

    *foo*

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo *bar][ref] [ref]: /uri .

    foo *bar

    -. +```````````````````````````````` + These cases illustrate the precedence of HTML tags, code spans, and autolinks over link grouping: -. +```````````````````````````````` example [foo [ref]: /uri .

    [foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo`][ref]` [ref]: /uri .

    [foo][ref]

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo [ref]: /uri .

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

    -. +```````````````````````````````` + Matching is case-insensitive: -. +```````````````````````````````` example [foo][BaR] [bar]: /url "title" .

    foo

    -. +```````````````````````````````` + Unicode case fold is used: -. +```````````````````````````````` example [Толпой][Толпой] is a Russian word. [ТОЛПОЙ]: /url .

    Толпой is a Russian word.

    -. +```````````````````````````````` + Consecutive internal [whitespace] is treated as one space for purposes of determining matching: -. +```````````````````````````````` example [Foo bar]: /url [Baz][Foo bar] .

    Baz

    -. +```````````````````````````````` + No [whitespace] is allowed between the [link text] and the [link label]: -. +```````````````````````````````` example [foo] [bar] [bar]: /url "title" .

    [foo] bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo] [bar] @@ -7027,15 +7629,16 @@ No [whitespace] is allowed between the [link text] and the .

    [foo] bar

    -. +```````````````````````````````` + This is a departure from John Gruber's original Markdown syntax description, which explicitly allows whitespace between the link text and the link label. It brings reference links in line with -[inline link]s, which (according to both original Markdown and +[inline links], which (according to both original Markdown and this spec) cannot have whitespace after the link text. More importantly, it prevents inadvertent capture of consecutive -[shortcut reference link]s. If whitespace is allowed between the +[shortcut reference links]. If whitespace is allowed between the link text and the link label, then in the following we will have a single reference link, not two shortcut reference links, as intended: @@ -7048,7 +7651,7 @@ intended: [bar]: /url2 ``` -(Note that [shortcut reference link]s were introduced by Gruber +(Note that [shortcut reference links] were introduced by Gruber himself in a beta version of `Markdown.pl`, but never included in the official syntax description. Without shortcut reference links, it is harmless to allow space between the link text and @@ -7056,10 +7659,10 @@ link label; but once shortcut references are introduced, it is too dangerous to allow this, as it frequently leads to unintended results.) -When there are multiple matching [link reference definition]s, +When there are multiple matching [link reference definitions], the first is used: -. +```````````````````````````````` example [foo]: /url1 [foo]: /url2 @@ -7067,80 +7670,88 @@ the first is used: [bar][foo] .

    bar

    -. +```````````````````````````````` + Note that matching is performed on normalized strings, not parsed inline content. So the following does not match, even though the labels define equivalent inline content: -. +```````````````````````````````` example [bar][foo\!] [foo!]: /url .

    [bar][foo!]

    -. +```````````````````````````````` -[Link label]s cannot contain brackets, unless they are + +[Link labels] cannot contain brackets, unless they are backslash-escaped: -. +```````````````````````````````` example [foo][ref[] [ref[]: /uri .

    [foo][ref[]

    [ref[]: /uri

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo][ref[bar]] [ref[bar]]: /uri .

    [foo][ref[bar]]

    [ref[bar]]: /uri

    -. +```````````````````````````````` -. + +```````````````````````````````` example [[[foo]]] [[[foo]]]: /url .

    [[[foo]]]

    [[[foo]]]: /url

    -. +```````````````````````````````` -. + +```````````````````````````````` example [foo][ref\[] [ref\[]: /uri .

    foo

    -. +```````````````````````````````` + Note that in this example `]` is not backslash-escaped: -. +```````````````````````````````` example [bar\\]: /uri [bar\\] .

    bar\

    -. +```````````````````````````````` + A [link label] must contain at least one [non-whitespace character]: -. +```````````````````````````````` example [] []: /uri .

    []

    []: /uri

    -. +```````````````````````````````` -. + +```````````````````````````````` example [ ] @@ -7151,9 +7762,10 @@ A [link label] must contain at least one [non-whitespace character]: ]

    [ ]: /uri

    -. +```````````````````````````````` -A [collapsed reference link](@collapsed-reference-link) + +A [collapsed reference link](@) consists of a [link label] that [matches] a [link reference definition] elsewhere in the document, followed by the string `[]`. @@ -7162,37 +7774,40 @@ 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]`. -. +```````````````````````````````` example [foo][] [foo]: /url "title" .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example [*foo* bar][] [*foo* bar]: /url "title" .

    foo bar

    -. +```````````````````````````````` + The link labels are case-insensitive: -. +```````````````````````````````` example [Foo][] [foo]: /url "title" .

    Foo

    -. +```````````````````````````````` + As with full reference links, [whitespace] is not allowed between the two sets of brackets: -. +```````````````````````````````` example [foo] [] @@ -7200,9 +7815,10 @@ allowed between the two sets of brackets: .

    foo []

    -. +```````````````````````````````` + -A [shortcut reference link](@shortcut-reference-link) +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. @@ -7211,132 +7827,144 @@ 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][]`. -. +```````````````````````````````` example [foo] [foo]: /url "title" .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example [*foo* bar] [*foo* bar]: /url "title" .

    foo bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example [[*foo* bar]] [*foo* bar]: /url "title" .

    [foo bar]

    -. +```````````````````````````````` -. + +```````````````````````````````` example [[bar [foo] [foo]: /url .

    [[bar foo

    -. +```````````````````````````````` + The link labels are case-insensitive: -. +```````````````````````````````` example [Foo] [foo]: /url "title" .

    Foo

    -. +```````````````````````````````` + A space after the link text should be preserved: -. +```````````````````````````````` example [foo] bar [foo]: /url .

    foo bar

    -. +```````````````````````````````` + If you just want bracketed text, you can backslash-escape the opening bracket to avoid links: -. +```````````````````````````````` example \[foo] [foo]: /url "title" .

    [foo]

    -. +```````````````````````````````` + Note that this is a link, because a link label ends with the first following closing bracket: -. +```````````````````````````````` example [foo*]: /url *[foo*] .

    *foo*

    -. +```````````````````````````````` + Full references take precedence over shortcut references: -. +```````````````````````````````` example [foo][bar] [foo]: /url1 [bar]: /url2 .

    foo

    -. +```````````````````````````````` + In the following case `[bar][baz]` is parsed as a reference, `[foo]` as normal text: -. +```````````````````````````````` example [foo][bar][baz] [baz]: /url .

    [foo]bar

    -. +```````````````````````````````` + Here, though, `[foo][bar]` is parsed as a reference, since `[bar]` is defined: -. +```````````````````````````````` example [foo][bar][baz] [baz]: /url1 [bar]: /url2 .

    foobaz

    -. +```````````````````````````````` + Here `[foo]` is not parsed as a shortcut reference, because it is followed by a link label (even though `[bar]` is not defined): -. +```````````````````````````````` example [foo][bar][baz] [baz]: /url1 [foo]: /url2 .

    [foo]bar

    -. +```````````````````````````````` + ## Images Syntax for images is like the syntax for links, with one difference. Instead of [link text], we have an -[image description](@image-description). The rules for this are the +[image description](@). The rules for this are the same as for [link text], except that (a) an image description starts with `![` rather than `[`, and (b) an image description may contain links. @@ -7344,31 +7972,35 @@ An image description has inline elements as its contents. When an image is rendered to HTML, this is standardly used as the image's `alt` attribute. -. +```````````````````````````````` example ![foo](/url "title") .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![foo *bar*] [foo *bar*]: train.jpg "train & tracks" .

    foo bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![foo ![bar](/url)](/url2) .

    foo bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![foo [bar](/url)](/url2) .

    foo bar

    -. +```````````````````````````````` + Though this spec is concerned with parsing, not rendering, it is recommended that in rendering to HTML, only the plain string content @@ -7377,96 +8009,107 @@ the above example, the alt attribute's value is `foo bar`, not `foo [bar](/url)` or `foo bar`. Only the plain string content is rendered, without formatting. -. +```````````````````````````````` example ![foo *bar*][] [foo *bar*]: train.jpg "train & tracks" .

    foo bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![foo *bar*][foobar] [FOOBAR]: train.jpg "train & tracks" .

    foo bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![foo](train.jpg) .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example My ![foo bar](/path/to/train.jpg "title" ) .

    My foo bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![foo]() .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![](/url) .

    -. +```````````````````````````````` + Reference-style: -. +```````````````````````````````` example ![foo][bar] [bar]: /url .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![foo][bar] [BAR]: /url .

    foo

    -. +```````````````````````````````` + Collapsed: -. +```````````````````````````````` example ![foo][] [foo]: /url "title" .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![*foo* bar][] [*foo* bar]: /url "title" .

    foo bar

    -. +```````````````````````````````` + The labels are case-insensitive: -. +```````````````````````````````` example ![Foo][] [foo]: /url "title" .

    Foo

    -. +```````````````````````````````` + As with reference links, [whitespace] is not allowed between the two sets of brackets: -. +```````````````````````````````` example ![foo] [] @@ -7474,162 +8117,187 @@ between the two sets of brackets: .

    foo []

    -. +```````````````````````````````` + Shortcut: -. +```````````````````````````````` example ![foo] [foo]: /url "title" .

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ![*foo* bar] [*foo* bar]: /url "title" .

    foo bar

    -. +```````````````````````````````` + Note that link labels cannot contain unescaped brackets: -. +```````````````````````````````` example ![[foo]] [[foo]]: /url "title" .

    ![[foo]]

    [[foo]]: /url "title"

    -. +```````````````````````````````` + The link labels are case-insensitive: -. +```````````````````````````````` example ![Foo] [foo]: /url "title" .

    Foo

    -. +```````````````````````````````` + If you just want bracketed text, you can backslash-escape the opening `!` and `[`: -. +```````````````````````````````` example \!\[foo] [foo]: /url "title" .

    ![foo]

    -. +```````````````````````````````` + If you want a link after a literal `!`, backslash-escape the `!`: -. +```````````````````````````````` example \![foo] [foo]: /url "title" .

    !foo

    -. +```````````````````````````````` + ## Autolinks -[Autolink](@autolink)s are absolute URIs and email addresses inside +[Autolink](@)s are absolute URIs and email addresses inside `<` and `>`. They are parsed as links, with the URL or email address as the link label. -A [URI autolink](@uri-autolink) consists of `<`, followed by an +A [URI autolink](@) consists of `<`, followed by an [absolute URI] not containing `<`, followed by `>`. It is parsed as a link to the URI, with the URI as the link's label. -An [absolute URI](@absolute-uri), +An [absolute URI](@), for these purposes, consists of a [scheme] followed by a colon (`:`) followed by zero or more characters other than ASCII [whitespace] and control characters, `<`, and `>`. If -the URI includes these characters, you must use percent-encoding +the URI includes these characters, they must be percent-encoded (e.g. `%20` for a space). -The following [schemes](@scheme) -are recognized (case-insensitive): -`coap`, `doi`, `javascript`, `aaa`, `aaas`, `about`, `acap`, `cap`, -`cid`, `crid`, `data`, `dav`, `dict`, `dns`, `file`, `ftp`, `geo`, `go`, -`gopher`, `h323`, `http`, `https`, `iax`, `icap`, `im`, `imap`, `info`, -`ipp`, `iris`, `iris.beep`, `iris.xpc`, `iris.xpcs`, `iris.lwz`, `ldap`, -`mailto`, `mid`, `msrp`, `msrps`, `mtqp`, `mupdate`, `news`, `nfs`, -`ni`, `nih`, `nntp`, `opaquelocktoken`, `pop`, `pres`, `rtsp`, -`service`, `session`, `shttp`, `sieve`, `sip`, `sips`, `sms`, `snmp`,` -soap.beep`, `soap.beeps`, `tag`, `tel`, `telnet`, `tftp`, `thismessage`, -`tn3270`, `tip`, `tv`, `urn`, `vemmi`, `ws`, `wss`, `xcon`, -`xcon-userid`, `xmlrpc.beep`, `xmlrpc.beeps`, `xmpp`, `z39.50r`, -`z39.50s`, `adiumxtra`, `afp`, `afs`, `aim`, `apt`,` attachment`, `aw`, -`beshare`, `bitcoin`, `bolo`, `callto`, `chrome`,` chrome-extension`, -`com-eventbrite-attendee`, `content`, `cvs`,` dlna-playsingle`, -`dlna-playcontainer`, `dtn`, `dvb`, `ed2k`, `facetime`, `feed`, -`finger`, `fish`, `gg`, `git`, `gizmoproject`, `gtalk`, `hcp`, `icon`, -`ipn`, `irc`, `irc6`, `ircs`, `itms`, `jar`, `jms`, `keyparc`, `lastfm`, -`ldaps`, `magnet`, `maps`, `market`,` message`, `mms`, `ms-help`, -`msnim`, `mumble`, `mvn`, `notes`, `oid`, `palm`, `paparazzi`, -`platform`, `proxy`, `psyc`, `query`, `res`, `resource`, `rmi`, `rsync`, -`rtmp`, `secondlife`, `sftp`, `sgn`, `skype`, `smb`, `soldat`, -`spotify`, `ssh`, `steam`, `svn`, `teamspeak`, `things`, `udp`, -`unreal`, `ut2004`, `ventrilo`, `view-source`, `webcal`, `wtai`, -`wyciwyg`, `xfire`, `xri`, `ymsgr`. +For purposes of this spec, a [scheme](@) is any sequence +of 2--32 characters beginning with an ASCII letter and followed +by any combination of ASCII letters, digits, or the symbols plus +("+"), period ("."), or hyphen ("-"). Here are some valid autolinks: -. +```````````````````````````````` example .

    http://foo.bar.baz

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

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

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

    irc://foo.bar:2233/baz

    -. +```````````````````````````````` + Uppercase is also fine: -. +```````````````````````````````` example .

    MAILTO:FOO@BAR.BAZ

    +```````````````````````````````` + + +Note that many strings that count as [absolute URIs] for +purposes of this spec are not valid URIs, because their +schemes are not registered or because of other problems +with their syntax: + +```````````````````````````````` example + . +

    a+b+c:d

    +```````````````````````````````` -Spaces are not allowed in autolinks: +```````````````````````````````` example + +. +

    made-up-scheme://foo,bar

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    http://../

    +```````````````````````````````` + + +```````````````````````````````` example + . +

    localhost:5001/foo

    +```````````````````````````````` + + +Spaces are not allowed in autolinks: + +```````````````````````````````` example .

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

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

    http://example.com/\[\

    -. +```````````````````````````````` -An [email autolink](@email-autolink) + +An [email autolink](@) consists of `<`, followed by an [email address], followed by `>`. The link's label is the email address, and the URL is `mailto:` followed by the email address. -An [email address](@email-address), +An [email address](@), for these purposes, is anything that matches the [non-normative regex from the HTML5 spec](https://html.spec.whatwg.org/multipage/forms.html#e-mail-state-(type=email)): @@ -7639,69 +8307,72 @@ spec](https://html.spec.whatwg.org/multipage/forms.html#e-mail-state-(type=email Examples of email autolinks: -. +```````````````````````````````` example .

    foo@bar.example.com

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

    foo+special@Bar.baz-bar0.com

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

    <foo+@bar.example.com>

    -. +```````````````````````````````` + These are not autolinks: -. +```````````````````````````````` example <> .

    <>

    -. +```````````````````````````````` -. - -. -

    <heck://bing.bong>

    -. -. +```````````````````````````````` example < http://foo.bar > .

    < http://foo.bar >

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

    <m:abc>

    +```````````````````````````````` + + +```````````````````````````````` example .

    <foo.bar.baz>

    -. +```````````````````````````````` -. - -. -

    <localhost:5001/foo>

    -. -. +```````````````````````````````` example http://example.com .

    http://example.com

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo@bar.example.com .

    foo@bar.example.com

    -. +```````````````````````````````` + ## Raw HTML @@ -7712,380 +8383,416 @@ so custom tags (and even, say, DocBook tags) may be used. Here is the grammar for tags: -A [tag name](@tag-name) consists of an ASCII letter +A [tag name](@) consists of an ASCII letter followed by zero or more ASCII letters, digits, or hyphens (`-`). -An [attribute](@attribute) consists of [whitespace], +An [attribute](@) consists of [whitespace], an [attribute name], and an optional [attribute value specification]. -An [attribute name](@attribute-name) +An [attribute name](@) consists of an ASCII letter, `_`, or `:`, followed by zero or more ASCII letters, digits, `_`, `.`, `:`, or `-`. (Note: This is the XML specification restricted to ASCII. HTML5 is laxer.) -An [attribute value specification](@attribute-value-specification) +An [attribute value specification](@) consists of optional [whitespace], a `=` character, optional [whitespace], and an [attribute value]. -An [attribute value](@attribute-value) +An [attribute value](@) consists of an [unquoted attribute value], a [single-quoted attribute value], or a [double-quoted attribute value]. -An [unquoted attribute value](@unquoted-attribute-value) +An [unquoted attribute value](@) is a nonempty string of characters not including spaces, `"`, `'`, `=`, `<`, `>`, or `` ` ``. -A [single-quoted attribute value](@single-quoted-attribute-value) +A [single-quoted attribute value](@) consists of `'`, zero or more characters not including `'`, and a final `'`. -A [double-quoted attribute value](@double-quoted-attribute-value) +A [double-quoted attribute value](@) consists of `"`, zero or more characters not including `"`, and a final `"`. -An [open tag](@open-tag) consists of a `<` character, a [tag name], -zero or more [attribute]s, optional [whitespace], an optional `/` +An [open tag](@) consists of a `<` character, a [tag name], +zero or more [attributes], optional [whitespace], an optional `/` character, and a `>` character. -A [closing tag](@closing-tag) consists of the string ``. -An [HTML comment](@html-comment) consists of ``, +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).) -A [processing instruction](@processing-instruction) +A [processing instruction](@) consists of the string ``, and the string `?>`. -A [declaration](@declaration) consists of the +A [declaration](@) consists of the string ``, and the character `>`. -A [CDATA section](@cdata-section) consists of +A [CDATA section](@) consists of the string ``, and the string `]]>`. -An [HTML tag](@html-tag) consists of an [open tag], a [closing tag], +An [HTML tag](@) consists of an [open tag], a [closing tag], an [HTML comment], a [processing instruction], a [declaration], or a [CDATA section]. Here are some simple open tags: -. +```````````````````````````````` example .

    -. +```````````````````````````````` + Empty elements: -. +```````````````````````````````` example .

    -. +```````````````````````````````` + [Whitespace] is allowed: -. +```````````````````````````````` example .

    -. +```````````````````````````````` + With attributes: -. +```````````````````````````````` example .

    -. +```````````````````````````````` + Custom tag names can be used: -. +```````````````````````````````` example Foo .

    Foo

    -. +```````````````````````````````` + Illegal tag names, not parsed as HTML: -. +```````````````````````````````` example <33> <__> .

    <33> <__>

    -. +```````````````````````````````` + Illegal attribute names: -. +```````````````````````````````` example
    .

    <a h*#ref="hi">

    -. +```````````````````````````````` + Illegal attribute values: -. +```````````````````````````````` example
    .

    </a href="foo">

    -. +```````````````````````````````` + Comments: -. +```````````````````````````````` example foo .

    foo

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

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

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

    foo <!--> foo -->

    foo <!-- foo--->

    -. +```````````````````````````````` + Processing instructions: -. +```````````````````````````````` example foo .

    foo

    -. +```````````````````````````````` + Declarations: -. +```````````````````````````````` example foo .

    foo

    -. +```````````````````````````````` + CDATA sections: -. +```````````````````````````````` example foo &<]]> .

    foo &<]]>

    -. +```````````````````````````````` + Entity and numeric character references are preserved in HTML attributes: -. +```````````````````````````````` example foo
    .

    foo

    -. +```````````````````````````````` + Backslash escapes do not work in HTML attributes: -. +```````````````````````````````` example foo .

    foo

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

    <a href=""">

    -. +```````````````````````````````` + ## Hard line breaks A line break (not in a code span or HTML tag) that is preceded by two or more spaces and does not occur at the end of a block -is parsed as a [hard line break](@hard-line-break) (rendered +is parsed as a [hard line break](@) (rendered in HTML as a `
    ` tag): -. +```````````````````````````````` example foo baz .

    foo
    baz

    -. +```````````````````````````````` + For a more visible alternative, a backslash before the [line ending] may be used instead of two spaces: -. +```````````````````````````````` example foo\ baz .

    foo
    baz

    -. +```````````````````````````````` + More than two spaces can be used: -. +```````````````````````````````` example foo baz .

    foo
    baz

    -. +```````````````````````````````` + Leading spaces at the beginning of the next line are ignored: -. +```````````````````````````````` example foo bar .

    foo
    bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example foo\ bar .

    foo
    bar

    -. +```````````````````````````````` + Line breaks can occur inside emphasis, links, and other constructs that allow inline content: -. +```````````````````````````````` example *foo bar* .

    foo
    bar

    -. +```````````````````````````````` -. + +```````````````````````````````` example *foo\ bar* .

    foo
    bar

    -. +```````````````````````````````` + Line breaks do not occur inside code spans -. +```````````````````````````````` example `code span` .

    code span

    -. +```````````````````````````````` -. + +```````````````````````````````` example `code\ span` .

    code\ span

    -. +```````````````````````````````` + or HTML tags: -. +```````````````````````````````` example
    .

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

    -. +```````````````````````````````` + Hard line breaks are for separating inline content within a block. Neither syntax for hard line breaks works at the end of a paragraph or other block element: -. +```````````````````````````````` example foo\ .

    foo\

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

    foo

    -. +```````````````````````````````` -. + +```````````````````````````````` example ### foo\ .

    foo\

    -. +```````````````````````````````` -. + +```````````````````````````````` example ### foo .

    foo

    -. +```````````````````````````````` + ## Soft line breaks @@ -8095,24 +8802,26 @@ softbreak. (A softbreak may be rendered in HTML either as a [line ending] or as a space. The result will be the same in browsers. In the examples here, a [line ending] will be used.) -. +```````````````````````````````` example foo baz .

    foo baz

    -. +```````````````````````````````` + Spaces at the end of the line and beginning of the next line are removed: -. +```````````````````````````````` example foo baz .

    foo baz

    -. +```````````````````````````````` + A conforming parser may render a soft line break in HTML either as a line break or as a space. @@ -8125,34 +8834,37 @@ as hard line breaks. Any characters not given an interpretation by the above rules will be parsed as plain textual content. -. +```````````````````````````````` example hello $.;'there .

    hello $.;'there

    -. +```````````````````````````````` -. + +```````````````````````````````` example Foo χρῆν .

    Foo χρῆν

    -. +```````````````````````````````` + Internal spaces are preserved verbatim: -. +```````````````````````````````` example Multiple spaces .

    Multiple spaces

    -. +```````````````````````````````` + -# Appendix: A parsing strategy {-} +# Appendix: A parsing strategy In this appendix we describe some features of the parsing strategy used in the CommonMark reference implementations. -## Overview {-} +## Overview Parsing has two phases: @@ -8190,7 +8902,7 @@ marked by arrows: "aliquando id" ``` -## Phase 1: block structure {-} +## Phase 1: block structure Each line that is processed has an effect on this tree. The line is analyzed and, depending on its contents, the document may be altered @@ -8227,8 +8939,8 @@ markers like `>`, list markers, and indentation have been consumed). This is text that can be incorporated into the last open block (a paragraph, code block, heading, or raw HTML). -Setext headings are formed when we detect that the second line of -a paragraph is a setext heading line. +Setext headings are formed when we see a line of a paragraph +that is a setext heading line. Reference link definitions are detected when a paragraph is closed; the accumulated text lines are parsed to see if they begin with @@ -8332,7 +9044,7 @@ We thus obtain the final tree: "aliquando id" ``` -## Phase 2: inline structure {-} +## Phase 2: inline structure Once all of the input has been parsed, all open blocks are closed. @@ -8363,7 +9075,7 @@ Notice how the [line ending] in the first paragraph has been parsed as a `softbreak`, and the asterisks in the first list item have become an `emph`. -### An algorithm for parsing nested emphasis and links {-} +### An algorithm for parsing nested emphasis and links By far the trickiest part of inline parsing is handling emphasis, strong emphasis, links, and images. This is done using the following @@ -8375,7 +9087,7 @@ When we're parsing inlines and we hit either - a `[` or `![` we insert a text node with these symbols as its literal content, and we -add a pointer to this text node to the [delimiter stack](@delimiter-stack). +add a pointer to this text node to the [delimiter stack](@). The [delimiter stack] is a doubly linked list. Each element contains a pointer to a text node, plus information about @@ -8393,7 +9105,7 @@ procedure (see below). When we hit the end of the input, we call the *process emphasis* procedure (see below), with `stack_bottom` = NULL. -#### *look for link or image* {-} +#### *look for link or image* Starting at the top of the delimiter stack, we look backwards through the stack for an opening `[` or `![` delimiter. @@ -8424,7 +9136,7 @@ through the stack for an opening `[` or `![` delimiter. `[` delimiters before the opening delimiter to *inactive*. (This will prevent us from getting links within links.) -#### *process emphasis* {-} +#### *process emphasis* Parameter `stack_bottom` sets a lower bound to how far we descend in the [delimiter stack]. If it is NULL, we can From 265b4ac9457a4dd7d35881c67f758c65f4b5306b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 18 Jan 2016 11:37:12 +1100 Subject: [PATCH 055/815] README: Bump version to 0.3.2 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 958cad28d..46d2c816f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.3.0 + 0.3.2 ``` @@ -105,7 +105,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.3.0 + 0.3.2 ``` From 2ba2a9bc12661a40fdfde3eb71e644f920499cd6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 18 Jan 2016 12:40:55 +1100 Subject: [PATCH 056/815] [maven-release-plugin] prepare release commonmark-parent-0.4.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 858393434..4b72bd161 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.3-SNAPSHOT + 0.4.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index ba097614f..b1c93d3fa 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.3-SNAPSHOT + 0.4.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 3e6fb18b9..706bbc899 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.3-SNAPSHOT + 0.4.0 commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 9c51db5e5..533b31fa6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.3-SNAPSHOT + 0.4.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index ae1f74c9b..78df8c40e 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.3-SNAPSHOT + 0.4.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 6f1658257..d702455c1 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.3-SNAPSHOT + 0.4.0 commonmark diff --git a/pom.xml b/pom.xml index e9fc02299..6701e9c8d 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.3.3-SNAPSHOT + 0.4.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.3.3-SNAPSHOT + 0.4.0 com.atlassian.commonmark commonmark-ext-autolink - 0.3.3-SNAPSHOT + 0.4.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.3.3-SNAPSHOT + 0.4.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.3.3-SNAPSHOT + 0.4.0 com.atlassian.commonmark commonmark-test-util - 0.3.3-SNAPSHOT + 0.4.0 @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.4.0 From d71cc4644d9dcd8058a97ee4475349e2ede74709 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 18 Jan 2016 12:40:55 +1100 Subject: [PATCH 057/815] [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-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 4b72bd161..515336b4c 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.0 + 0.4.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index b1c93d3fa..3c8f0d3d9 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.0 + 0.4.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 706bbc899..8ca06e28f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.0 + 0.4.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 533b31fa6..86679f478 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.0 + 0.4.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 78df8c40e..e2b7a6d3f 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.0 + 0.4.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index d702455c1..9b7662e7a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.0 + 0.4.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 6701e9c8d..c484d43c6 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.0 + 0.4.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.4.0 + 0.4.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.4.0 + 0.4.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.4.0 + 0.4.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.4.0 + 0.4.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.4.0 + 0.4.1-SNAPSHOT @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.4.0 + HEAD From cdac54af637efcbbd006ed3bae9bffdc3571eb65 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 18 Jan 2016 16:30:48 +1100 Subject: [PATCH 058/815] README: Bump version to 0.4.0 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 46d2c816f..84afd24ab 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.3.2 + 0.4.0 ``` @@ -105,7 +105,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.3.2 + 0.4.0 ``` From b954f82cb582e619f08fb58345c31a635b31d5f1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 20 Jan 2016 20:20:23 +1100 Subject: [PATCH 059/815] Fix definition of WHITESPACE_CHAR Spec: http://spec.commonmark.org/0.24/#unicode-whitespace-character --- .../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 13fe220ce..6f0511aae 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -64,7 +64,7 @@ public class InlineParserImpl implements InlineParser { private static final Pattern SPNL = Pattern.compile("^ *(?:\n *)?"); - private static final Pattern WHITESPACE_CHAR = Pattern.compile("^\\p{IsWhite_Space}"); + private static final Pattern UNICODE_WHITESPACE_CHAR = Pattern.compile("^[\\p{Zs}\t\r\n\f]"); private static final Pattern WHITESPACE = Pattern.compile("\\s+"); @@ -566,7 +566,7 @@ private boolean parseCloseBracket() { if ((dest = parseLinkDestination()) != null) { spnl(); // title needs a whitespace before - if (WHITESPACE_CHAR.matcher(input.substring(index - 1, index)).matches()) { + if (WHITESPACE.matcher(input.substring(index - 1, index)).matches()) { title = parseLinkTitle(); spnl(); } @@ -785,8 +785,8 @@ private DelimiterRun scanDelims(DelimiterProcessor inlineDelimiter) { String.valueOf(charAfter); boolean beforeIsPunctuation = PUNCTUATION.matcher(before).matches(); - boolean beforeIsWhitespace = WHITESPACE_CHAR.matcher(before).matches(); - boolean afterIsWhitespace = WHITESPACE_CHAR.matcher(after).matches(); + boolean beforeIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(before).matches(); + boolean afterIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(after).matches(); boolean afterIsPunctuation = PUNCTUATION.matcher(after).matches(); boolean leftFlanking = !afterIsWhitespace && From 52f7e079a2db12d96463802f5fd3cb54ffa46521 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 20 Jan 2016 22:08:57 +1100 Subject: [PATCH 060/815] ext-autolink: Update to autolink 0.3.0 This stops recognizing "abc://foo" within "1abc://foo" as a link. --- 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 515336b4c..e119cf9c9 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -19,7 +19,7 @@ org.nibor.autolink autolink - 0.2.0 + 0.3.0 From 6fbb403d24f6c08f1af9e385df81dd2d78dc6a40 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Mon, 25 Jan 2016 20:24:23 +0300 Subject: [PATCH 061/815] Remove usage of class Objects It's possible to use this class on Android only starting with 19 API level. --- .../java/org/commonmark/internal/ListBlockParser.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index ed069044e..29ee8244b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -3,7 +3,6 @@ import org.commonmark.node.*; import org.commonmark.parser.block.*; -import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -84,13 +83,17 @@ private static ListData parseListMarker(CharSequence ln, int offset) { */ private static boolean listsMatch(ListBlock a, ListBlock b) { if (a instanceof BulletList && b instanceof BulletList) { - return Objects.equals(((BulletList) a).getBulletMarker(), ((BulletList) b).getBulletMarker()); + return equals(((BulletList) a).getBulletMarker(), ((BulletList) b).getBulletMarker()); } else if (a instanceof OrderedList && b instanceof OrderedList) { - return Objects.equals(((OrderedList) a).getDelimiter(), ((OrderedList) b).getDelimiter()); + return equals(((OrderedList) a).getDelimiter(), ((OrderedList) b).getDelimiter()); } 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 From 592f2230ad029e4d46a9a4bf7258fa0084656d0f Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Mon, 25 Jan 2016 21:07:52 +0300 Subject: [PATCH 062/815] Remove usage of class StandardCharsets It's possible to use this class on Android only starting with 19 API level. --- .../src/main/java/org/commonmark/internal/util/Escaping.java | 4 ++-- .../java/org/commonmark/internal/util/Html5Entities.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java index cc10ec906..9136b56f8 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,6 @@ package org.commonmark.internal.util; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -78,7 +78,7 @@ public void replace(String input, StringBuilder sb) { sb.append(input, 1, input.length()); } } else { - byte[] bytes = input.getBytes(StandardCharsets.UTF_8); + byte[] bytes = input.getBytes(Charset.forName("UTF-8")); for (byte b : bytes) { sb.append('%'); sb.append(HEX_DIGITS[(b >> 4) & 0xF]); 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 3a6eac9cc..62f05448d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -43,7 +43,8 @@ public static String entityToString(String input) { private static Map readEntities() { Map entities = new HashMap<>(); InputStream stream = Html5Entities.class.getResourceAsStream("entities.properties"); - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + Charset charset = Charset.forName("UTF-8"); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, charset))) { String line; while ((line = bufferedReader.readLine()) != null) { if (line.length() == 0) { From 59fca77ba282710a6a579b102e258ddd0cec540d Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Mon, 25 Jan 2016 21:20:38 +0300 Subject: [PATCH 063/815] Set absolute path to avoid ProGuard problem --- .../main/java/org/commonmark/internal/util/Html5Entities.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 3a6eac9cc..07703cb1a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java @@ -14,6 +14,7 @@ public class Html5Entities { private static final Map NAMED_CHARACTER_REFERENCES = readEntities(); private static final Pattern NUMERIC_PATTERN = Pattern.compile("^&#[Xx]?"); + private static final String ENTITY_PATH = "/org/commonmark/internal/util/entities.properties"; public static String entityToString(String input) { Matcher matcher = NUMERIC_PATTERN.matcher(input); @@ -42,7 +43,7 @@ public static String entityToString(String input) { private static Map readEntities() { Map entities = new HashMap<>(); - InputStream stream = Html5Entities.class.getResourceAsStream("entities.properties"); + InputStream stream = Html5Entities.class.getResourceAsStream(ENTITY_PATH); try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { String line; while ((line = bufferedReader.readLine()) != null) { From 32cf0e881085e21a5979493dfacd471c20cbbd79 Mon Sep 17 00:00:00 2001 From: Prayag Verma Date: Tue, 26 Jan 2016 14:01:50 +0530 Subject: [PATCH 064/815] Update license year to range --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index b09e367ce..1e011418e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2015, Atlassian Pty Ltd +Copyright (c) 2015-2016, Atlassian Pty Ltd All rights reserved. Redistribution and use in source and binary forms, with or without From 330652c2f62c2704c73cef85db822c4c2d02ebca Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Tue, 22 Sep 2015 12:54:40 -0600 Subject: [PATCH 065/815] Adds support for introspection on the delimiter character and length for inline elements emphasis (italic), strong emphasis (bold), and strikethrough nodes. New interface 'Delimited' can be implemented by other custom node classes with similar behaviour. --- .../ext/gfm/strikethrough/Strikethrough.java | 22 +++++- .../StrikethroughDelimiterProcessor.java | 2 +- .../inline/EmphasisDelimiterProcessor.java | 5 +- .../java/org/commonmark/node/Delimited.java | 9 +++ .../java/org/commonmark/node/Emphasis.java | 20 +++++- .../org/commonmark/node/StrongEmphasis.java | 21 +++++- .../org/commonmark/test/DelimitedTest.java | 69 +++++++++++++++++++ 7 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/node/Delimited.java create mode 100644 commonmark/src/test/java/org/commonmark/test/DelimitedTest.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 b4ecfce2f..1d89594de 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 @@ -1,9 +1,29 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.node.CustomNode; +import org.commonmark.node.Delimited; /** * A strikethrough node containing text and other inline nodes nodes as children. */ -public class Strikethrough extends CustomNode { +public class Strikethrough extends CustomNode implements Delimited { + + private final char delimiterChar; + private final int delimiterCount; + + public Strikethrough(char delimiterChar, int delimiterCount) { + this.delimiterChar = delimiterChar; + this.delimiterCount = delimiterCount; + } + + @Override + public char getDelimiterChar() { + return delimiterChar; + } + + @Override + public int getDelimiterCount() { + return delimiterCount; + } + } 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 14a847e2d..3e9b71efc 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 @@ -39,7 +39,7 @@ public void process(Text opener, Text closer, int delimiterCount) { } // Normal case, wrap nodes between delimiters in strikethrough. - Node strikethrough = new Strikethrough(); + Node strikethrough = new Strikethrough(getDelimiterChar(), delimiterCount); Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 3e83dd7a9..5532c816a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -1,6 +1,7 @@ package org.commonmark.internal.inline; import org.commonmark.parser.DelimiterProcessor; +import org.commonmark.node.Delimited; import org.commonmark.node.Emphasis; import org.commonmark.node.Node; import org.commonmark.node.StrongEmphasis; @@ -26,7 +27,9 @@ public int getDelimiterUse(int openerCount, int closerCount) { @Override public void process(Text opener, Text closer, int delimiterUse) { - Node emphasis = delimiterUse == 1 ? new Emphasis() : new StrongEmphasis(); + Node emphasis = delimiterUse == 1 + ? new Emphasis(getDelimiterChar(), delimiterUse) + : new StrongEmphasis(getDelimiterChar(), delimiterUse); Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { diff --git a/commonmark/src/main/java/org/commonmark/node/Delimited.java b/commonmark/src/main/java/org/commonmark/node/Delimited.java new file mode 100644 index 000000000..dc65a772a --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/Delimited.java @@ -0,0 +1,9 @@ +package org.commonmark.node; + +public interface Delimited { + + char getDelimiterChar(); + + int getDelimiterCount(); + +} diff --git a/commonmark/src/main/java/org/commonmark/node/Emphasis.java b/commonmark/src/main/java/org/commonmark/node/Emphasis.java index 3f7c86051..39069de04 100644 --- a/commonmark/src/main/java/org/commonmark/node/Emphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/Emphasis.java @@ -1,6 +1,24 @@ package org.commonmark.node; -public class Emphasis extends Node { +public class Emphasis extends Node implements Delimited { + + private final char delimiterChar; + private final int delimiterCount; + + public Emphasis(char delimiterChar, int delimiterCount) { + this.delimiterChar = delimiterChar; + this.delimiterCount = delimiterCount; + } + + @Override + public char getDelimiterChar() { + return delimiterChar; + } + + @Override + public int getDelimiterCount() { + return delimiterCount; + } @Override public void accept(Visitor visitor) { diff --git a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java index 26ab4fbf5..eeb9e9343 100644 --- a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java @@ -1,9 +1,28 @@ package org.commonmark.node; -public class StrongEmphasis extends Node { +public class StrongEmphasis extends Node implements Delimited { + + private char delimiterChar; + private int delimiterCount; + + public StrongEmphasis(char delimiterChar, int delimiterCount) { + this.delimiterChar = delimiterChar; + this.delimiterCount = delimiterCount; + } + + @Override + public char getDelimiterChar() { + return delimiterChar; + } + + @Override + public int getDelimiterCount() { + return delimiterCount; + } @Override public void accept(Visitor visitor) { visitor.visit(this); } + } diff --git a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java new file mode 100644 index 000000000..813192bdc --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java @@ -0,0 +1,69 @@ +package org.commonmark.test; + +import org.commonmark.node.Node; +import org.commonmark.node.Delimited; +import org.commonmark.node.Emphasis; +import org.commonmark.node.StrongEmphasis; +import org.commonmark.node.Visitor; +import org.commonmark.node.AbstractVisitor; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.commonmark.internal.util.Debugging.log; +import static org.commonmark.internal.util.Debugging.toStringTree; + +public class DelimitedTest { + + @Test + public void one() { + + final Parser.Builder builder = Parser.builder(); + final Parser parser = builder.build(); + final Node document = parser.parse(getText()); + final java.util.List list = new java.util.ArrayList(); + + final Visitor visitor = new AbstractVisitor() { + @Override + public void visit(Emphasis node) { + list.add(node); + } + + @Override + public void visit(StrongEmphasis node) { + list.add(node); + } + }; + + //log(toStringTree(document)); + document.accept(visitor); + + assertEquals(4, list.size()); + + Delimited emphasis = list.get(0); + Delimited strong = list.get(1); + Delimited important = list.get(2); + Delimited critical = list.get(3); + + assertEquals('*', emphasis.getDelimiterChar()); + assertEquals('*', strong.getDelimiterChar()); + assertEquals('_', important.getDelimiterChar()); + assertEquals('_', critical.getDelimiterChar()); + + assertEquals(1, emphasis.getDelimiterCount()); + assertEquals(2, strong.getDelimiterCount()); + assertEquals(1, important.getDelimiterCount()); + assertEquals(2, critical.getDelimiterCount()); + + } + + String getText() { + String s = ""; + s += "* *emphasis* \n"; + s += "* **strong** \n"; + s += "* _important_ \n"; + s += "* __CRITICAL__ \n"; + return s; + } + +} From c6424daa1f11f0112da5a6b442dad6bced6e0079 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 3 Feb 2016 19:51:24 +1100 Subject: [PATCH 066/815] Change Delimited interface to allow for asymmetric delimiters --- .../ext/gfm/strikethrough/Strikethrough.java | 17 ++-- .../StrikethroughDelimiterProcessor.java | 2 +- .../gfm/strikethrough/StrikethroughTest.java | 11 +++ .../inline/EmphasisDelimiterProcessor.java | 8 +- .../java/org/commonmark/node/Delimited.java | 14 +++- .../java/org/commonmark/node/Emphasis.java | 23 +++--- .../org/commonmark/node/StrongEmphasis.java | 24 +++--- .../org/commonmark/test/DelimitedTest.java | 81 ++++++++----------- 8 files changed, 93 insertions(+), 87 deletions(-) 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 1d89594de..115ae9ea4 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 @@ -8,22 +8,15 @@ */ public class Strikethrough extends CustomNode implements Delimited { - private final char delimiterChar; - private final int delimiterCount; - - public Strikethrough(char delimiterChar, int delimiterCount) { - this.delimiterChar = delimiterChar; - this.delimiterCount = delimiterCount; - } + private static final String DELIMITER = "~~"; @Override - public char getDelimiterChar() { - return delimiterChar; + public String getOpeningDelimiter() { + return DELIMITER; } @Override - public int getDelimiterCount() { - return delimiterCount; + public String getClosingDelimiter() { + return DELIMITER; } - } 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 3e9b71efc..14a847e2d 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 @@ -39,7 +39,7 @@ public void process(Text opener, Text closer, int delimiterCount) { } // Normal case, wrap nodes between delimiters in strikethrough. - Node strikethrough = new Strikethrough(getDelimiterChar(), delimiterCount); + Node strikethrough = new Strikethrough(); Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { 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 d65c1557d..12bcf2a49 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 @@ -2,6 +2,7 @@ import org.commonmark.Extension; import org.commonmark.html.HtmlRenderer; +import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; import org.junit.Test; @@ -9,6 +10,8 @@ import java.util.Collections; import java.util.Set; +import static org.junit.Assert.assertEquals; + public class StrikethroughTest extends RenderingTestCase { private static final Set EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); @@ -63,6 +66,14 @@ public void insideBlockQuote() { "
    \n

    strike that

    \n
    \n"); } + @Test + public void delimited() { + Node document = PARSER.parse("~~foo~~"); + Strikethrough strikethrough = (Strikethrough) document.getFirstChild().getFirstChild(); + assertEquals("~~", strikethrough.getOpeningDelimiter()); + assertEquals("~~", strikethrough.getClosingDelimiter()); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 5532c816a..1b9ebc371 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -1,11 +1,10 @@ package org.commonmark.internal.inline; -import org.commonmark.parser.DelimiterProcessor; -import org.commonmark.node.Delimited; import org.commonmark.node.Emphasis; import org.commonmark.node.Node; import org.commonmark.node.StrongEmphasis; import org.commonmark.node.Text; +import org.commonmark.parser.DelimiterProcessor; public abstract class EmphasisDelimiterProcessor implements DelimiterProcessor { @@ -27,9 +26,10 @@ public int getDelimiterUse(int openerCount, int closerCount) { @Override public void process(Text opener, Text closer, int delimiterUse) { + String singleDelimiter = String.valueOf(getDelimiterChar()); Node emphasis = delimiterUse == 1 - ? new Emphasis(getDelimiterChar(), delimiterUse) - : new StrongEmphasis(getDelimiterChar(), delimiterUse); + ? new Emphasis(singleDelimiter) + : new StrongEmphasis(singleDelimiter + singleDelimiter); Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { diff --git a/commonmark/src/main/java/org/commonmark/node/Delimited.java b/commonmark/src/main/java/org/commonmark/node/Delimited.java index dc65a772a..ef02c84ad 100644 --- a/commonmark/src/main/java/org/commonmark/node/Delimited.java +++ b/commonmark/src/main/java/org/commonmark/node/Delimited.java @@ -1,9 +1,17 @@ package org.commonmark.node; +/** + * A node that uses delimiters in the source form (e.g. *bold*). + */ public interface Delimited { - char getDelimiterChar(); - - int getDelimiterCount(); + /** + * @return the opening (beginning) delimiter, e.g. * + */ + String getOpeningDelimiter(); + /** + * @return the closing (ending) delimiter, e.g. * + */ + String getClosingDelimiter(); } diff --git a/commonmark/src/main/java/org/commonmark/node/Emphasis.java b/commonmark/src/main/java/org/commonmark/node/Emphasis.java index 39069de04..9877e7b63 100644 --- a/commonmark/src/main/java/org/commonmark/node/Emphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/Emphasis.java @@ -2,22 +2,27 @@ public class Emphasis extends Node implements Delimited { - private final char delimiterChar; - private final int delimiterCount; + private String delimiter; - public Emphasis(char delimiterChar, int delimiterCount) { - this.delimiterChar = delimiterChar; - this.delimiterCount = delimiterCount; + public Emphasis() { + } + + public Emphasis(String delimiter) { + this.delimiter = delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; } @Override - public char getDelimiterChar() { - return delimiterChar; + public String getOpeningDelimiter() { + return delimiter; } @Override - public int getDelimiterCount() { - return delimiterCount; + public String getClosingDelimiter() { + return delimiter; } @Override diff --git a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java index eeb9e9343..dbff571cd 100644 --- a/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java +++ b/commonmark/src/main/java/org/commonmark/node/StrongEmphasis.java @@ -2,27 +2,31 @@ public class StrongEmphasis extends Node implements Delimited { - private char delimiterChar; - private int delimiterCount; + private String delimiter; - public StrongEmphasis(char delimiterChar, int delimiterCount) { - this.delimiterChar = delimiterChar; - this.delimiterCount = delimiterCount; + public StrongEmphasis() { + } + + public StrongEmphasis(String delimiter) { + this.delimiter = delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; } @Override - public char getDelimiterChar() { - return delimiterChar; + public String getOpeningDelimiter() { + return delimiter; } @Override - public int getDelimiterCount() { - return delimiterCount; + public String getClosingDelimiter() { + return delimiter; } @Override public void accept(Visitor visitor) { visitor.visit(this); } - } diff --git a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java index 813192bdc..a34a32c44 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimitedTest.java @@ -1,41 +1,38 @@ package org.commonmark.test; -import org.commonmark.node.Node; -import org.commonmark.node.Delimited; -import org.commonmark.node.Emphasis; -import org.commonmark.node.StrongEmphasis; -import org.commonmark.node.Visitor; -import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; + import static org.junit.Assert.assertEquals; -import static org.commonmark.internal.util.Debugging.log; -import static org.commonmark.internal.util.Debugging.toStringTree; public class DelimitedTest { @Test - public void one() { - - final Parser.Builder builder = Parser.builder(); - final Parser parser = builder.build(); - final Node document = parser.parse(getText()); - final java.util.List list = new java.util.ArrayList(); - - final Visitor visitor = new AbstractVisitor() { - @Override - public void visit(Emphasis node) { - list.add(node); - } - - @Override - public void visit(StrongEmphasis node) { - list.add(node); - } - }; - - //log(toStringTree(document)); + public void emphasisDelimiters() { + String input = "* *emphasis* \n" + + "* **strong** \n" + + "* _important_ \n" + + "* __CRITICAL__ \n"; + + Parser parser = Parser.builder().build(); + Node document = parser.parse(input); + + final List list = new ArrayList<>(); + Visitor visitor = new AbstractVisitor() { + @Override + public void visit(Emphasis node) { + list.add(node); + } + + @Override + public void visit(StrongEmphasis node) { + list.add(node); + } + }; document.accept(visitor); assertEquals(4, list.size()); @@ -45,25 +42,13 @@ public void visit(StrongEmphasis node) { Delimited important = list.get(2); Delimited critical = list.get(3); - assertEquals('*', emphasis.getDelimiterChar()); - assertEquals('*', strong.getDelimiterChar()); - assertEquals('_', important.getDelimiterChar()); - assertEquals('_', critical.getDelimiterChar()); - - assertEquals(1, emphasis.getDelimiterCount()); - assertEquals(2, strong.getDelimiterCount()); - assertEquals(1, important.getDelimiterCount()); - assertEquals(2, critical.getDelimiterCount()); - - } - - String getText() { - String s = ""; - s += "* *emphasis* \n"; - s += "* **strong** \n"; - s += "* _important_ \n"; - s += "* __CRITICAL__ \n"; - return s; + assertEquals("*", emphasis.getOpeningDelimiter()); + assertEquals("*", emphasis.getClosingDelimiter()); + assertEquals("**", strong.getOpeningDelimiter()); + assertEquals("**", strong.getClosingDelimiter()); + assertEquals("_", important.getOpeningDelimiter()); + assertEquals("_", important.getClosingDelimiter()); + assertEquals("__", critical.getOpeningDelimiter()); + assertEquals("__", critical.getClosingDelimiter()); } - } From cec61d0f334eb7f3dd125a5b58796c862eee7b7d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 5 Feb 2016 09:59:07 +1100 Subject: [PATCH 067/815] Add support for asymmetric delimiter processors (api break, #17) DelimiterProcessor can now specify an opening delimiter char and a different closing delimiter char for asymmetric delimiters. An example would be `{` as an opening delimiter and `}` as a closing delimiter in `{foo}`. --- .../StrikethroughDelimiterProcessor.java | 8 +- .../commonmark/internal/InlineParserImpl.java | 47 +++++---- .../inline/AsteriskDelimiterProcessor.java | 6 +- .../inline/EmphasisDelimiterProcessor.java | 18 +++- .../inline/UnderscoreDelimiterProcessor.java | 6 +- .../commonmark/parser/DelimiterProcessor.java | 11 ++- .../test/DelimiterProcessorTest.java | 96 +++++++++++++++++++ 7 files changed, 162 insertions(+), 30 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java 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 14a847e2d..118f21cc4 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 @@ -8,7 +8,12 @@ public class StrikethroughDelimiterProcessor implements DelimiterProcessor { @Override - public char getDelimiterChar() { + public char getOpeningDelimiterChar() { + return '~'; + } + + @Override + public char getClosingDelimiterChar() { return '~'; } @@ -50,5 +55,4 @@ public void process(Text opener, Text closer, int delimiterCount) { opener.insertAfter(strikethrough); } - } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 6f0511aae..09fcf2962 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -135,14 +135,22 @@ public static Map calculateDelimiterProcessors(Li private static void addDelimiterProcessors(Iterable delimiterProcessors, Map map) { for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { - char c = delimiterProcessor.getDelimiterChar(); - DelimiterProcessor existing = map.put(c, delimiterProcessor); - if (existing != null) { - throw new IllegalArgumentException("Inline delimiter parser can not be registered more than once, delimiter character: " + c); + char opening = delimiterProcessor.getOpeningDelimiterChar(); + addDelimiterProcessorForChar(opening, delimiterProcessor, map); + char closing = delimiterProcessor.getClosingDelimiterChar(); + if (opening != closing) { + addDelimiterProcessorForChar(closing, delimiterProcessor, map); } } } + private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterProcessor toAdd, Map delimiterProcessors) { + DelimiterProcessor existing = delimiterProcessors.put(delimiterChar, toAdd); + if (existing != null) { + throw new IllegalArgumentException("Delimiter processor conflict with delimiter char '" + delimiterChar + "'"); + } + } + /** * Parse content in block into inline children, using reference map to resolve references. */ @@ -307,8 +315,8 @@ private boolean parseInline() { default: boolean isDelimiter = delimiterCharacters.get(c); if (isDelimiter) { - DelimiterProcessor inlineDelimiter = delimiterProcessors.get(c); - res = parseDelimiters(inlineDelimiter); + DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c); + res = parseDelimiters(delimiterProcessor, c); } else { res = parseString(); } @@ -441,8 +449,8 @@ private boolean parseBackticks() { /** * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ - private boolean parseDelimiters(DelimiterProcessor inlineDelimiter) { - DelimiterRun res = scanDelims(inlineDelimiter); + private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + DelimiterRun res = scanDelimiters(delimiterProcessor, delimiterChar); if (res == null) { return false; } @@ -454,7 +462,7 @@ private boolean parseDelimiters(DelimiterProcessor inlineDelimiter) { // Add entry to stack for this opener this.delimiter = new Delimiter(node, this.delimiter, startIndex); - this.delimiter.delimiterChar = inlineDelimiter.getDelimiterChar(); + this.delimiter.delimiterChar = delimiterChar; this.delimiter.numDelims = numDelims; this.delimiter.canOpen = res.canOpen; this.delimiter.canClose = res.canClose; @@ -762,17 +770,16 @@ private boolean parseString() { * * @return information about delimiter run, or {@code null} */ - private DelimiterRun scanDelims(DelimiterProcessor inlineDelimiter) { + private DelimiterRun scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { int startIndex = index; int delimiterCount = 0; - char delimiterChar = inlineDelimiter.getDelimiterChar(); while (peek() == delimiterChar) { delimiterCount++; index++; } - if (delimiterCount < inlineDelimiter.getMinDelimiterCount()) { + if (delimiterCount < delimiterProcessor.getMinDelimiterCount()) { index = startIndex; return null; } @@ -784,10 +791,11 @@ private DelimiterRun scanDelims(DelimiterProcessor inlineDelimiter) { String after = charAfter == '\0' ? "\n" : String.valueOf(charAfter); + // We could be more lazy here, in most cases we don't need to do every match case. boolean beforeIsPunctuation = PUNCTUATION.matcher(before).matches(); boolean beforeIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(before).matches(); - boolean afterIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(after).matches(); boolean afterIsPunctuation = PUNCTUATION.matcher(after).matches(); + boolean afterIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(after).matches(); boolean leftFlanking = !afterIsWhitespace && !(afterIsPunctuation && !beforeIsWhitespace && !beforeIsPunctuation); @@ -799,8 +807,8 @@ private DelimiterRun scanDelims(DelimiterProcessor inlineDelimiter) { canOpen = leftFlanking && (!rightFlanking || beforeIsPunctuation); canClose = rightFlanking && (!leftFlanking || afterIsPunctuation); } else { - canOpen = leftFlanking; - canClose = rightFlanking; + canOpen = leftFlanking && delimiterChar == delimiterProcessor.getOpeningDelimiterChar(); + canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingDelimiterChar(); } index = startIndex; @@ -820,16 +828,19 @@ private void processDelimiters(Delimiter stackBottom) { while (closer != null) { char delimiterChar = closer.delimiterChar; - if (!closer.canClose || !delimiterProcessors.containsKey(delimiterChar)) { + DelimiterProcessor delimiterProcessor = delimiterProcessors.get(delimiterChar); + if (!closer.canClose || delimiterProcessor == null) { closer = closer.next; continue; } + char openingDelimiterChar = delimiterProcessor.getOpeningDelimiterChar(); + // found delimiter closer. now look back for first matching opener: boolean openerFound = false; Delimiter opener = closer.previous; while (opener != null && opener != stackBottom && opener != openersBottom.get(delimiterChar)) { - if (opener.delimiterChar == delimiterChar && opener.canOpen) { + if (opener.delimiterChar == openingDelimiterChar && opener.canOpen) { openerFound = true; break; } @@ -848,8 +859,6 @@ private void processDelimiters(Delimiter stackBottom) { continue; } - DelimiterProcessor delimiterProcessor = delimiterProcessors.get(closer.delimiterChar); - int useDelims = delimiterProcessor.getDelimiterUse(opener.numDelims, closer.numDelims); if (useDelims <= 0) { // nope diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/AsteriskDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/AsteriskDelimiterProcessor.java index b8630dfc4..321c78ed6 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AsteriskDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AsteriskDelimiterProcessor.java @@ -1,8 +1,8 @@ package org.commonmark.internal.inline; public class AsteriskDelimiterProcessor extends EmphasisDelimiterProcessor { - @Override - public char getDelimiterChar() { - return '*'; + + public AsteriskDelimiterProcessor() { + super('*'); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 1b9ebc371..02260ea67 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -8,6 +8,22 @@ public abstract class EmphasisDelimiterProcessor implements DelimiterProcessor { + private final char delimiterChar; + + protected EmphasisDelimiterProcessor(char delimiterChar) { + this.delimiterChar = delimiterChar; + } + + @Override + public char getOpeningDelimiterChar() { + return delimiterChar; + } + + @Override + public char getClosingDelimiterChar() { + return delimiterChar; + } + @Override public int getMinDelimiterCount() { return 1; @@ -26,7 +42,7 @@ public int getDelimiterUse(int openerCount, int closerCount) { @Override public void process(Text opener, Text closer, int delimiterUse) { - String singleDelimiter = String.valueOf(getDelimiterChar()); + String singleDelimiter = String.valueOf(getOpeningDelimiterChar()); Node emphasis = delimiterUse == 1 ? new Emphasis(singleDelimiter) : new StrongEmphasis(singleDelimiter + singleDelimiter); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/UnderscoreDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/UnderscoreDelimiterProcessor.java index 2804739c3..886eb89fe 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/UnderscoreDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/UnderscoreDelimiterProcessor.java @@ -1,8 +1,8 @@ package org.commonmark.internal.inline; public class UnderscoreDelimiterProcessor extends EmphasisDelimiterProcessor { - @Override - public char getDelimiterChar() { - return '_'; + + public UnderscoreDelimiterProcessor() { + super('_'); } } diff --git a/commonmark/src/main/java/org/commonmark/parser/DelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/parser/DelimiterProcessor.java index 7e9e84df9..13ec57cb0 100644 --- a/commonmark/src/main/java/org/commonmark/parser/DelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/parser/DelimiterProcessor.java @@ -10,9 +10,16 @@ public interface DelimiterProcessor { /** - * @return the character that activates this, must not clash with any built-in special characters + * @return the character that marks the beginning of a delimited node, must not clash with any built-in special + * characters */ - char getDelimiterChar(); + char getOpeningDelimiterChar(); + + /** + * @return the character that marks the the ending of a delimited node, must not clash with any built-in special + * characters. Note that for a symmetric delimiter such as "*", this is the same as the opening. + */ + char getClosingDelimiterChar(); /** * Minimum number of delimiter characters that are needed to activate this. Must be at least 1. diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java new file mode 100644 index 000000000..1f9ed95ed --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -0,0 +1,96 @@ +package org.commonmark.test; + +import org.commonmark.html.CustomHtmlRenderer; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.html.HtmlWriter; +import org.commonmark.node.CustomNode; +import org.commonmark.node.Node; +import org.commonmark.node.Text; +import org.commonmark.node.Visitor; +import org.commonmark.parser.DelimiterProcessor; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import java.util.Locale; + +public class DelimiterProcessorTest extends RenderingTestCase { + + private static final Parser PARSER = Parser.builder().customDelimiterProcessor(new AsymmetricDelimiterProcessor()).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().customHtmlRenderer(new UpperCaseNodeRenderer()).build(); + + @Test + public void asymmetricDelimiter() { + assertRendering("{foo} bar", "

    FOO bar

    \n"); + assertRendering("f{oo ba}r", "

    fOO BAr

    \n"); + assertRendering("{{foo} bar", "

    {FOO bar

    \n"); + assertRendering("{foo}} bar", "

    FOO} bar

    \n"); + assertRendering("{{foo} bar}", "

    FOO BAR

    \n"); + assertRendering("{foo bar", "

    {foo bar

    \n"); + assertRendering("foo} bar", "

    foo} bar

    \n"); + assertRendering("}foo} bar", "

    }foo} bar

    \n"); + assertRendering("{foo{ bar", "

    {foo{ bar

    \n"); + assertRendering("}foo{ bar", "

    }foo{ bar

    \n"); + } + + @Override + protected String render(String source) { + Node node = PARSER.parse(source); + return RENDERER.render(node); + } + + private static class AsymmetricDelimiterProcessor implements DelimiterProcessor { + + @Override + public char getOpeningDelimiterChar() { + return '{'; + } + + @Override + public char getClosingDelimiterChar() { + return '}'; + } + + @Override + public int getMinDelimiterCount() { + return 1; + } + + @Override + public int getDelimiterUse(int openerCount, int closerCount) { + return 1; + } + + @Override + public void process(Text opener, Text closer, int delimiterUse) { + UpperCaseNode content = new UpperCaseNode(); + Node tmp = opener.getNext(); + while (tmp != null && tmp != closer) { + Node next = tmp.getNext(); + content.appendChild(tmp); + tmp = next; + } + opener.insertAfter(content); + } + } + + private static class UpperCaseNode extends CustomNode { + } + + private static class UpperCaseNodeRenderer implements CustomHtmlRenderer { + @Override + public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { + if (node instanceof UpperCaseNode) { + UpperCaseNode upperCaseNode = (UpperCaseNode) node; + for (Node child = upperCaseNode.getFirstChild(); child != null; child = child.getNext()) { + if (child instanceof Text) { + Text text = (Text) child; + text.setLiteral(text.getLiteral().toUpperCase(Locale.ENGLISH)); + } + child.accept(visitor); + } + return true; + } + return false; + } + } +} From 45f0ebe9e6ff6c0def1fd4ce9e52470cbac1ec4d Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Mon, 8 Feb 2016 21:00:30 +0300 Subject: [PATCH 068/815] Add basic Android project --- commonmark-android-test/.gitignore | 5 + commonmark-android-test/app/build.gradle | 74 ++++++++ .../app/src/main/AndroidManifest.xml | 3 + commonmark-android-test/build.gradle | 18 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + commonmark-android-test/gradlew | 160 ++++++++++++++++++ commonmark-android-test/gradlew.bat | 90 ++++++++++ commonmark-android-test/settings.gradle | 1 + 9 files changed, 357 insertions(+) create mode 100644 commonmark-android-test/.gitignore create mode 100644 commonmark-android-test/app/build.gradle create mode 100644 commonmark-android-test/app/src/main/AndroidManifest.xml create mode 100644 commonmark-android-test/build.gradle create mode 100644 commonmark-android-test/gradle/wrapper/gradle-wrapper.jar create mode 100644 commonmark-android-test/gradle/wrapper/gradle-wrapper.properties create mode 100755 commonmark-android-test/gradlew create mode 100644 commonmark-android-test/gradlew.bat create mode 100644 commonmark-android-test/settings.gradle diff --git a/commonmark-android-test/.gitignore b/commonmark-android-test/.gitignore new file mode 100644 index 000000000..b738a69b4 --- /dev/null +++ b/commonmark-android-test/.gitignore @@ -0,0 +1,5 @@ +.gradle +local.properties +test.properties +gradle.properties +build diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle new file mode 100644 index 000000000..1ca49f9be --- /dev/null +++ b/commonmark-android-test/app/build.gradle @@ -0,0 +1,74 @@ +apply plugin: 'com.android.application' + +def testProperties +def testPropertiesFile = file('../test.properties') +if (testPropertiesFile.canRead()) { + testProperties = new Properties() + testPropertiesFile.withInputStream { + stream -> testProperties.load(stream) + } +} + +android { + compileSdkVersion 15 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.atlassian.commonmark.android.test" + minSdkVersion 15 + targetSdkVersion 15 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + productFlavors { + maven + snapshot + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + } + + testOptions { + resultsDir = testProperties['path.report'] + } +} + +repositories { + flatDir { + dirs '../../commonmark/target', + '../../commonmark-ext-autolink/target', + '../../commonmark-ext-gfm-strikethrough/target', + '../../commonmark-ext-gfm-tables/target', + '../../commonmark-test-util/target' + } +} + +dependencies { + androidTestCompile 'com.android.support.test:runner:0.4.1' + androidTestCompile 'com.android.support.test:rules:0.4.1' + + androidTestCompile ':commonmark-test-util-' + testProperties['version.snapshot'] + + androidTestMavenCompile 'com.atlassian.commonmark:commonmark:' + testProperties['version.maven'] + androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-autolink:' + testProperties['version.maven'] + androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:' + testProperties['version.maven'] + androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-gfm-tables:' + testProperties['version.maven'] + + androidTestSnapshotCompile 'org.nibor.autolink:autolink:' + testProperties['version.snapshot_autolink'] + androidTestSnapshotCompile ':commonmark-' + testProperties['version.snapshot'] + androidTestSnapshotCompile ':commonmark-ext-autolink-' + testProperties['version.snapshot'] + androidTestSnapshotCompile ':commonmark-ext-gfm-strikethrough-' + testProperties['version.snapshot'] + androidTestSnapshotCompile ':commonmark-ext-gfm-tables-' + testProperties['version.snapshot'] +} diff --git a/commonmark-android-test/app/src/main/AndroidManifest.xml b/commonmark-android-test/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..de2cd5725 --- /dev/null +++ b/commonmark-android-test/app/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/commonmark-android-test/build.gradle b/commonmark-android-test/build.gradle new file mode 100644 index 000000000..027fee4d9 --- /dev/null +++ b/commonmark-android-test/build.gradle @@ -0,0 +1,18 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.5.0' + } +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/commonmark-android-test/gradle/wrapper/gradle-wrapper.jar b/commonmark-android-test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +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" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +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. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "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 +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 + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; 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 + # 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\"" + fi + i=$((i+1)) + 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 + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/commonmark-android-test/gradlew.bat b/commonmark-android-test/gradlew.bat new file mode 100644 index 000000000..8a0b282aa --- /dev/null +++ b/commonmark-android-test/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +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. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +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. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +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% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/commonmark-android-test/settings.gradle b/commonmark-android-test/settings.gradle new file mode 100644 index 000000000..e7b4def49 --- /dev/null +++ b/commonmark-android-test/settings.gradle @@ -0,0 +1 @@ +include ':app' From bc6163a9b869519f66fa2aa35823fd21ec5cb204 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Mon, 8 Feb 2016 21:01:33 +0300 Subject: [PATCH 069/815] Fix ClassDefNotFoundException in SpecReader --- .../src/main/java/org/commonmark/spec/SpecReader.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java b/commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java index 551d8618a..77a2ead4e 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java +++ b/commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -47,7 +47,7 @@ public static List readExamplesAsString() { public static String readSpec() { StringBuilder sb = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(getSpecInputStream(), StandardCharsets.UTF_8))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(getSpecInputStream(), Charset.forName("UTF-8")))) { String line; while ((line = reader.readLine()) != null) { sb.append(line); @@ -71,7 +71,7 @@ private List read() throws IOException { resetContents(); try (BufferedReader reader = new BufferedReader( - new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + new InputStreamReader(inputStream, Charset.forName("UTF-8")))) { String line; while ((line = reader.readLine()) != null) { processLine(line); From b1d2f8cd1c81bcaa05313d7f721d5750d30770b8 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Mon, 8 Feb 2016 21:02:07 +0300 Subject: [PATCH 070/815] Add AndroidSupportTest --- .../android/test/AndroidSupportTest.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java diff --git a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java new file mode 100644 index 000000000..0308cf549 --- /dev/null +++ b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java @@ -0,0 +1,76 @@ +package com.atlassian.commonmark.android.test; + +import org.commonmark.Extension; +import org.commonmark.ext.autolink.AutolinkExtension; +import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; +import org.commonmark.ext.gfm.tables.TablesExtension; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.spec.SpecReader; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import java.util.Collections; + +import static org.junit.Assert.assertNotNull; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class AndroidSupportTest { + + private String spec; + + @Before + public void setUp() throws Exception { + spec = SpecReader.readSpec(); + } + + @Test + public void parseTest() throws Exception { + Parser parser = new Parser.Builder().build(); + + Node document = parser.parse(spec); + + assertNotNull(document); + } + + @Test + public void autolinkExtensionTest() throws Exception { + parseWithExtensionsTest(AutolinkExtension.create()); + } + + @Test + public void strikethroughExtensionTest() throws Exception { + parseWithExtensionsTest(StrikethroughExtension.create()); + } + + @Test + public void tablesExtensionTest() throws Exception { + parseWithExtensionsTest(TablesExtension.create()); + } + + @Test + public void htmlRendererTest() throws Exception { + Parser parser = new Parser.Builder().build(); + HtmlRenderer renderer = new HtmlRenderer.Builder().build(); + + String renderedString = renderer.render(parser.parse(spec)); + + assertNotNull(renderedString); + } + + private void parseWithExtensionsTest(Extension extension) throws Exception { + Parser parser = new Parser.Builder() + .extensions(Collections.singletonList(extension)) + .build(); + + Node document = parser.parse(spec); + + assertNotNull(document); + } +} From a69ed05f2ff8d1302f37efa8e91eed9ddf94423a Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Mon, 8 Feb 2016 21:02:52 +0300 Subject: [PATCH 071/815] Add README.md for module commonmark-android-test --- commonmark-android-test/README.md | 95 +++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 commonmark-android-test/README.md diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md new file mode 100644 index 000000000..eb65c6f67 --- /dev/null +++ b/commonmark-android-test/README.md @@ -0,0 +1,95 @@ +commonmark-android-test +======================= + +This module ensures that commonmark-java is supported on Android + +Requirements: + +* Java 7 or above +* Android SDK 15 +* Running emulator or connected android device + +Configuration +----- + +1. Download Android SDK +2. Be sure that SDK Platform 15 and emulator for this platform (system image) are installed. It's recommended to use x86 +3. Export to PATH: `path_to_android_sdk/platform-tools` and `path_to_android_sdk/tools` +4. Create 2 properties files in commonmark-android-test + +/local.properties +```properties +sdk.dir=/path_to_android_sdk +``` + +/test.properties +```properties +# Absolute or relative (./ == /app) path to test reports. +path.report=../report + +# Version number of commonmark and extensions in maven central. +version.maven=0.4.0 + +# Version number of commonmark and extensions in project. +version.snapshot=0.4.1-SNAPSHOT +# Version number of autolink for snapshots (since it's not contained in extension jar). +version.snapshot_autolink=0.3.0 +``` + +If you're going to test on device with Android 15 then you can skip downloading emulator. + +Usage +----- + +#### Run test with MAVEN version + +on Mac/Linux: +```shell +./gradlew :app:connectedMavenDebugAndroidTest +``` + +on Windows: +```bat +.\gradlew :app:connectedMavenDebugAndroidTest +``` + +#### Run test with SNAPSHOT version + +Before running tests you need to run `maven install` + +on Mac/Linux: +```shell +./gradlew :app:connectedSnapshotDebugAndroidTest +``` + +on Windows: +```bat +.\gradlew :app:connectedSnapshotDebugAndroidTest +``` + + +#### Testing in CI + +on Mac/Linux: +```shell +echo no | android create avd --force -n test -t "android-15" +emulator -avd test & +adb wait-for-device +./gradlew :app:clean :app:connectedSnapshotDebugAndroidTest +adb emu kill +``` + +on Windows: +```bat +echo no | android create avd --force -n test -t "android-15" +start emulator -avd test +adb wait-for-device +gradlew :app:clean :app:connectedSnapshotDebugAndroidTest & adb emu kill +``` + +There could be problems with command `adb wait-for-device` which could be resolved by adding additional pause before running test. + +Links +----- +[Gradle Documentations](https://docs.gradle.org/current/userguide/userguide.html) +[Android Gradle Plugin Docs](http://tools.android.com/tech-docs/new-build-system) From 9cca142af96e1a269286a0f3136c492d15f12e20 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Feb 2016 00:07:20 +1100 Subject: [PATCH 072/815] ext-autolink: Update to autolink 0.4.0 * Treat more special characters as trailing delimiters to not include `">`, `"/>` and `");` at the end of URLs * Fix unexpected link end with unfinished delimiter pairs in URLs * Fix Android incompatibility by not using java.util.Objects --- 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 e119cf9c9..8cdc78133 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -19,7 +19,7 @@ org.nibor.autolink autolink - 0.3.0 + 0.4.0 From 2592c6b3f4fb856be840964b6fb876d475d9e0dd Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Feb 2016 11:38:11 +1100 Subject: [PATCH 073/815] [maven-release-plugin] prepare release commonmark-parent-0.4.1 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index e119cf9c9..7360e1631 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1-SNAPSHOT + 0.4.1 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 3c8f0d3d9..62791494b 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1-SNAPSHOT + 0.4.1 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 8ca06e28f..4144a511a 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1-SNAPSHOT + 0.4.1 commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 86679f478..2f1e2fa05 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1-SNAPSHOT + 0.4.1 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index e2b7a6d3f..c06b27625 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1-SNAPSHOT + 0.4.1 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 9b7662e7a..310b31bd5 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1-SNAPSHOT + 0.4.1 commonmark diff --git a/pom.xml b/pom.xml index c484d43c6..45d48cd92 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1-SNAPSHOT + 0.4.1 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.4.1-SNAPSHOT + 0.4.1 com.atlassian.commonmark commonmark-ext-autolink - 0.4.1-SNAPSHOT + 0.4.1 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.4.1-SNAPSHOT + 0.4.1 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.4.1-SNAPSHOT + 0.4.1 com.atlassian.commonmark commonmark-test-util - 0.4.1-SNAPSHOT + 0.4.1 @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.4.1 From 5d167d86257286a2551e62d35318ea37b84bb3c0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Feb 2016 11:38:11 +1100 Subject: [PATCH 074/815] [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-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 7360e1631..f17585f35 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1 + 0.4.2-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 62791494b..21cdc79c2 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1 + 0.4.2-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 4144a511a..668839c40 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1 + 0.4.2-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 2f1e2fa05..784a2d026 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1 + 0.4.2-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index c06b27625..b48c896e8 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1 + 0.4.2-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 310b31bd5..326208c92 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1 + 0.4.2-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 45d48cd92..9a5c9d6d3 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.1 + 0.4.2-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,27 +84,27 @@ com.atlassian.commonmark commonmark - 0.4.1 + 0.4.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.4.1 + 0.4.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.4.1 + 0.4.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.4.1 + 0.4.2-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.4.1 + 0.4.2-SNAPSHOT @@ -147,7 +147,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.4.1 + HEAD From ff35940f9386d88fd649cd4fcfee4d2bde23d4f7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Feb 2016 13:24:26 +1100 Subject: [PATCH 075/815] README: Bump version to 0.4.1 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84afd24ab..8a8e793a0 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.4.0 + 0.4.1 ``` @@ -105,7 +105,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.4.0 + 0.4.1 ``` From 37834dd8563e9668bab9e0fc523718bfa294654f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 16 Feb 2016 16:54:24 +1100 Subject: [PATCH 076/815] Make sure .bat file has CRLF line endings It had LF line endings before, but Windows may have problems with that. --- commonmark-android-test/gradlew.bat | 180 ++++++++++++++-------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/commonmark-android-test/gradlew.bat b/commonmark-android-test/gradlew.bat index 8a0b282aa..aec99730b 100644 --- a/commonmark-android-test/gradlew.bat +++ b/commonmark-android-test/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@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= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -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. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -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. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_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=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -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% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="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 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +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. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +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. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +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% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 8c87c0f028796f9c942b7a44a68d4a494f12d9a6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 16 Feb 2016 16:58:48 +1100 Subject: [PATCH 077/815] Add android test project to Travis build matrix --- .travis.yml | 18 +++++++++++++++++- commonmark-android-test/.travis.sh | 19 +++++++++++++++++++ commonmark-android-test/README.md | 16 +++++++++------- commonmark-android-test/app/build.gradle | 3 ++- commonmark-ext-autolink/pom.xml | 6 +++++- 5 files changed, 52 insertions(+), 10 deletions(-) create mode 100755 commonmark-android-test/.travis.sh diff --git a/.travis.yml b/.travis.yml index fa2c88c01..017b01cfe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,20 @@ -language: java +language: android jdk: - openjdk7 - oraclejdk8 +env: + - TEST=java + - TEST=android +matrix: + exclude: + - jdk: oraclejdk8 + env: TEST=android +android: + components: + - android-15 + - build-tools-21.1.1 + - extra-android-m2repository + - sys-img-armeabi-v7a-android-15 +script: + - 'if [[ $TEST = java ]]; then mvn test; fi' + - 'if [[ $TEST = android ]]; then mvn install -DskipTests && cd commonmark-android-test && ./.travis.sh; fi' diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh new file mode 100755 index 000000000..f84ad2b4c --- /dev/null +++ b/commonmark-android-test/.travis.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +version=$(cd .. && mvn help:evaluate -Dexpression=project.version | grep -v '^\[' | tail -1) +autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpression=autolink.version | grep -v '^\[' | tail -1) + +touch test.properties +echo "path.report=../report" >> test.properties +echo "version.maven=0.4.1" >> test.properties +echo "version.maven_autolink=0.4.0" >> test.properties +echo "version.snapshot=$version" >> test.properties +echo "version.snapshot_autolink=$autolink_version" >> test.properties + +echo no | android create avd --force -n test -t "android-15" +emulator -avd test -no-audio -no-window & +android-wait-for-emulator + +TERM=dumb ./gradlew --stacktrace :app:connectedSnapshotDebugAndroidTest diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index eb65c6f67..8400cb44a 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -27,13 +27,15 @@ sdk.dir=/path_to_android_sdk # Absolute or relative (./ == /app) path to test reports. path.report=../report -# Version number of commonmark and extensions in maven central. -version.maven=0.4.0 - -# Version number of commonmark and extensions in project. -version.snapshot=0.4.1-SNAPSHOT -# Version number of autolink for snapshots (since it's not contained in extension jar). -version.snapshot_autolink=0.3.0 +# Version number of commonmark and extensions in maven central. +version.maven=0.4.1 +# Version number of autolink in maven central (not bundled with extension jar). +version.snapshot_autolink=0.4.0 + +# Version number of commonmark and extensions in project. +version.snapshot=0.4.2-SNAPSHOT +# Version number of autolink for snapshots (not bundled in extension jar). +version.snapshot_autolink=0.4.0 ``` If you're going to test on device with Android 15 then you can skip downloading emulator. diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 1ca49f9be..0901a5496 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -11,7 +11,7 @@ if (testPropertiesFile.canRead()) { android { compileSdkVersion 15 - buildToolsVersion "23.0.2" + buildToolsVersion "21.1.1" defaultConfig { applicationId "com.atlassian.commonmark.android.test" @@ -61,6 +61,7 @@ dependencies { androidTestCompile ':commonmark-test-util-' + testProperties['version.snapshot'] + androidTestMavenCompile 'org.nibor.autolink:autolink:' + testProperties['version.maven_autolink'] androidTestMavenCompile 'com.atlassian.commonmark:commonmark:' + testProperties['version.maven'] androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-autolink:' + testProperties['version.maven'] androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:' + testProperties['version.maven'] diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index c1f43f843..b4f65ae74 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -11,6 +11,10 @@ commonmark-java extension for autolinking commonmark-java extension for turning plain URLs and email addresses into links + + 0.4.0 + + com.atlassian.commonmark @@ -19,7 +23,7 @@ org.nibor.autolink autolink - 0.4.0 + ${autolink.version} From 61b9d986a29cdba74ffbe43d2a69fd984580a7eb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 18 Feb 2016 10:45:34 +1100 Subject: [PATCH 078/815] Bump JMH and maven-compiler-plugin versions The latter allows us to get rid of a workaround, yay. --- pom.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 9a5c9d6d3..1d5ee2c14 100644 --- a/pom.xml +++ b/pom.xml @@ -41,11 +41,10 @@ org.apache.maven.plugins maven-compiler-plugin + 3.5.1 7 7 - - false @@ -116,12 +115,12 @@ org.openjdk.jmh jmh-core - 1.10.4 + 1.11.3 org.openjdk.jmh jmh-generator-annprocess - 1.10.4 + 1.11.3 From 3d46a76f6b1f4339f84dee0c099c5f7675f3b6ca Mon Sep 17 00:00:00 2001 From: Arian Treffer Date: Thu, 18 Feb 2016 16:03:36 +0100 Subject: [PATCH 079/815] Failing test to demonstrate bug --- .../java/org/commonmark/test/ParserTest.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index d6a46d58d..6c6915919 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -5,7 +5,6 @@ import org.commonmark.parser.Parser; import org.commonmark.parser.block.*; import org.commonmark.spec.SpecReader; -import org.hamcrest.CoreMatchers; import org.junit.Test; import java.io.IOException; @@ -13,9 +12,9 @@ import java.io.InputStreamReader; import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; public class ParserTest { @@ -47,6 +46,35 @@ public void customBlockParserFactory() { assertEquals("hey", ((Text) document.getFirstChild().getFirstChild()).getLiteral()); assertThat(document.getLastChild(), instanceOf(DashBlock.class)); } + + @Test + public void indentation() { + String given = " - 1 space\n - 3 spaces\n - 5 spaces\n\t - tab + space"; + Parser parser = Parser.builder().build(); + Node document = parser.parse(given); + + assertThat(document.getFirstChild(), instanceOf(BulletList.class)); + + Node list = document.getFirstChild(); // first level list + assertEquals("expect one child", list.getFirstChild(), list.getLastChild()); + assertEquals("1 space", firstText(list.getFirstChild())); + + list = list.getFirstChild().getLastChild(); // second level list + assertEquals("expect one child", list.getFirstChild(), list.getLastChild()); + assertEquals("3 spaces", firstText(list.getFirstChild())); + + list = list.getFirstChild().getLastChild(); // third level list + assertEquals("5 spaces", firstText(list.getFirstChild())); + assertEquals("tab + space", firstText(list.getFirstChild().getNext())); + } + + private String firstText(Node n) { + while (!(n instanceof Text)) { + assertThat(n, notNullValue()); + n = n.getFirstChild(); + } + return ((Text) n).getLiteral(); + } private static class DashBlock extends CustomBlock { } From fdcc279b0f278d836e096ea6fb48e4e6ff898f65 Mon Sep 17 00:00:00 2001 From: Arian Treffer Date: Thu, 18 Feb 2016 16:29:57 +0100 Subject: [PATCH 080/815] Bug fixed for tabbed columns --- .../main/java/org/commonmark/internal/DocumentParser.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 27dbde7cf..29a6c5de8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -297,6 +297,11 @@ private void setNewColumn(int newColumn) { while (column < newColumn && index != line.length()) { advance(); } + if (column > newColumn) { + // Last character was a tab and we overshot our target + index--; + column = newColumn; + } } private void advance() { From e6eb8db7166eb103976356d0024ec681e009cede Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Fri, 19 Feb 2016 15:54:07 +1100 Subject: [PATCH 081/815] Initial commit. Adding header ids to all h tags based on their natural english --- .../headerids/HeaderIdAttributeProvider.java | 88 +++++++++++++++++++ .../src/main/javadoc/overview.html | 6 ++ .../commonmark/ext/header/HeaderIdTest.java | 64 ++++++++++++++ pom.xml | 1 + 4 files changed, 159 insertions(+) create mode 100644 commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java create mode 100644 commonmark-ext-header-ids/src/main/javadoc/overview.html create mode 100644 commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java new file mode 100644 index 000000000..a8b438126 --- /dev/null +++ b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java @@ -0,0 +1,88 @@ +package org.commonmark.ext.headerids; + +import org.commonmark.html.AttributeProvider; +import org.commonmark.node.*; + +import java.util.*; + +/** + * Extension for automatically turning plain URLs and email addresses into links. + *

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

    + *

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

    + */ +public class HeaderIdAttributeProvider implements AttributeProvider { + + private final Map headingMap; + + private HeaderIdAttributeProvider() { + headingMap = new HashMap<>(); + } + + public static AttributeProvider create() { + return new HeaderIdAttributeProvider(); + } + + private class IdAttribute { + StringBuilder sb = new StringBuilder(); + + public void add(String s) { + + // Do some basic substitution + s = s.toLowerCase(); + s = s.replaceAll(" +", "-"); + + for(char c: s.toCharArray()) { + if(0x0041 < c && c < 0x007A || c == '-' || c == '_') { + sb.append(c); + } + } + } + + public String getUniqueHeader(Map headingMap) { + String currentValue = toString(); + if(!headingMap.containsKey(currentValue)) { + headingMap.put(currentValue, 1); + return currentValue; + } else { + int currentCount = headingMap.get(currentValue); + headingMap.put(currentValue, currentCount + 1); + return currentValue + currentCount; + } + + } + + @Override + public String toString() { + return sb.toString(); + } + } + + @Override + public void setAttributes(Node node, final Map attributes) { + + if(node instanceof Heading) { + + final IdAttribute idAttribute = new IdAttribute(); + + node.accept(new AbstractVisitor() { + @Override + public void visit(Text text) { + idAttribute.add(text.getLiteral()); + } + + @Override + public void visit(Code code) { + idAttribute.add(code.getLiteral()); + } + }); + + attributes.put("id", idAttribute.getUniqueHeader(headingMap)); + } + } +} diff --git a/commonmark-ext-header-ids/src/main/javadoc/overview.html b/commonmark-ext-header-ids/src/main/javadoc/overview.html new file mode 100644 index 000000000..e98452c0c --- /dev/null +++ b/commonmark-ext-header-ids/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for automatically adding Id attributes to all headers +

    See {@link org.commonmark.ext.autolink.AutolinkExtension}

    + + diff --git a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java new file mode 100644 index 000000000..b6ae26b85 --- /dev/null +++ b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java @@ -0,0 +1,64 @@ +package org.commonmark.ext.headerids; + +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; +import org.commonmark.test.RenderingTestCase; +import org.junit.Before; +import org.junit.Test; + +public class HeaderIdTest extends RenderingTestCase { + + private static final Parser PARSER = Parser.builder().build(); + private static HtmlRenderer RENDERER = HtmlRenderer.builder().attributeProvider(HeaderIdAttributeProvider.create()).build(); + + @Before + public void resetHeader() { + RENDERER = HtmlRenderer.builder() + .attributeProvider(HeaderIdAttributeProvider.create()) + .build(); + } + + @Test + public void baseCaseSingleHeader() { + assertRendering("# Heading here\n", + "

    Heading here

    \n"); + } + + @Test + public void singleHeaderWithCodeBlock() { + assertRendering("Hi there\n# Heading `here`\n", + "

    Hi there

    \n

    Heading here

    \n"); + } + + @Test + public void duplicateHeadersMakeUniqueIds() { + assertRendering("# Heading here\n# Heading here", + "

    Heading here

    \n

    Heading here

    \n"); + } + + @Test + public void duplicateHeadersOnceDissalowedCharactersMakeUniqueIds() { + assertRendering("# Hi there\n" + + "# ∂Hi å∂There˚∂ˆ´¨", "

    Hi there

    \n" + + "

    ∂Hi å∂There˚∂ˆ´¨

    \n"); + } + + @Test + public void testCaseIsIgnoredWhenComparingIds() { + assertRendering("# HEADING here\n" + + "# heading here", + "

    HEADING here

    \n" + + "

    heading here

    \n"); + } + + @Test + public void testNestedBlocks() { + assertRendering("## `h` `e` **l** *l* o", + "

    h e l l o

    "); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/pom.xml b/pom.xml index 1d5ee2c14..a1f400099 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ commonmark-ext-autolink commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables + commonmark-ext-header-ids commonmark-integration-test commonmark-test-util From 2a19e1893587f44e5250ba21282c3dc9184e9f1a Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Fri, 19 Feb 2016 15:58:32 +1100 Subject: [PATCH 082/815] Adding the new pom.xml and altering the description to match the Header provider --- commonmark-ext-header-ids/pom.xml | 27 +++++++++++++++++++ .../headerids/HeaderIdAttributeProvider.java | 13 +++------ 2 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 commonmark-ext-header-ids/pom.xml diff --git a/commonmark-ext-header-ids/pom.xml b/commonmark-ext-header-ids/pom.xml new file mode 100644 index 000000000..bdf6f2f79 --- /dev/null +++ b/commonmark-ext-header-ids/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + com.atlassian.commonmark + commonmark-parent + 0.4.2-SNAPSHOT + + + commonmark-ext-header-ids + commonmark-java extension for adding id attributes to h tags + commonmark-java extension for adding unique id attributes to header tags + + + + com.atlassian.commonmark + commonmark + + + + com.atlassian.commonmark + commonmark-test-util + test + + + + diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java index a8b438126..2f6037f0c 100644 --- a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java +++ b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java @@ -6,15 +6,10 @@ import java.util.*; /** - * Extension for automatically turning plain URLs and email addresses into links. - *

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

    - *

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

    + * Extension for automatically adding ids to every h tag + *

    + * Create via {@link #create()} and add it to your {@link org.commonmark.html.HtmlRenderer} + * ({@link org.commonmark.html.HtmlRenderer.Builder#attributeProvider(AttributeProvider)} */ public class HeaderIdAttributeProvider implements AttributeProvider { From 45f0d2e5b12fb8e4d6d14a52e2fa616f30ef8ab8 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Fri, 19 Feb 2016 19:46:47 +1100 Subject: [PATCH 083/815] Fixing test, forgot newline in the comparison --- .../src/test/java/org/commonmark/ext/header/HeaderIdTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java index b6ae26b85..36551a84b 100644 --- a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java +++ b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java @@ -54,7 +54,7 @@ public void testCaseIsIgnoredWhenComparingIds() { @Test public void testNestedBlocks() { assertRendering("## `h` `e` **l** *l* o", - "

    h e l l o

    "); + "

    h e l l o

    \n"); } @Override From d50993937976d6d1817a285085a5a43b6bb2727a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 22 Feb 2016 11:20:13 +1100 Subject: [PATCH 084/815] Fix build properties example in readme --- commonmark-android-test/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 8400cb44a..12c0005e9 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -30,7 +30,7 @@ path.report=../report # Version number of commonmark and extensions in maven central. version.maven=0.4.1 # Version number of autolink in maven central (not bundled with extension jar). -version.snapshot_autolink=0.4.0 +version.maven_autolink=0.4.0 # Version number of commonmark and extensions in project. version.snapshot=0.4.2-SNAPSHOT From 3458712282f4c40a05405737c049ca204cb63570 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 22 Feb 2016 11:48:11 +1100 Subject: [PATCH 085/815] Fix mvn command instructions --- commonmark-android-test/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 12c0005e9..4448e1c92 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -57,7 +57,8 @@ on Windows: #### Run test with SNAPSHOT version -Before running tests you need to run `maven install` +Before running tests you need to run `mvn clean install` in the root of +this repository. on Mac/Linux: ```shell From 78f6f42bdb4d373c10f75630c635d08fca3c434d Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Mon, 22 Feb 2016 13:43:47 +1100 Subject: [PATCH 086/815] Reformatting to be in line with current style --- .../headerids/HeaderIdAttributeProvider.java | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java index 2f6037f0c..f3b35b741 100644 --- a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java +++ b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java @@ -19,10 +19,38 @@ private HeaderIdAttributeProvider() { headingMap = new HashMap<>(); } - public static AttributeProvider create() { + public static HeaderIdAttributeProvider create() { return new HeaderIdAttributeProvider(); } + + @Override + public void setAttributes(Node node, final Map attributes) { + + if (node instanceof Heading) { + + final IdAttribute idAttribute = new IdAttribute(); + + node.accept(new AbstractVisitor() { + @Override + public void visit(Text text) { + idAttribute.add(text.getLiteral()); + } + + @Override + public void visit(Code code) { + idAttribute.add(code.getLiteral()); + } + + + + + }); + + attributes.put("id", idAttribute.getUniqueHeader(headingMap)); + } + } + private class IdAttribute { StringBuilder sb = new StringBuilder(); @@ -32,8 +60,8 @@ public void add(String s) { s = s.toLowerCase(); s = s.replaceAll(" +", "-"); - for(char c: s.toCharArray()) { - if(0x0041 < c && c < 0x007A || c == '-' || c == '_') { + for (char c : s.toCharArray()) { + if (0x0041 < c && c < 0x007A || c == '-' || c == '_') { sb.append(c); } } @@ -49,7 +77,6 @@ public String getUniqueHeader(Map headingMap) { headingMap.put(currentValue, currentCount + 1); return currentValue + currentCount; } - } @Override @@ -57,27 +84,4 @@ public String toString() { return sb.toString(); } } - - @Override - public void setAttributes(Node node, final Map attributes) { - - if(node instanceof Heading) { - - final IdAttribute idAttribute = new IdAttribute(); - - node.accept(new AbstractVisitor() { - @Override - public void visit(Text text) { - idAttribute.add(text.getLiteral()); - } - - @Override - public void visit(Code code) { - idAttribute.add(code.getLiteral()); - } - }); - - attributes.put("id", idAttribute.getUniqueHeader(headingMap)); - } - } } From 432314840b47f50ca0c80c5d4a35c125561ae484 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Mon, 22 Feb 2016 13:44:56 +1100 Subject: [PATCH 087/815] Adding default header id for the case that there are no printable characters --- .../headerids/HeaderIdAttributeProvider.java | 19 +++++++++++++++++- .../commonmark/ext/header/HeaderIdTest.java | 20 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java index f3b35b741..353312a33 100644 --- a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java +++ b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java @@ -14,15 +14,28 @@ public class HeaderIdAttributeProvider implements AttributeProvider { private final Map headingMap; + private String defaultHeading; private HeaderIdAttributeProvider() { + this("heading"); + } + + private HeaderIdAttributeProvider(String defaultHeading) { headingMap = new HashMap<>(); + this.defaultHeading = defaultHeading; } public static HeaderIdAttributeProvider create() { return new HeaderIdAttributeProvider(); } + public String getDefaultHeading() { + return defaultHeading; + } + + public void setDefaultHeading(String defaultHeading) { + this.defaultHeading = defaultHeading; + } @Override public void setAttributes(Node node, final Map attributes) { @@ -69,7 +82,11 @@ public void add(String s) { public String getUniqueHeader(Map headingMap) { String currentValue = toString(); - if(!headingMap.containsKey(currentValue)) { + if (currentValue.length() == 0) { + currentValue = defaultHeading; + } + + if (!headingMap.containsKey(currentValue)) { headingMap.put(currentValue, 1); return currentValue; } else { diff --git a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java index 36551a84b..31ea1b976 100644 --- a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java +++ b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java @@ -1,5 +1,6 @@ package org.commonmark.ext.headerids; +import org.commonmark.html.AttributeProvider; import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; @@ -9,7 +10,8 @@ public class HeaderIdTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().build(); - private static HtmlRenderer RENDERER = HtmlRenderer.builder().attributeProvider(HeaderIdAttributeProvider.create()).build(); + private static final HeaderIdAttributeProvider attributeProvider = HeaderIdAttributeProvider.create(); + private static HtmlRenderer RENDERER = HtmlRenderer.builder().attributeProvider(attributeProvider).build(); @Before public void resetHeader() { @@ -57,6 +59,22 @@ public void testNestedBlocks() { "

    h e l l o

    \n"); } + @Test + public void noPrintableCharacters() { + assertRendering("# ∂∂ƒƒ", + "

    ∂∂ƒƒ

    \n"); + } + + @Test + public void boldEmphasisCharacters() { + assertRendering("# _hello_ **there**", "

    hello there

    \n"); + } + + @Test + public void testStrongEmphasis() { + assertRendering("# _**Hi there**_", "

    Hi there

    \n"); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); From d0ca05659269442536dff6738cab5bc56a82009d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 22 Feb 2016 14:26:44 +1100 Subject: [PATCH 088/815] README: Use new URL for GFM tables help --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a8e793a0..8967643e4 100644 --- a/README.md +++ b/README.md @@ -172,4 +172,4 @@ BSD (2-clause) licensed, see LICENSE.txt file. [Maven Central]: https://search.maven.org/#search|ga|1|g%3A%22com.atlassian.commonmark%22 [Semantic Versioning]: http://semver.org/ [autolink-java]: https://github.com/robinst/autolink-java -[gfm-tables]: https://help.github.com/articles/github-flavored-markdown/#tables +[gfm-tables]: https://help.github.com/articles/organizing-information-with-tables/ From b958ebf7b502b6992ad44f776ccf590d9657d883 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Fri, 9 Oct 2015 15:50:57 +0200 Subject: [PATCH 089/815] Add Metadata extension --- README.md | 20 +++ commonmark-ext-metadata/pom.xml | 29 ++++ .../ext/metadata/MetadataBlock.java | 6 + .../ext/metadata/MetadataExtension.java | 26 +++ .../commonmark/ext/metadata/MetadataNode.java | 26 +++ .../ext/metadata/MetadataVisitor.java | 29 ++++ .../internal/MetadataBlockParser.java | 112 ++++++++++++ .../internal/MetadataBlockRenderer.java | 15 ++ .../src/main/javadoc/overview.html | 6 + .../commonmark/ext/metadata/MetadataTest.java | 160 ++++++++++++++++++ pom.xml | 1 + 11 files changed, 430 insertions(+) create mode 100644 commonmark-ext-metadata/pom.xml create mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataBlock.java create mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java create mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java create mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java create mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java create mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockRenderer.java create mode 100644 commonmark-ext-metadata/src/main/javadoc/overview.html create mode 100644 commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java diff --git a/README.md b/README.md index 8967643e4..c784cafe6 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,26 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. +### Metadata + +Enables metadata block in YAML format. This extension only supports limited features. The example is following: + +``` +--- +key: value +list: + - value 1 + - value 2 +literal: | + this is literal value. + + literal values 2 +--- + +document start this +``` + +Use class `MetadataExtension` in artifact `commonmark-ext-metadata`. To fetch metadata, use `MetadataVisitor`. Contributing ------------ diff --git a/commonmark-ext-metadata/pom.xml b/commonmark-ext-metadata/pom.xml new file mode 100644 index 000000000..90113deef --- /dev/null +++ b/commonmark-ext-metadata/pom.xml @@ -0,0 +1,29 @@ + + + + commonmark-parent + com.atlassian.commonmark + 0.2.1-SNAPSHOT + + 4.0.0 + + commonmark-ext-metadata + commonmark-java extension for metadata + commonmark-java extension for metadata + + + + com.atlassian.commonmark + commonmark + + + + com.atlassian.commonmark + commonmark-test-util + test + + + + diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataBlock.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataBlock.java new file mode 100644 index 000000000..d9dba4ff6 --- /dev/null +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataBlock.java @@ -0,0 +1,6 @@ +package org.commonmark.ext.metadata; + +import org.commonmark.node.CustomBlock; + +public class MetadataBlock extends CustomBlock { +} diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java new file mode 100644 index 000000000..2d104db9f --- /dev/null +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java @@ -0,0 +1,26 @@ +package org.commonmark.ext.metadata; + +import org.commonmark.Extension; +import org.commonmark.ext.metadata.internal.MetadataBlockParser; +import org.commonmark.ext.metadata.internal.MetadataBlockRenderer; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; + +public class MetadataExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { + private MetadataExtension() { + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.customHtmlRenderer(new MetadataBlockRenderer()); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customBlockParserFactory(new MetadataBlockParser.Factory()); + } + + public static Extension create() { + return new MetadataExtension(); + } +} diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java new file mode 100644 index 000000000..146785c5b --- /dev/null +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java @@ -0,0 +1,26 @@ +package org.commonmark.ext.metadata; + +import org.commonmark.node.CustomNode; + +import java.util.List; + +public class MetadataNode extends CustomNode { + private String key; + private List values; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } +} diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java new file mode 100644 index 000000000..8b5e4630e --- /dev/null +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java @@ -0,0 +1,29 @@ +package org.commonmark.ext.metadata; + +import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.CustomNode; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MetadataVisitor extends AbstractVisitor { + private Map> data; + + public MetadataVisitor() { + data = new HashMap<>(); + } + + @Override + public void visit(CustomNode customNode) { + if (customNode instanceof MetadataNode) { + data.put(((MetadataNode) customNode).getKey(), ((MetadataNode) customNode).getValues()); + } else { + super.visit(customNode); + } + } + + public Map> getData() { + return data; + } +} diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java new file mode 100644 index 000000000..d0bc2285f --- /dev/null +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java @@ -0,0 +1,112 @@ +package org.commonmark.ext.metadata.internal; + +import org.commonmark.ext.metadata.MetadataBlock; +import org.commonmark.ext.metadata.MetadataNode; +import org.commonmark.node.Block; +import org.commonmark.parser.InlineParser; +import org.commonmark.parser.block.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MetadataBlockParser extends AbstractBlockParser { + private static final Pattern REGEX_METADATA = Pattern.compile("^[ ]{0,3}([A-Za-z0-9_-]+):\\s*(.*)"); + private static final Pattern REGEX_METADATA_LIST = Pattern.compile("^[ ]+-\\s*(.*)"); + private static final Pattern REGEX_METADATA_LITERAL = Pattern.compile("^\\s*(.*)"); + private static final Pattern REGEX_BOUNDARY = Pattern.compile("^(-{3,}|\\.{3,})(\\s*)?"); + + private List lines; + private MetadataBlock block; + private boolean literal; + + public MetadataBlockParser() { + lines = new ArrayList<>(); + block = new MetadataBlock(); + literal = false; + } + + @Override + public Block getBlock() { + return block; + } + + @Override + public void addLine(CharSequence line) { + lines.add(line.toString()); + } + + @Override + public BlockContinue tryContinue(ParserState parserState) { + if (REGEX_BOUNDARY.matcher(parserState.getLine()).matches()) { + return BlockContinue.finished(); + } + return BlockContinue.atIndex(parserState.getIndex()); + } + + @Override + public void parseInlines(InlineParser inlineParser) { + String key = null; + List values = new ArrayList<>(); + + for (String line : lines) { + Matcher matcher = REGEX_BOUNDARY.matcher(line); + if (matcher.matches()) { + continue; + } + + matcher = REGEX_METADATA.matcher(line); + if (matcher.matches()) { + if (key != null) { + MetadataNode node = new MetadataNode(); + node.setKey(key); + node.setValues(values); + block.appendChild(node); + } + + literal = false; + key = matcher.group(1); + values = new ArrayList<>(); + if ("|".equals(matcher.group(2))) { + literal = true; + } else if (!"".equals(matcher.group(2))) { + values.add(matcher.group(2)); + } + } else { + matcher = REGEX_METADATA_LIST.matcher(line); + if (matcher.matches()) { + values.add(matcher.group(1)); + } else if (literal) { + matcher = REGEX_METADATA_LITERAL.matcher(line); + if (matcher.matches()) { + if (values.size() == 1) { + values.set(0, values.get(0) + "\n" + matcher.group(1).trim()); + } else { + values.add(matcher.group(1).trim()); + } + } + } + } + } + + if (key != null) { + MetadataNode node = new MetadataNode(); + node.setKey(key); + node.setValues(values); + block.appendChild(node); + } + } + + public static class Factory extends AbstractBlockParserFactory { + @Override + public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + String line = state.getLine().toString(); + if (matchedBlockParser.getParagraphStartLine() == null && REGEX_BOUNDARY.matcher(line).matches()) { + return BlockStart.of(new MetadataBlockParser()).atIndex(state.getNextNonSpaceIndex()); + } + + return BlockStart.none(); + } + } +} diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockRenderer.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockRenderer.java new file mode 100644 index 000000000..362c2602b --- /dev/null +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockRenderer.java @@ -0,0 +1,15 @@ +package org.commonmark.ext.metadata.internal; + +import org.commonmark.ext.metadata.MetadataBlock; +import org.commonmark.ext.metadata.MetadataNode; +import org.commonmark.html.CustomHtmlRenderer; +import org.commonmark.html.HtmlWriter; +import org.commonmark.node.Node; +import org.commonmark.node.Visitor; + +public class MetadataBlockRenderer implements CustomHtmlRenderer { + @Override + public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { + return node instanceof MetadataBlock || node instanceof MetadataNode; + } +} diff --git a/commonmark-ext-metadata/src/main/javadoc/overview.html b/commonmark-ext-metadata/src/main/javadoc/overview.html new file mode 100644 index 000000000..0762318ba --- /dev/null +++ b/commonmark-ext-metadata/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for Metadata +

    See {@link org.commonmark.ext.metadata.MetadataExtension}

    + + diff --git a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java new file mode 100644 index 000000000..2363dfe18 --- /dev/null +++ b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java @@ -0,0 +1,160 @@ +package org.commonmark.ext.metadata; + +import org.commonmark.Extension; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.test.RenderingTestCase; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class MetadataTest extends RenderingTestCase { + private static final Set EXTENSIONS = Collections.singleton(MetadataExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void simpleValue() { + final String input = "---" + + "\nhello: world" + + "\n..." + + "\n" + + "\ngreat"; + final String rendered = "

    great

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertEquals(1, data.size()); + assertEquals("hello", data.keySet().iterator().next()); + assertEquals(1, data.get("hello").size()); + assertEquals("world", data.get("hello").get(0)); + + assertRendering(input, rendered); + } + + @Test + public void listValues() { + final String input = "---" + + "\nlist:" + + "\n - value1" + + "\n - value2" + + "\n..." + + "\n" + + "\ngreat"; + final String rendered = "

    great

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + 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)); + + assertRendering(input, rendered); + } + + @Test + public void literalValue() { + final String input = "---" + + "\nliteral: |" + + "\n hello markdown!" + + "\n literal thing..." + + "\n---" + + "\n" + + "\ngreat"; + final String rendered = "

    great

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertEquals(1, data.size()); + assertTrue(data.containsKey("literal")); + assertEquals(1, data.get("literal").size()); + assertEquals("hello markdown!\nliteral thing...", data.get("literal").get(0)); + + assertRendering(input, rendered); + } + + @Test + public void complexValues() { + final String input = "---" + + "\nsimple: value" + + "\nliteral: |" + + "\n hello markdown!" + + "\n" + + "\n literal literal" + + "\nlist:" + + "\n - value1" + + "\n - value2" + + "\n---" + + "\ngreat"; + final String rendered = "

    great

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertEquals(3, data.size()); + + assertTrue(data.containsKey("simple")); + assertEquals(1, data.get("simple").size()); + assertEquals("value", data.get("simple").get(0)); + + assertTrue(data.containsKey("literal")); + assertEquals(1, data.get("literal").size()); + assertEquals("hello markdown!\n\nliteral literal", data.get("literal").get(0)); + + assertTrue(data.containsKey("list")); + assertEquals(2, data.get("list").size()); + assertEquals("value1", data.get("list").get(0)); + assertEquals("value2", data.get("list").get(1)); + + assertRendering(input, rendered); + } + + @Test + public void metadataInParagraph() { + final String input = "# hello\n" + + "\nhello markdown world!" + + "\n---" + + "\nhello: world" + + "\n---"; + final String rendered = "

    hello

    \n

    hello markdown world!

    \n

    hello: world

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertTrue(data.isEmpty()); + + assertRendering(input, rendered); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/pom.xml b/pom.xml index 1d5ee2c14..27524b673 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ commonmark-ext-gfm-tables commonmark-integration-test commonmark-test-util + commonmark-ext-metadata From d1aa3bf0d4a0b0c41298dd23f70adc07095c285f Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Thu, 3 Dec 2015 01:31:43 +0900 Subject: [PATCH 090/815] Address comments --- README.md | 4 +- commonmark-ext-metadata/pom.xml | 4 +- .../ext/metadata/MetadataExtension.java | 11 +++++ .../ext/metadata/MetadataVisitor.java | 4 +- .../internal/MetadataBlockParser.java | 49 ++++++++++++++++--- .../commonmark/ext/metadata/MetadataTest.java | 48 +++++++++++++++++- commonmark-integration-test/pom.xml | 5 ++ .../integration/SpecIntegrationTest.java | 4 +- pom.xml | 7 ++- 9 files changed, 120 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c784cafe6..2bb0af717 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. ### Metadata -Enables metadata block in YAML format. This extension only supports limited features. The example is following: +Enables metadata block in YAML format. This extension only supports a subset of YAML syntax. Here's an example of what's supported: ``` --- @@ -159,7 +159,7 @@ literal: | literal values 2 --- -document start this +document start here ``` Use class `MetadataExtension` in artifact `commonmark-ext-metadata`. To fetch metadata, use `MetadataVisitor`. diff --git a/commonmark-ext-metadata/pom.xml b/commonmark-ext-metadata/pom.xml index 90113deef..31e9e287e 100644 --- a/commonmark-ext-metadata/pom.xml +++ b/commonmark-ext-metadata/pom.xml @@ -2,12 +2,12 @@ + 4.0.0 commonmark-parent com.atlassian.commonmark - 0.2.1-SNAPSHOT + 0.3.1-SNAPSHOT - 4.0.0 commonmark-ext-metadata commonmark-java extension for metadata diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java index 2d104db9f..f0460d3f5 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java @@ -6,6 +6,17 @@ import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; +/** + * Extension for YAML-like metadata. + *

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

    + *

    + * The parsed metadata is turned into {@link MetadataNode}. You can access the metadata using {@link MetadataVisitor}. + *

    + */ public class MetadataExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { private MetadataExtension() { } diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java index 8b5e4630e..63f4296d9 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java @@ -3,7 +3,7 @@ import org.commonmark.node.AbstractVisitor; import org.commonmark.node.CustomNode; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -11,7 +11,7 @@ public class MetadataVisitor extends AbstractVisitor { private Map> data; public MetadataVisitor() { - data = new HashMap<>(); + data = new LinkedHashMap<>(); } @Override diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java index d0bc2285f..3718e19c8 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java @@ -2,6 +2,7 @@ import org.commonmark.ext.metadata.MetadataBlock; import org.commonmark.ext.metadata.MetadataNode; +import org.commonmark.internal.DocumentBlockParser; import org.commonmark.node.Block; import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.*; @@ -74,10 +75,7 @@ public void parseInlines(InlineParser inlineParser) { values.add(matcher.group(2)); } } else { - matcher = REGEX_METADATA_LIST.matcher(line); - if (matcher.matches()) { - values.add(matcher.group(1)); - } else if (literal) { + if (literal) { matcher = REGEX_METADATA_LITERAL.matcher(line); if (matcher.matches()) { if (values.size() == 1) { @@ -86,6 +84,11 @@ public void parseInlines(InlineParser inlineParser) { values.add(matcher.group(1).trim()); } } + } else { + matcher = REGEX_METADATA_LIST.matcher(line); + if (matcher.matches()) { + values.add(matcher.group(1)); + } } } } @@ -101,12 +104,44 @@ public void parseInlines(InlineParser inlineParser) { public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - String line = state.getLine().toString(); - if (matchedBlockParser.getParagraphStartLine() == null && REGEX_BOUNDARY.matcher(line).matches()) { - return BlockStart.of(new MetadataBlockParser()).atIndex(state.getNextNonSpaceIndex()); + CharSequence line = state.getLine(); + 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 && + REGEX_BOUNDARY.matcher(line).matches()) { + // count valid metadata line in metadata block + int prevIndex; + int index = nextLineEnd(line, 0); + int validLineCount = 0; + CharSequence subseq; + do { + prevIndex = index + 1; + index = nextLineEnd(line, prevIndex); + subseq = line.subSequence(prevIndex, index); + if (REGEX_METADATA.matcher(subseq).matches()) { + validLineCount++; + } + } while (!REGEX_BOUNDARY.matcher(subseq).matches() && prevIndex != index); + + if (validLineCount > 0) { + return BlockStart.of(new MetadataBlockParser()).atIndex(state.getNextNonSpaceIndex()); + } } return BlockStart.none(); } + + private int nextLineEnd(CharSequence seq, int startIndex) { + int index = startIndex; + try { + while (seq.charAt(index) != '\n') { + index++; + } + } catch (IndexOutOfBoundsException ignored) { + index--; + } + + return index; + } } } diff --git a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java index 2363dfe18..78e3b2883 100644 --- a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java +++ b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java @@ -43,6 +43,28 @@ public void simpleValue() { assertRendering(input, rendered); } + @Test + public void emptyValue() { + final String input = "---" + + "\nkey:" + + "\n---" + + "\n" + + "\ngreat"; + final String rendered = "

    great

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertEquals(1, data.size()); + assertEquals("key", data.keySet().iterator().next()); + assertEquals(0, data.get("key").size()); + + assertRendering(input, rendered); + } + @Test public void listValues() { final String input = "---" + @@ -70,7 +92,7 @@ public void listValues() { } @Test - public void literalValue() { + public void literalValue1() { final String input = "---" + "\nliteral: |" + "\n hello markdown!" + @@ -94,6 +116,30 @@ public void literalValue() { assertRendering(input, rendered); } + @Test + public void literalValue2() { + final String input = "---" + + "\nliteral: |" + + "\n - hello markdown!" + + "\n---" + + "\n" + + "\ngreat"; + final String rendered = "

    great

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertEquals(1, data.size()); + assertTrue(data.containsKey("literal")); + assertEquals(1, data.get("literal").size()); + assertEquals("- hello markdown!", data.get("literal").get(0)); + + assertRendering(input, rendered); + } + @Test public void complexValues() { final String input = "---" + diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 784a2d026..040179d8d 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -32,6 +32,11 @@ commonmark-ext-gfm-tables test
    + + com.atlassian.commonmark + commonmark-ext-metadata + test + org.pegdown pegdown 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 be5f9d890..e67db809f 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,6 +4,7 @@ import org.commonmark.ext.autolink.AutolinkExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; +import org.commonmark.ext.metadata.MetadataExtension; import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.spec.SpecExample; @@ -20,7 +21,8 @@ public class SpecIntegrationTest extends SpecTestCase { private static final List EXTENSIONS = Arrays.asList( AutolinkExtension.create(), StrikethroughExtension.create(), - TablesExtension.create()); + TablesExtension.create(), + MetadataExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).percentEncodeUrls(true).build(); diff --git a/pom.xml b/pom.xml index 27524b673..953e1b52a 100644 --- a/pom.xml +++ b/pom.xml @@ -24,9 +24,9 @@ commonmark-ext-autolink commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables + commonmark-ext-metadata commonmark-integration-test commonmark-test-util - commonmark-ext-metadata @@ -101,6 +101,11 @@ commonmark-ext-gfm-tables 0.4.2-SNAPSHOT + + com.atlassian.commonmark + commonmark-ext-metadata + 0.3.1-SNAPSHOT + com.atlassian.commonmark commonmark-test-util From 900f5f7818638e293a315c00f98ac12d65bc7627 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Mon, 7 Dec 2015 22:22:47 +0900 Subject: [PATCH 091/815] Address comments - Move `literal` into `parseInlines` method - Fix a bug in `parseInlines` for incomplete block - Simplify construction of `MetadataNode` - Add a test case for incomplete metadata block --- .../commonmark/ext/metadata/MetadataNode.java | 5 +++++ .../metadata/internal/MetadataBlockParser.java | 15 ++++----------- .../commonmark/ext/metadata/MetadataTest.java | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java index 146785c5b..bd6a3fdf3 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java @@ -8,6 +8,11 @@ public class MetadataNode extends CustomNode { private String key; private List values; + public MetadataNode(String key, List values) { + this.key = key; + this.values = values; + } + public String getKey() { return key; } diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java index 3718e19c8..a6ce782c6 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java @@ -20,12 +20,10 @@ public class MetadataBlockParser extends AbstractBlockParser { private List lines; private MetadataBlock block; - private boolean literal; public MetadataBlockParser() { lines = new ArrayList<>(); block = new MetadataBlock(); - literal = false; } @Override @@ -50,6 +48,7 @@ public BlockContinue tryContinue(ParserState parserState) { public void parseInlines(InlineParser inlineParser) { String key = null; List values = new ArrayList<>(); + boolean literal = false; for (String line : lines) { Matcher matcher = REGEX_BOUNDARY.matcher(line); @@ -60,10 +59,7 @@ public void parseInlines(InlineParser inlineParser) { matcher = REGEX_METADATA.matcher(line); if (matcher.matches()) { if (key != null) { - MetadataNode node = new MetadataNode(); - node.setKey(key); - node.setValues(values); - block.appendChild(node); + block.appendChild(new MetadataNode(key, values)); } literal = false; @@ -94,10 +90,7 @@ public void parseInlines(InlineParser inlineParser) { } if (key != null) { - MetadataNode node = new MetadataNode(); - node.setKey(key); - node.setValues(values); - block.appendChild(node); + block.appendChild(new MetadataNode(key, values)); } } @@ -121,7 +114,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar if (REGEX_METADATA.matcher(subseq).matches()) { validLineCount++; } - } while (!REGEX_BOUNDARY.matcher(subseq).matches() && prevIndex != index); + } while (!REGEX_BOUNDARY.matcher(subseq).matches() && prevIndex < index); if (validLineCount > 0) { return BlockStart.of(new MetadataBlockParser()).atIndex(state.getNextNonSpaceIndex()); diff --git a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java index 78e3b2883..d38a698a0 100644 --- a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java +++ b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java @@ -199,6 +199,23 @@ public void metadataInParagraph() { assertRendering(input, rendered); } + @Test + public void unclosedMetadata() { + final String input = "---\n" + + "test"; + final String rendered = "
    \n

    test

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertTrue(data.isEmpty()); + + assertRendering(input, rendered); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); From e04ccdcd3a6099e613e94eaf5690d2640369fd82 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Tue, 22 Dec 2015 22:06:11 +0900 Subject: [PATCH 092/815] Revert checking multilines in tryStart method --- .../internal/MetadataBlockParser.java | 40 +++---------------- .../commonmark/ext/metadata/MetadataTest.java | 26 ++++++------ 2 files changed, 19 insertions(+), 47 deletions(-) diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java index a6ce782c6..0b543afe9 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java @@ -16,7 +16,8 @@ public class MetadataBlockParser extends AbstractBlockParser { private static final Pattern REGEX_METADATA = Pattern.compile("^[ ]{0,3}([A-Za-z0-9_-]+):\\s*(.*)"); private static final Pattern REGEX_METADATA_LIST = Pattern.compile("^[ ]+-\\s*(.*)"); private static final Pattern REGEX_METADATA_LITERAL = Pattern.compile("^\\s*(.*)"); - private static final Pattern REGEX_BOUNDARY = Pattern.compile("^(-{3,}|\\.{3,})(\\s*)?"); + private static final Pattern REGEX_BEGIN = Pattern.compile("^-{3}(\\s.*)?"); + private static final Pattern REGEX_END = Pattern.compile("^(-{3}|\\.{3})(\\s.*)?"); private List lines; private MetadataBlock block; @@ -38,7 +39,7 @@ public void addLine(CharSequence line) { @Override public BlockContinue tryContinue(ParserState parserState) { - if (REGEX_BOUNDARY.matcher(parserState.getLine()).matches()) { + if (REGEX_END.matcher(parserState.getLine()).matches()) { return BlockContinue.finished(); } return BlockContinue.atIndex(parserState.getIndex()); @@ -51,7 +52,7 @@ public void parseInlines(InlineParser inlineParser) { boolean literal = false; for (String line : lines) { - Matcher matcher = REGEX_BOUNDARY.matcher(line); + Matcher matcher = REGEX_END.matcher(line); if (matcher.matches()) { continue; } @@ -101,40 +102,11 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar 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 && - REGEX_BOUNDARY.matcher(line).matches()) { - // count valid metadata line in metadata block - int prevIndex; - int index = nextLineEnd(line, 0); - int validLineCount = 0; - CharSequence subseq; - do { - prevIndex = index + 1; - index = nextLineEnd(line, prevIndex); - subseq = line.subSequence(prevIndex, index); - if (REGEX_METADATA.matcher(subseq).matches()) { - validLineCount++; - } - } while (!REGEX_BOUNDARY.matcher(subseq).matches() && prevIndex < index); - - if (validLineCount > 0) { - return BlockStart.of(new MetadataBlockParser()).atIndex(state.getNextNonSpaceIndex()); - } + REGEX_BEGIN.matcher(line).matches()) { + return BlockStart.of(new MetadataBlockParser()).atIndex(state.getNextNonSpaceIndex()); } return BlockStart.none(); } - - private int nextLineEnd(CharSequence seq, int startIndex) { - int index = startIndex; - try { - while (seq.charAt(index) != '\n') { - index++; - } - } catch (IndexOutOfBoundsException ignored) { - index--; - } - - return index; - } } } diff --git a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java index d38a698a0..5b9a51a1c 100644 --- a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java +++ b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java @@ -201,19 +201,19 @@ public void metadataInParagraph() { @Test public void unclosedMetadata() { - final String input = "---\n" + - "test"; - final String rendered = "
    \n

    test

    \n"; - - MetadataVisitor visitor = new MetadataVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map> data = visitor.getData(); - - assertTrue(data.isEmpty()); - - assertRendering(input, rendered); +// final String input = "---\n" + +// "test"; +// final String rendered = "
    \n

    test

    \n"; +// +// MetadataVisitor visitor = new MetadataVisitor(); +// Node document = PARSER.parse(input); +// document.accept(visitor); +// +// Map> data = visitor.getData(); +// +// assertTrue(data.isEmpty()); +// +// assertRendering(input, rendered); } @Override From a30c087bcb037d98731d539f3c846ed101b36f13 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Tue, 22 Dec 2015 22:08:48 +0900 Subject: [PATCH 093/815] Update version --- commonmark-ext-metadata/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-metadata/pom.xml b/commonmark-ext-metadata/pom.xml index 31e9e287e..02b694ae1 100644 --- a/commonmark-ext-metadata/pom.xml +++ b/commonmark-ext-metadata/pom.xml @@ -6,7 +6,7 @@ commonmark-parent com.atlassian.commonmark - 0.3.1-SNAPSHOT + 0.3.2-SNAPSHOT commonmark-ext-metadata diff --git a/pom.xml b/pom.xml index 953e1b52a..34732806a 100644 --- a/pom.xml +++ b/pom.xml @@ -104,7 +104,7 @@ com.atlassian.commonmark commonmark-ext-metadata - 0.3.1-SNAPSHOT + 0.3.2-SNAPSHOT com.atlassian.commonmark From d607daf710f4d7ceb8ed914907e349cb4d310d48 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Fri, 15 Jan 2016 12:09:52 +0900 Subject: [PATCH 094/815] Add overridden examples and a block decision logic in `tryContinue` method --- .../ext/metadata/internal/MetadataBlockParser.java | 11 ++++++++++- .../commonmark/integration/SpecIntegrationTest.java | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java index 0b543afe9..a911875b6 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java +++ b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java @@ -39,9 +39,18 @@ public void addLine(CharSequence line) { @Override public BlockContinue tryContinue(ParserState parserState) { - if (REGEX_END.matcher(parserState.getLine()).matches()) { + final CharSequence line = parserState.getLine(); + + if (REGEX_END.matcher(line).matches()) { + // if this line is `---` or `...` which means end of metadata block return BlockContinue.finished(); + } else if (!REGEX_METADATA.matcher(line).matches() && !REGEX_METADATA_LIST.matcher(line).matches() && + !REGEX_METADATA_LITERAL.matcher(line).matches()) { + // if this line isn't matched with any metadata contents, then exit this block + return BlockContinue.none(); } + + // this line is matched with one of metadata contents return BlockContinue.atIndex(parserState.getIndex()); } 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 e67db809f..15a6d8b35 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 @@ -69,6 +69,10 @@ private static Map getOverriddenExamples() { // Plain autolink m.put("foo@bar.example.com\n", "
    \n"); + // Metadata block + m.put("---\nFoo\n---\nBar\n---\nBaz\n", "

    Bar

    \n

    Baz

    \n"); + m.put("---\n---\n", ""); + return m; } From 628800c1c68ab1dd0eabb76f7080cee7c7fb83f5 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Fri, 15 Jan 2016 12:15:15 +0900 Subject: [PATCH 095/815] Recover test --- .../commonmark/ext/metadata/MetadataTest.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java index 5b9a51a1c..5975188c8 100644 --- a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java +++ b/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java @@ -200,20 +200,20 @@ public void metadataInParagraph() { } @Test - public void unclosedMetadata() { -// final String input = "---\n" + -// "test"; -// final String rendered = "
    \n

    test

    \n"; -// -// MetadataVisitor visitor = new MetadataVisitor(); -// Node document = PARSER.parse(input); -// document.accept(visitor); -// -// Map> data = visitor.getData(); -// -// assertTrue(data.isEmpty()); -// -// assertRendering(input, rendered); + public void nonMatchedStartTag() { + final String input = "----\n" + + "test"; + final String rendered = "
    \n

    test

    \n"; + + MetadataVisitor visitor = new MetadataVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertTrue(data.isEmpty()); + + assertRendering(input, rendered); } @Override From 4e83c351f03b1542556298a99b3bc9c144b2fcfa Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Wed, 24 Feb 2016 22:03:22 +0900 Subject: [PATCH 096/815] Refactor YAML front matter extension - Rename the extension from `Metadata` to `YAMLFrontMatter` --- .../ext/metadata/MetadataBlock.java | 6 -- .../ext/metadata/MetadataExtension.java | 37 ------- .../internal/MetadataBlockRenderer.java | 15 --- .../src/main/javadoc/overview.html | 6 -- .../pom.xml | 8 +- .../ext/yaml/YAMLFrontMatterBlock.java | 6 ++ .../ext/yaml/YAMLFrontMatterExtension.java | 37 +++++++ .../ext/yaml/YAMLFrontMatterNode.java | 6 +- .../ext/yaml/YAMLFrontMatterVisitor.java | 10 +- .../internal/YAMLFrontMatterBlockParser.java | 99 +++++++++---------- .../YAMLFrontMatterBlockRenderer.java | 15 +++ .../src/main/javadoc/overview.html | 6 ++ .../ext/yaml/YAMLFrontMatterTest.java | 24 ++--- commonmark-integration-test/pom.xml | 2 +- .../integration/SpecIntegrationTest.java | 6 +- pom.xml | 6 +- 16 files changed, 143 insertions(+), 146 deletions(-) delete mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataBlock.java delete mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java delete mode 100644 commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockRenderer.java delete mode 100644 commonmark-ext-metadata/src/main/javadoc/overview.html rename {commonmark-ext-metadata => commonmark-ext-yaml-front-matter}/pom.xml (76%) create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterBlock.java create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterExtension.java rename commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java => commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterNode.java (75%) rename commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java => commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterVisitor.java (60%) rename commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java => commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockParser.java (52%) create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockRenderer.java create mode 100644 commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html rename commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java => commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/yaml/YAMLFrontMatterTest.java (89%) diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataBlock.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataBlock.java deleted file mode 100644 index d9dba4ff6..000000000 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataBlock.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.commonmark.ext.metadata; - -import org.commonmark.node.CustomBlock; - -public class MetadataBlock extends CustomBlock { -} diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java deleted file mode 100644 index f0460d3f5..000000000 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataExtension.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.commonmark.ext.metadata; - -import org.commonmark.Extension; -import org.commonmark.ext.metadata.internal.MetadataBlockParser; -import org.commonmark.ext.metadata.internal.MetadataBlockRenderer; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.parser.Parser; - -/** - * Extension for YAML-like metadata. - *

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

    - *

    - * The parsed metadata is turned into {@link MetadataNode}. You can access the metadata using {@link MetadataVisitor}. - *

    - */ -public class MetadataExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { - private MetadataExtension() { - } - - @Override - public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.customHtmlRenderer(new MetadataBlockRenderer()); - } - - @Override - public void extend(Parser.Builder parserBuilder) { - parserBuilder.customBlockParserFactory(new MetadataBlockParser.Factory()); - } - - public static Extension create() { - return new MetadataExtension(); - } -} diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockRenderer.java b/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockRenderer.java deleted file mode 100644 index 362c2602b..000000000 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockRenderer.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.commonmark.ext.metadata.internal; - -import org.commonmark.ext.metadata.MetadataBlock; -import org.commonmark.ext.metadata.MetadataNode; -import org.commonmark.html.CustomHtmlRenderer; -import org.commonmark.html.HtmlWriter; -import org.commonmark.node.Node; -import org.commonmark.node.Visitor; - -public class MetadataBlockRenderer implements CustomHtmlRenderer { - @Override - public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { - return node instanceof MetadataBlock || node instanceof MetadataNode; - } -} diff --git a/commonmark-ext-metadata/src/main/javadoc/overview.html b/commonmark-ext-metadata/src/main/javadoc/overview.html deleted file mode 100644 index 0762318ba..000000000 --- a/commonmark-ext-metadata/src/main/javadoc/overview.html +++ /dev/null @@ -1,6 +0,0 @@ - - -Extension for Metadata -

    See {@link org.commonmark.ext.metadata.MetadataExtension}

    - - diff --git a/commonmark-ext-metadata/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml similarity index 76% rename from commonmark-ext-metadata/pom.xml rename to commonmark-ext-yaml-front-matter/pom.xml index 02b694ae1..ada9956bc 100644 --- a/commonmark-ext-metadata/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -6,12 +6,12 @@ commonmark-parent com.atlassian.commonmark - 0.3.2-SNAPSHOT + 0.4.2-SNAPSHOT - commonmark-ext-metadata - commonmark-java extension for metadata - commonmark-java extension for metadata + commonmark-ext-yaml-front-matter + commonmark-java extension for YAML front matter + commonmark-java extension for YAML front matter diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterBlock.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterBlock.java new file mode 100644 index 000000000..5bd6d738c --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterBlock.java @@ -0,0 +1,6 @@ +package org.commonmark.ext.yaml; + +import org.commonmark.node.CustomBlock; + +public class YAMLFrontMatterBlock extends CustomBlock { +} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterExtension.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterExtension.java new file mode 100644 index 000000000..959e7ea3d --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterExtension.java @@ -0,0 +1,37 @@ +package org.commonmark.ext.yaml; + +import org.commonmark.Extension; +import org.commonmark.ext.yaml.internal.YAMLFrontMatterBlockParser; +import org.commonmark.ext.yaml.internal.YAMLFrontMatterBlockRenderer; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; + +/** + * Extension for YAML-like metadata. + *

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

    + *

    + * The parsed metadata is turned into {@link YAMLFrontMatterNode}. You can access the metadata using {@link YAMLFrontMatterVisitor}. + *

    + */ +public class YAMLFrontMatterExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { + private YAMLFrontMatterExtension() { + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.customHtmlRenderer(new YAMLFrontMatterBlockRenderer()); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customBlockParserFactory(new YAMLFrontMatterBlockParser.Factory()); + } + + public static Extension create() { + return new YAMLFrontMatterExtension(); + } +} diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterNode.java similarity index 75% rename from commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterNode.java index bd6a3fdf3..829263ffb 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataNode.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterNode.java @@ -1,14 +1,14 @@ -package org.commonmark.ext.metadata; +package org.commonmark.ext.yaml; import org.commonmark.node.CustomNode; import java.util.List; -public class MetadataNode extends CustomNode { +public class YAMLFrontMatterNode extends CustomNode { private String key; private List values; - public MetadataNode(String key, List values) { + public YAMLFrontMatterNode(String key, List values) { this.key = key; this.values = values; } diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterVisitor.java similarity index 60% rename from commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterVisitor.java index 63f4296d9..f9da72a57 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/MetadataVisitor.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterVisitor.java @@ -1,4 +1,4 @@ -package org.commonmark.ext.metadata; +package org.commonmark.ext.yaml; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.CustomNode; @@ -7,17 +7,17 @@ import java.util.List; import java.util.Map; -public class MetadataVisitor extends AbstractVisitor { +public class YAMLFrontMatterVisitor extends AbstractVisitor { private Map> data; - public MetadataVisitor() { + public YAMLFrontMatterVisitor() { data = new LinkedHashMap<>(); } @Override public void visit(CustomNode customNode) { - if (customNode instanceof MetadataNode) { - data.put(((MetadataNode) customNode).getKey(), ((MetadataNode) customNode).getValues()); + if (customNode instanceof YAMLFrontMatterNode) { + data.put(((YAMLFrontMatterNode) customNode).getKey(), ((YAMLFrontMatterNode) customNode).getValues()); } else { super.visit(customNode); } diff --git a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockParser.java similarity index 52% rename from commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockParser.java index a911875b6..49b35fc15 100644 --- a/commonmark-ext-metadata/src/main/java/org/commonmark/ext/metadata/internal/MetadataBlockParser.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockParser.java @@ -1,7 +1,7 @@ -package org.commonmark.ext.metadata.internal; +package org.commonmark.ext.yaml.internal; -import org.commonmark.ext.metadata.MetadataBlock; -import org.commonmark.ext.metadata.MetadataNode; +import org.commonmark.ext.yaml.YAMLFrontMatterBlock; +import org.commonmark.ext.yaml.YAMLFrontMatterNode; import org.commonmark.internal.DocumentBlockParser; import org.commonmark.node.Block; import org.commonmark.parser.InlineParser; @@ -12,19 +12,25 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class MetadataBlockParser extends AbstractBlockParser { +public class YAMLFrontMatterBlockParser extends AbstractBlockParser { private static final Pattern REGEX_METADATA = Pattern.compile("^[ ]{0,3}([A-Za-z0-9_-]+):\\s*(.*)"); private static final Pattern REGEX_METADATA_LIST = Pattern.compile("^[ ]+-\\s*(.*)"); private static final Pattern REGEX_METADATA_LITERAL = Pattern.compile("^\\s*(.*)"); private static final Pattern REGEX_BEGIN = Pattern.compile("^-{3}(\\s.*)?"); private static final Pattern REGEX_END = Pattern.compile("^(-{3}|\\.{3})(\\s.*)?"); - private List lines; - private MetadataBlock block; - - public MetadataBlockParser() { - lines = new ArrayList<>(); - block = new MetadataBlock(); + private boolean inYAMLBlock; + private boolean inLiteral; + private String currentKey; + private List currentValues; + private YAMLFrontMatterBlock block; + + public YAMLFrontMatterBlockParser() { + inYAMLBlock = true; + inLiteral = false; + currentKey = null; + currentValues = new ArrayList<>(); + block = new YAMLFrontMatterBlock(); } @Override @@ -34,74 +40,65 @@ public Block getBlock() { @Override public void addLine(CharSequence line) { - lines.add(line.toString()); } @Override public BlockContinue tryContinue(ParserState parserState) { final CharSequence line = parserState.getLine(); - if (REGEX_END.matcher(line).matches()) { - // if this line is `---` or `...` which means end of metadata block - return BlockContinue.finished(); - } else if (!REGEX_METADATA.matcher(line).matches() && !REGEX_METADATA_LIST.matcher(line).matches() && - !REGEX_METADATA_LITERAL.matcher(line).matches()) { - // if this line isn't matched with any metadata contents, then exit this block - return BlockContinue.none(); - } - - // this line is matched with one of metadata contents - return BlockContinue.atIndex(parserState.getIndex()); - } - - @Override - public void parseInlines(InlineParser inlineParser) { - String key = null; - List values = new ArrayList<>(); - boolean literal = false; - - for (String line : lines) { - Matcher matcher = REGEX_END.matcher(line); - if (matcher.matches()) { - continue; + if (inYAMLBlock) { + if (REGEX_END.matcher(line).matches()) { + if (currentKey != null) { + block.appendChild(new YAMLFrontMatterNode(currentKey, currentValues)); + } + return BlockContinue.finished(); } - matcher = REGEX_METADATA.matcher(line); + Matcher matcher = REGEX_METADATA.matcher(line); if (matcher.matches()) { - if (key != null) { - block.appendChild(new MetadataNode(key, values)); + if (currentKey != null) { + block.appendChild(new YAMLFrontMatterNode(currentKey, currentValues)); } - literal = false; - key = matcher.group(1); - values = new ArrayList<>(); + inLiteral = false; + currentKey = matcher.group(1); + currentValues = new ArrayList<>(); if ("|".equals(matcher.group(2))) { - literal = true; + inLiteral = true; } else if (!"".equals(matcher.group(2))) { - values.add(matcher.group(2)); + currentValues.add(matcher.group(2)); } + + return BlockContinue.atIndex(parserState.getIndex()); } else { - if (literal) { + if (inLiteral) { matcher = REGEX_METADATA_LITERAL.matcher(line); if (matcher.matches()) { - if (values.size() == 1) { - values.set(0, values.get(0) + "\n" + matcher.group(1).trim()); + if (currentValues.size() == 1) { + currentValues.set(0, currentValues.get(0) + "\n" + matcher.group(1).trim()); } else { - values.add(matcher.group(1).trim()); + currentValues.add(matcher.group(1).trim()); } } } else { matcher = REGEX_METADATA_LIST.matcher(line); if (matcher.matches()) { - values.add(matcher.group(1)); + currentValues.add(matcher.group(1)); } } + + return BlockContinue.atIndex(parserState.getIndex()); } + } else if (REGEX_BEGIN.matcher(line).matches()) { + inYAMLBlock = true; + return BlockContinue.atIndex(parserState.getIndex()); } - if (key != null) { - block.appendChild(new MetadataNode(key, values)); - } + return BlockContinue.none(); + } + + @Override + public void parseInlines(InlineParser inlineParser) { } public static class Factory extends AbstractBlockParserFactory { @@ -112,7 +109,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar // check whether this line is the first line of whole document or not if (parentParser instanceof DocumentBlockParser && parentParser.getBlock().getFirstChild() == null && REGEX_BEGIN.matcher(line).matches()) { - return BlockStart.of(new MetadataBlockParser()).atIndex(state.getNextNonSpaceIndex()); + return BlockStart.of(new YAMLFrontMatterBlockParser()).atIndex(state.getNextNonSpaceIndex()); } return BlockStart.none(); diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockRenderer.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockRenderer.java new file mode 100644 index 000000000..610c8a161 --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockRenderer.java @@ -0,0 +1,15 @@ +package org.commonmark.ext.yaml.internal; + +import org.commonmark.ext.yaml.YAMLFrontMatterBlock; +import org.commonmark.ext.yaml.YAMLFrontMatterNode; +import org.commonmark.html.CustomHtmlRenderer; +import org.commonmark.html.HtmlWriter; +import org.commonmark.node.Node; +import org.commonmark.node.Visitor; + +public class YAMLFrontMatterBlockRenderer implements CustomHtmlRenderer { + @Override + public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { + return node instanceof YAMLFrontMatterBlock || node instanceof YAMLFrontMatterNode; + } +} diff --git a/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html b/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html new file mode 100644 index 000000000..53dd60bc5 --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for YAML front matter +

    See {@link org.commonmark.ext.yaml.YAMLFrontMatterExtension}

    + + diff --git a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/yaml/YAMLFrontMatterTest.java similarity index 89% rename from commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java rename to commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/yaml/YAMLFrontMatterTest.java index 5975188c8..125b83c1d 100644 --- a/commonmark-ext-metadata/src/test/java/org/commonmark/ext/metadata/MetadataTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/yaml/YAMLFrontMatterTest.java @@ -1,4 +1,4 @@ -package org.commonmark.ext.metadata; +package org.commonmark.ext.yaml; import org.commonmark.Extension; import org.commonmark.html.HtmlRenderer; @@ -15,8 +15,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class MetadataTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(MetadataExtension.create()); +public class YAMLFrontMatterTest extends RenderingTestCase { + private static final Set EXTENSIONS = Collections.singleton(YAMLFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); @@ -29,7 +29,7 @@ public void simpleValue() { "\ngreat"; final String rendered = "

    great

    \n"; - MetadataVisitor visitor = new MetadataVisitor(); + YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -52,7 +52,7 @@ public void emptyValue() { "\ngreat"; final String rendered = "

    great

    \n"; - MetadataVisitor visitor = new MetadataVisitor(); + YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -76,7 +76,7 @@ public void listValues() { "\ngreat"; final String rendered = "

    great

    \n"; - MetadataVisitor visitor = new MetadataVisitor(); + YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -102,7 +102,7 @@ public void literalValue1() { "\ngreat"; final String rendered = "

    great

    \n"; - MetadataVisitor visitor = new MetadataVisitor(); + YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -126,7 +126,7 @@ public void literalValue2() { "\ngreat"; final String rendered = "

    great

    \n"; - MetadataVisitor visitor = new MetadataVisitor(); + YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -155,7 +155,7 @@ public void complexValues() { "\ngreat"; final String rendered = "

    great

    \n"; - MetadataVisitor visitor = new MetadataVisitor(); + YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -180,7 +180,7 @@ public void complexValues() { } @Test - public void metadataInParagraph() { + public void yamlInParagraph() { final String input = "# hello\n" + "\nhello markdown world!" + "\n---" + @@ -188,7 +188,7 @@ public void metadataInParagraph() { "\n---"; final String rendered = "

    hello

    \n

    hello markdown world!

    \n

    hello: world

    \n"; - MetadataVisitor visitor = new MetadataVisitor(); + YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -205,7 +205,7 @@ public void nonMatchedStartTag() { "test"; final String rendered = "
    \n

    test

    \n"; - MetadataVisitor visitor = new MetadataVisitor(); + YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 040179d8d..7e985e7b1 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -34,7 +34,7 @@
    com.atlassian.commonmark - commonmark-ext-metadata + commonmark-ext-yaml-front-matter test 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 15a6d8b35..a45a4a4cc 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.ext.autolink.AutolinkExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; -import org.commonmark.ext.metadata.MetadataExtension; +import org.commonmark.ext.yaml.YAMLFrontMatterExtension; import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.spec.SpecExample; @@ -22,7 +22,7 @@ public class SpecIntegrationTest extends SpecTestCase { AutolinkExtension.create(), StrikethroughExtension.create(), TablesExtension.create(), - MetadataExtension.create()); + YAMLFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).percentEncodeUrls(true).build(); @@ -69,7 +69,7 @@ private static Map getOverriddenExamples() { // Plain autolink m.put("foo@bar.example.com\n", "

    foo@bar.example.com

    \n"); - // Metadata block + // YAML front matter block m.put("---\nFoo\n---\nBar\n---\nBaz\n", "

    Bar

    \n

    Baz

    \n"); m.put("---\n---\n", ""); diff --git a/pom.xml b/pom.xml index 34732806a..de78615b7 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ commonmark-ext-autolink commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables - commonmark-ext-metadata + commonmark-ext-yaml-front-matter commonmark-integration-test commonmark-test-util @@ -103,8 +103,8 @@
    com.atlassian.commonmark - commonmark-ext-metadata - 0.3.2-SNAPSHOT + commonmark-ext-yaml-front-matter + 0.4.2-SNAPSHOT com.atlassian.commonmark From 2354c89df9d7b79b8e52d9e2eb2732c5c7159446 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Wed, 24 Feb 2016 22:05:57 +0900 Subject: [PATCH 097/815] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2bb0af717..bff006f35 100644 --- a/README.md +++ b/README.md @@ -143,9 +143,9 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. -### Metadata +### YAML front matter -Enables metadata block in YAML format. This extension only supports a subset of YAML syntax. Here's an example of what's supported: +Enables an YAML front matter block. This extension only supports a subset of YAML syntax. Here's an example of what's supported: ``` --- @@ -162,7 +162,7 @@ literal: | document start here ``` -Use class `MetadataExtension` in artifact `commonmark-ext-metadata`. To fetch metadata, use `MetadataVisitor`. +Use class `YAMLFrontMatterExtension` in artifact `commonmark-ext-yaml-front-matter`. To fetch metadata, use `YAMLFrontMatterVisitor`. Contributing ------------ From 7c3202a189b088d02d8369da437ae655044a2706 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Thu, 25 Feb 2016 20:10:42 +0900 Subject: [PATCH 098/815] Rename base package and classes --- README.md | 2 +- .../front/matter/YAMLFrontMatterBlock.java | 6 +++ .../matter}/YAMLFrontMatterNode.java | 6 +-- .../internal/YAMLFrontMatterBlockParser.java | 20 +++++----- .../YAMLFrontMatterBlockRenderer.java | 15 ++++++++ .../ext/yaml/YAMLFrontMatterBlock.java | 6 --- .../ext/yaml/YAMLFrontMatterExtension.java | 37 ------------------- .../ext/yaml/YAMLFrontMatterVisitor.java | 29 --------------- .../YAMLFrontMatterBlockRenderer.java | 15 -------- .../matter}/YAMLFrontMatterTest.java | 22 +++++------ .../integration/SpecIntegrationTest.java | 4 +- 11 files changed, 48 insertions(+), 114 deletions(-) create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterBlock.java rename commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/{yaml => front/matter}/YAMLFrontMatterNode.java (74%) rename commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/{yaml => front/matter}/internal/YAMLFrontMatterBlockParser.java (87%) create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockRenderer.java delete mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterBlock.java delete mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterExtension.java delete mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterVisitor.java delete mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockRenderer.java rename commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/{yaml => front/matter}/YAMLFrontMatterTest.java (90%) diff --git a/README.md b/README.md index bff006f35..c0347d34f 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ literal: | document start here ``` -Use class `YAMLFrontMatterExtension` in artifact `commonmark-ext-yaml-front-matter`. To fetch metadata, use `YAMLFrontMatterVisitor`. +Use class `YamlFrontMatterExtension` in artifact `commonmark-ext-yaml-front-matter`. To fetch metadata, use `YamlFrontMatterVisitor`. Contributing ------------ diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterBlock.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterBlock.java new file mode 100644 index 000000000..0d9aba2d3 --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterBlock.java @@ -0,0 +1,6 @@ +package org.commonmark.ext.front.matter; + +import org.commonmark.node.CustomBlock; + +public class YamlFrontMatterBlock extends CustomBlock { +} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterNode.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterNode.java similarity index 74% rename from commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterNode.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterNode.java index 829263ffb..20eb3baf7 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterNode.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterNode.java @@ -1,14 +1,14 @@ -package org.commonmark.ext.yaml; +package org.commonmark.ext.front.matter; import org.commonmark.node.CustomNode; import java.util.List; -public class YAMLFrontMatterNode extends CustomNode { +public class YamlFrontMatterNode extends CustomNode { private String key; private List values; - public YAMLFrontMatterNode(String key, List values) { + public YamlFrontMatterNode(String key, List values) { this.key = key; this.values = values; } diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockParser.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockParser.java similarity index 87% rename from commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockParser.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockParser.java index 49b35fc15..4de4ae12f 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockParser.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockParser.java @@ -1,7 +1,7 @@ -package org.commonmark.ext.yaml.internal; +package org.commonmark.ext.front.matter.internal; -import org.commonmark.ext.yaml.YAMLFrontMatterBlock; -import org.commonmark.ext.yaml.YAMLFrontMatterNode; +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.parser.InlineParser; @@ -12,7 +12,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class YAMLFrontMatterBlockParser extends AbstractBlockParser { +public class YamlFrontMatterBlockParser extends AbstractBlockParser { private static final Pattern REGEX_METADATA = Pattern.compile("^[ ]{0,3}([A-Za-z0-9_-]+):\\s*(.*)"); private static final Pattern REGEX_METADATA_LIST = Pattern.compile("^[ ]+-\\s*(.*)"); private static final Pattern REGEX_METADATA_LITERAL = Pattern.compile("^\\s*(.*)"); @@ -23,14 +23,14 @@ public class YAMLFrontMatterBlockParser extends AbstractBlockParser { private boolean inLiteral; private String currentKey; private List currentValues; - private YAMLFrontMatterBlock block; + private YamlFrontMatterBlock block; - public YAMLFrontMatterBlockParser() { + public YamlFrontMatterBlockParser() { inYAMLBlock = true; inLiteral = false; currentKey = null; currentValues = new ArrayList<>(); - block = new YAMLFrontMatterBlock(); + block = new YamlFrontMatterBlock(); } @Override @@ -49,7 +49,7 @@ public BlockContinue tryContinue(ParserState parserState) { if (inYAMLBlock) { if (REGEX_END.matcher(line).matches()) { if (currentKey != null) { - block.appendChild(new YAMLFrontMatterNode(currentKey, currentValues)); + block.appendChild(new YamlFrontMatterNode(currentKey, currentValues)); } return BlockContinue.finished(); } @@ -57,7 +57,7 @@ public BlockContinue tryContinue(ParserState parserState) { Matcher matcher = REGEX_METADATA.matcher(line); if (matcher.matches()) { if (currentKey != null) { - block.appendChild(new YAMLFrontMatterNode(currentKey, currentValues)); + block.appendChild(new YamlFrontMatterNode(currentKey, currentValues)); } inLiteral = false; @@ -109,7 +109,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar // check whether this line is the first line of whole document or not if (parentParser instanceof DocumentBlockParser && parentParser.getBlock().getFirstChild() == null && REGEX_BEGIN.matcher(line).matches()) { - return BlockStart.of(new YAMLFrontMatterBlockParser()).atIndex(state.getNextNonSpaceIndex()); + return BlockStart.of(new YamlFrontMatterBlockParser()).atIndex(state.getNextNonSpaceIndex()); } return BlockStart.none(); diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockRenderer.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockRenderer.java new file mode 100644 index 000000000..436401194 --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockRenderer.java @@ -0,0 +1,15 @@ +package org.commonmark.ext.front.matter.internal; + +import org.commonmark.ext.front.matter.YamlFrontMatterBlock; +import org.commonmark.ext.front.matter.YamlFrontMatterNode; +import org.commonmark.html.CustomHtmlRenderer; +import org.commonmark.html.HtmlWriter; +import org.commonmark.node.Node; +import org.commonmark.node.Visitor; + +public class YamlFrontMatterBlockRenderer implements CustomHtmlRenderer { + @Override + public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { + return node instanceof YamlFrontMatterBlock || node instanceof YamlFrontMatterNode; + } +} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterBlock.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterBlock.java deleted file mode 100644 index 5bd6d738c..000000000 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterBlock.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.commonmark.ext.yaml; - -import org.commonmark.node.CustomBlock; - -public class YAMLFrontMatterBlock extends CustomBlock { -} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterExtension.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterExtension.java deleted file mode 100644 index 959e7ea3d..000000000 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterExtension.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.commonmark.ext.yaml; - -import org.commonmark.Extension; -import org.commonmark.ext.yaml.internal.YAMLFrontMatterBlockParser; -import org.commonmark.ext.yaml.internal.YAMLFrontMatterBlockRenderer; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.parser.Parser; - -/** - * Extension for YAML-like metadata. - *

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

    - *

    - * The parsed metadata is turned into {@link YAMLFrontMatterNode}. You can access the metadata using {@link YAMLFrontMatterVisitor}. - *

    - */ -public class YAMLFrontMatterExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { - private YAMLFrontMatterExtension() { - } - - @Override - public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.customHtmlRenderer(new YAMLFrontMatterBlockRenderer()); - } - - @Override - public void extend(Parser.Builder parserBuilder) { - parserBuilder.customBlockParserFactory(new YAMLFrontMatterBlockParser.Factory()); - } - - public static Extension create() { - return new YAMLFrontMatterExtension(); - } -} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterVisitor.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterVisitor.java deleted file mode 100644 index f9da72a57..000000000 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/YAMLFrontMatterVisitor.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.commonmark.ext.yaml; - -import org.commonmark.node.AbstractVisitor; -import org.commonmark.node.CustomNode; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class YAMLFrontMatterVisitor extends AbstractVisitor { - private Map> data; - - public YAMLFrontMatterVisitor() { - data = new LinkedHashMap<>(); - } - - @Override - public void visit(CustomNode customNode) { - if (customNode instanceof YAMLFrontMatterNode) { - data.put(((YAMLFrontMatterNode) customNode).getKey(), ((YAMLFrontMatterNode) customNode).getValues()); - } else { - super.visit(customNode); - } - } - - public Map> getData() { - return data; - } -} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockRenderer.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockRenderer.java deleted file mode 100644 index 610c8a161..000000000 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/yaml/internal/YAMLFrontMatterBlockRenderer.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.commonmark.ext.yaml.internal; - -import org.commonmark.ext.yaml.YAMLFrontMatterBlock; -import org.commonmark.ext.yaml.YAMLFrontMatterNode; -import org.commonmark.html.CustomHtmlRenderer; -import org.commonmark.html.HtmlWriter; -import org.commonmark.node.Node; -import org.commonmark.node.Visitor; - -public class YAMLFrontMatterBlockRenderer implements CustomHtmlRenderer { - @Override - public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { - return node instanceof YAMLFrontMatterBlock || node instanceof YAMLFrontMatterNode; - } -} diff --git a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/yaml/YAMLFrontMatterTest.java b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YAMLFrontMatterTest.java similarity index 90% rename from commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/yaml/YAMLFrontMatterTest.java rename to commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YAMLFrontMatterTest.java index 125b83c1d..619d44be8 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/yaml/YAMLFrontMatterTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YAMLFrontMatterTest.java @@ -1,4 +1,4 @@ -package org.commonmark.ext.yaml; +package org.commonmark.ext.front.matter; import org.commonmark.Extension; import org.commonmark.html.HtmlRenderer; @@ -15,8 +15,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class YAMLFrontMatterTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(YAMLFrontMatterExtension.create()); +public class YamlFrontMatterTest extends RenderingTestCase { + private static final Set EXTENSIONS = Collections.singleton(YamlFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); @@ -29,7 +29,7 @@ public void simpleValue() { "\ngreat"; final String rendered = "

    great

    \n"; - YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -52,7 +52,7 @@ public void emptyValue() { "\ngreat"; final String rendered = "

    great

    \n"; - YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -76,7 +76,7 @@ public void listValues() { "\ngreat"; final String rendered = "

    great

    \n"; - YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -102,7 +102,7 @@ public void literalValue1() { "\ngreat"; final String rendered = "

    great

    \n"; - YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -126,7 +126,7 @@ public void literalValue2() { "\ngreat"; final String rendered = "

    great

    \n"; - YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -155,7 +155,7 @@ public void complexValues() { "\ngreat"; final String rendered = "

    great

    \n"; - YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -188,7 +188,7 @@ public void yamlInParagraph() { "\n---"; final String rendered = "

    hello

    \n

    hello markdown world!

    \n

    hello: world

    \n"; - YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); @@ -205,7 +205,7 @@ public void nonMatchedStartTag() { "test"; final String rendered = "
    \n

    test

    \n"; - YAMLFrontMatterVisitor visitor = new YAMLFrontMatterVisitor(); + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); Node document = PARSER.parse(input); document.accept(visitor); 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 a45a4a4cc..a45e0375d 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.ext.autolink.AutolinkExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; -import org.commonmark.ext.yaml.YAMLFrontMatterExtension; +import org.commonmark.ext.front.matter.YamlFrontMatterExtension; import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.spec.SpecExample; @@ -22,7 +22,7 @@ public class SpecIntegrationTest extends SpecTestCase { AutolinkExtension.create(), StrikethroughExtension.create(), TablesExtension.create(), - YAMLFrontMatterExtension.create()); + YamlFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).percentEncodeUrls(true).build(); From a7918f75653e1c93d1f61638a6eee1eba6dad2f3 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Fri, 26 Feb 2016 18:06:48 +0900 Subject: [PATCH 099/815] Rename files --- ...erBlock.java => YamlFrontMatterBlock.java} | 0 .../matter/YamlFrontMatterExtension.java | 37 +++++++++++++++++++ ...tterNode.java => YamlFrontMatterNode.java} | 0 .../front/matter/YamlFrontMatterVisitor.java | 29 +++++++++++++++ ...r.java => YamlFrontMatterBlockParser.java} | 0 ...java => YamlFrontMatterBlockRenderer.java} | 0 ...tterTest.java => YamlFrontMatterTest.java} | 0 7 files changed, 66 insertions(+) rename commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/{YAMLFrontMatterBlock.java => YamlFrontMatterBlock.java} (100%) create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java rename commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/{YAMLFrontMatterNode.java => YamlFrontMatterNode.java} (100%) create mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterVisitor.java rename commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/{YAMLFrontMatterBlockParser.java => YamlFrontMatterBlockParser.java} (100%) rename commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/{YAMLFrontMatterBlockRenderer.java => YamlFrontMatterBlockRenderer.java} (100%) rename commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/{YAMLFrontMatterTest.java => YamlFrontMatterTest.java} (100%) diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterBlock.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterBlock.java similarity index 100% rename from commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterBlock.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterBlock.java diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java new file mode 100644 index 000000000..ec01b3194 --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java @@ -0,0 +1,37 @@ +package org.commonmark.ext.front.matter; + +import org.commonmark.Extension; +import org.commonmark.ext.front.matter.internal.YamlFrontMatterBlockParser; +import org.commonmark.ext.front.matter.internal.YamlFrontMatterBlockRenderer; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; + +/** + * Extension for YAML-like metadata. + *

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

    + *

    + * The parsed metadata is turned into {@link YamlFrontMatterNode}. You can access the metadata using {@link YamlFrontMatterVisitor}. + *

    + */ +public class YamlFrontMatterExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { + private YamlFrontMatterExtension() { + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.customHtmlRenderer(new YamlFrontMatterBlockRenderer()); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customBlockParserFactory(new YamlFrontMatterBlockParser.Factory()); + } + + public static Extension create() { + return new YamlFrontMatterExtension(); + } +} diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterNode.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterNode.java similarity index 100% rename from commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YAMLFrontMatterNode.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterNode.java diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterVisitor.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterVisitor.java new file mode 100644 index 000000000..1c23966f5 --- /dev/null +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterVisitor.java @@ -0,0 +1,29 @@ +package org.commonmark.ext.front.matter; + +import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.CustomNode; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class YamlFrontMatterVisitor extends AbstractVisitor { + private Map> data; + + public YamlFrontMatterVisitor() { + data = new LinkedHashMap<>(); + } + + @Override + public void visit(CustomNode customNode) { + if (customNode instanceof YamlFrontMatterNode) { + data.put(((YamlFrontMatterNode) customNode).getKey(), ((YamlFrontMatterNode) customNode).getValues()); + } else { + super.visit(customNode); + } + } + + public Map> getData() { + return data; + } +} 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 similarity index 100% rename from commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockParser.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockRenderer.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockRenderer.java similarity index 100% rename from commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YAMLFrontMatterBlockRenderer.java rename to commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockRenderer.java 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 similarity index 100% rename from commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YAMLFrontMatterTest.java rename to commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java From e2d2228d8be92e59c03580028aff7ffca175af00 Mon Sep 17 00:00:00 2001 From: Chiwan Park Date: Fri, 26 Feb 2016 18:08:07 +0900 Subject: [PATCH 100/815] Rename class name in javadoc --- commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html b/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html index 53dd60bc5..d5ef92fdc 100644 --- a/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html +++ b/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html @@ -1,6 +1,6 @@ Extension for YAML front matter -

    See {@link org.commonmark.ext.yaml.YAMLFrontMatterExtension}

    +

    See {@link org.commonmark.ext.yaml.YamlFrontMatterExtension}

    From d6bfef277475010525897423188c6986869c4854 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 27 Feb 2016 00:27:12 +1100 Subject: [PATCH 101/815] README: Tweak wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0347d34f..2cb3f98ed 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. ### YAML front matter -Enables an YAML front matter block. This extension only supports a subset of YAML syntax. Here's an example of what's supported: +Adds support for metadata through a YAML front matter block. This extension only supports a subset of YAML syntax. Here's an example of what's supported: ``` --- From db6dfb6e4b788c64cf05960d4a487dc66d382710 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 Mar 2016 13:55:33 +1100 Subject: [PATCH 102/815] Remove no longer needed method --- .../main/java/org/commonmark/internal/ParagraphParser.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index 4ca32c487..db1b9f187 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -62,10 +62,6 @@ public void parseInlines(InlineParser inlineParser) { } } - public boolean hasSingleLine() { - return content.hasSingleLine(); - } - public String getContentString() { return content.getString(); } From 9ee0535dc55944e036c1d12ed2f4dd61eb238177 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 1 Mar 2016 14:07:42 +1100 Subject: [PATCH 103/815] travis: Add travis_retry for android tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 017b01cfe..d0972af82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,4 @@ android: - sys-img-armeabi-v7a-android-15 script: - 'if [[ $TEST = java ]]; then mvn test; fi' - - 'if [[ $TEST = android ]]; then mvn install -DskipTests && cd commonmark-android-test && ./.travis.sh; fi' + - 'if [[ $TEST = android ]]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' From 156c0083a14254054c1da11ae4e54d48317c404a Mon Sep 17 00:00:00 2001 From: Jake Wang Date: Sat, 12 Mar 2016 07:32:22 +0800 Subject: [PATCH 104/815] Corrected wrong package name in javadoc overview --- commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html b/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html index d5ef92fdc..ad3a4287c 100644 --- a/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html +++ b/commonmark-ext-yaml-front-matter/src/main/javadoc/overview.html @@ -1,6 +1,6 @@ Extension for YAML front matter -

    See {@link org.commonmark.ext.yaml.YamlFrontMatterExtension}

    +

    See {@link org.commonmark.ext.front.matter.YamlFrontMatterExtension}

    From 9353368c9218596142f02546d6bc861b1eedce1c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Apr 2016 12:37:53 +1100 Subject: [PATCH 105/815] Blank line after empty list item terminates list --- .../java/org/commonmark/internal/ListItemParser.java | 9 +++++++-- .../test/java/org/commonmark/test/SpecialInputTest.java | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java index 2ae0a767f..27894aca3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java @@ -33,8 +33,13 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState state) { - if (state.isBlank() && block.getFirstChild() != null) { - return BlockContinue.atIndex(state.getNextNonSpaceIndex()); + if (state.isBlank()) { + if (block.getFirstChild() == null) { + // Blank line after empty list item + return BlockContinue.none(); + } else { + return BlockContinue.atIndex(state.getNextNonSpaceIndex()); + } } if (state.getIndent() >= itemIndent) { diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 55c28f767..979cf9597 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -61,6 +61,11 @@ public void looseListInBlockQuote() { assertRendering("> *\n>\n> * a", "
    \n
      \n
    • \n
    • \n

      a

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

    foo

    \n"); + } + @Test public void orderedListMarkerOnly() { assertRendering("2.", "
      \n
    1. \n
    \n"); From 3f1c7246515985ec1064f088851acabe7963fbf9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 4 Apr 2016 12:30:21 +1000 Subject: [PATCH 106/815] Bump JMH and pegdown versions --- commonmark-integration-test/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 7e985e7b1..29c1861e8 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -40,7 +40,7 @@ org.pegdown pegdown - 1.5.0 + 1.6.0 test diff --git a/pom.xml b/pom.xml index de78615b7..fa8200b76 100644 --- a/pom.xml +++ b/pom.xml @@ -121,12 +121,12 @@ org.openjdk.jmh jmh-core - 1.11.3 + 1.12 org.openjdk.jmh jmh-generator-annprocess - 1.11.3 + 1.12
    From 6e3a713547070aecd5241b11b5bf5c9c1fa1fea6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 4 Apr 2016 12:45:00 +1000 Subject: [PATCH 107/815] Tweak timeout in PathologicalTest Test sometimes fails on some build machines. --- .../org/commonmark/test/PathologicalTest.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 8a5ed2a9d..20dd5ffd8 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -16,10 +16,10 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class PathologicalTest extends CoreRenderingTestCase { - private static final int X = 10_000; + private int x = 10_000; @Rule - public Timeout timeout = new Timeout(2, TimeUnit.SECONDS); + public Timeout timeout = new Timeout(3, TimeUnit.SECONDS); @Rule public Stopwatch stopwatch = new Stopwatch() { @@ -32,7 +32,7 @@ protected void finished(long nanos, Description description) { @Test public void nestedStrongEmphasis() { // this is limited by the stack size because visitor is recursive - int x = 1000; + x = 1000; assertRendering( repeat("*a **a ", x) + "b" + repeat(" a** a*", x), "

    " + repeat("a a ", x) + "b" + @@ -42,56 +42,56 @@ public void nestedStrongEmphasis() { @Test public void emphasisClosersWithNoOpeners() { assertRendering( - repeat("a_ ", X), - "

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    " + repeat("*a_ ", 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 public void nestedBlockQuotes() { // this is limited by the stack size because visitor is recursive - int x = 1000; + x = 1000; assertRendering( repeat("> ", x) + "a\n", repeat("
    \n", x) + "

    a

    \n" + @@ -105,5 +105,4 @@ private static String repeat(String s, int count) { } return sb.toString(); } - } From a2ad9b34d365d04e5e4008ede2a038957498124a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 5 Apr 2016 15:27:26 +1000 Subject: [PATCH 108/815] Update to CommonMark spec 0.25 Changes how partially consumed tabs are handled. --- .../src/main/resources/spec.txt | 55 ++++++++-- .../commonmark/internal/BlockQuoteParser.java | 31 +++--- .../commonmark/internal/DocumentParser.java | 42 +++++--- .../internal/IndentedCodeBlockParser.java | 11 +- .../commonmark/internal/ListBlockParser.java | 100 +++++++++++------- .../commonmark/internal/ListItemParser.java | 17 +-- .../org/commonmark/internal/util/Parsing.java | 54 ++++++---- .../org/commonmark/test/SpecialInputTest.java | 6 ++ 8 files changed, 215 insertions(+), 101 deletions(-) diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index 449e5b065..1a4a7dc90 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.24 -date: '2015-01-12' +version: 0.25 +date: '2016-03-24' license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -301,15 +301,40 @@ by spaces with a tab stop of 4 characters. ```````````````````````````````` +```````````````````````````````` example +- foo + +→→bar +. +
      +
    • +

      foo

      +
        bar
      +
      +
    • +
    +```````````````````````````````` ```````````````````````````````` example ->→foo→bar +>→→foo .
    -

    foo→bar

    +
      foo
    +
    ```````````````````````````````` +```````````````````````````````` example +-→→foo +. +
      +
    • +
        foo
      +
      +
    • +
    +```````````````````````````````` + ```````````````````````````````` example foo @@ -320,6 +345,24 @@ bar ```````````````````````````````` +```````````````````````````````` example + - foo + - bar +→ - baz +. +
      +
    • foo +
        +
      • bar +
          +
        • baz
        • +
        +
      • +
      +
    • +
    +```````````````````````````````` + ## Insecure characters @@ -1204,7 +1247,7 @@ bar

    or use a thematic break that cannot count as a [setext heading -line], such as +underline], such as ```````````````````````````````` example Foo @@ -8940,7 +8983,7 @@ This is text that can be incorporated into the last open block (a paragraph, code block, heading, or raw HTML). Setext headings are formed when we see a line of a paragraph -that is a setext heading line. +that is a [setext heading underline]. Reference link definitions are detected when a paragraph is closed; the accumulated text lines are parsed to see if they begin with diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java index 247af08cc..6b19f8aaf 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.BlockQuote; import org.commonmark.parser.block.*; @@ -26,29 +27,33 @@ public BlockQuote getBlock() { @Override public BlockContinue tryContinue(ParserState state) { int nextNonSpace = state.getNextNonSpaceIndex(); - CharSequence line = state.getLine(); - if (state.getIndent() <= 3 && nextNonSpace < line.length() && line.charAt(nextNonSpace) == '>') { - int newIndex = nextNonSpace + 1; - if (newIndex < line.length() && line.charAt(newIndex) == ' ') { - newIndex++; + if (isMarker(state, nextNonSpace)) { + int newColumn = state.getColumn() + state.getIndent() + 1; + // optional following space or tab + if (Parsing.isSpaceOrTab(state.getLine(), nextNonSpace + 1)) { + newColumn++; } - return BlockContinue.atIndex(newIndex); + return BlockContinue.atColumn(newColumn); } else { return BlockContinue.none(); } } + private static boolean isMarker(ParserState state, int index) { + CharSequence line = state.getLine(); + return state.getIndent() < Parsing.CODE_BLOCK_INDENT && index < line.length() && line.charAt(index) == '>'; + } + public static class Factory extends AbstractBlockParserFactory { public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - CharSequence line = state.getLine(); int nextNonSpace = state.getNextNonSpaceIndex(); - if (state.getIndent() < 4 && line.charAt(nextNonSpace) == '>') { - int newOffset = nextNonSpace + 1; - // optional following space - if (newOffset < line.length() && line.charAt(newOffset) == ' ') { - newOffset++; + if (isMarker(state, nextNonSpace)) { + int newColumn = state.getColumn() + state.getIndent() + 1; + // optional following space or tab + if (Parsing.isSpaceOrTab(state.getLine(), nextNonSpace + 1)) { + newColumn++; } - return BlockStart.of(new BlockQuoteParser()).atIndex(newOffset); + return BlockStart.of(new BlockQuoteParser()).atColumn(newColumn); } else { return BlockStart.none(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 29a6c5de8..16919540b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -35,9 +35,9 @@ public class DocumentParser implements ParserState { private int nextNonSpace = 0; private int nextNonSpaceColumn = 0; - private boolean blank; - private int indent = 0; + private boolean blank; + private boolean columnIsInTab; private final List blockParserFactories; private final InlineParserImpl inlineParser; @@ -192,7 +192,7 @@ private void incorporateLine(CharSequence ln) { findNextNonSpace(); // this is a little performance optimization: - if (isBlank() || (indent < IndentedCodeBlockParser.INDENT && Parsing.isLetter(line, nextNonSpace))) { + if (isBlank() || (indent < Parsing.CODE_BLOCK_INDENT && Parsing.isLetter(line, nextNonSpace))) { setNewIndex(nextNonSpace); break; } @@ -301,6 +301,9 @@ private void setNewColumn(int newColumn) { // Last character was a tab and we overshot our target index--; column = newColumn; + columnIsInTab = true; + } else { + columnIsInTab = false; } } @@ -308,13 +311,36 @@ private void advance() { char c = line.charAt(index); if (c == '\t') { index++; - column += (4 - (column % 4)); + column += Parsing.columnsToNextTabStop(column); } else { index++; column++; } } + /** + * Add line content to the active block parser. We assume it can accept lines -- that check should be done before + * calling this. + */ + private void addLine() { + CharSequence content; + if (columnIsInTab) { + // Our column is in a partially consumed tab. Expand the remaining columns (to the next tab stop) to spaces. + int afterTab = index + 1; + CharSequence rest = line.subSequence(afterTab, line.length()); + int spaces = Parsing.columnsToNextTabStop(column); + StringBuilder sb = new StringBuilder(spaces + rest.length()); + for (int i = 0; i < spaces; i++) { + sb.append(' '); + } + sb.append(rest); + content = sb.toString(); + } else { + content = line.subSequence(index, line.length()); + } + getActiveBlockParser().addLine(content); + } + private BlockStartImpl findBlockStart(BlockParser blockParser) { MatchedBlockParser matchedBlockParser = new MatchedBlockParserImpl(blockParser); for (BlockParserFactory blockParserFactory : blockParserFactories) { @@ -410,14 +436,6 @@ private void breakOutOfLists(List blockParsers) { } } - /** - * Add a line to the block at the tip. We assume the tip can accept lines -- that check should be done before - * calling this. - */ - private void addLine() { - getActiveBlockParser().addLine(line.subSequence(index, line.length())); - } - /** * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try * its parent, and so on til we find a block that can accept children. diff --git a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java index 066ae1587..831ff2c36 100644 --- a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.block.*; @@ -7,8 +8,6 @@ public class IndentedCodeBlockParser extends AbstractBlockParser { - public static int INDENT = 4; - private static final Pattern TRAILING_BLANK_LINES = Pattern.compile("(?:\n[ \t]*)+$"); private final IndentedCodeBlock block = new IndentedCodeBlock(); @@ -21,8 +20,8 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState state) { - if (state.getIndent() >= INDENT) { - return BlockContinue.atColumn(state.getColumn() + INDENT); + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT) { + return BlockContinue.atColumn(state.getColumn() + Parsing.CODE_BLOCK_INDENT); } else if (state.isBlank()) { return BlockContinue.atIndex(state.getNextNonSpaceIndex()); } else { @@ -51,8 +50,8 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { // An indented code block cannot interrupt a paragraph. - if (state.getIndent() >= INDENT && !state.isBlank() && !(state.getActiveBlockParser().getBlock() instanceof Paragraph)) { - return BlockStart.of(new IndentedCodeBlockParser()).atColumn(state.getColumn() + INDENT); + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT && !state.isBlank() && !(state.getActiveBlockParser().getBlock() instanceof Paragraph)) { + return BlockStart.of(new IndentedCodeBlockParser()).atColumn(state.getColumn() + Parsing.CODE_BLOCK_INDENT); } else { return BlockStart.none(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index 29ee8244b..36b91feaf 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.block.*; @@ -8,8 +9,9 @@ public class ListBlockParser extends AbstractBlockParser { - private static Pattern BULLET_LIST_MARKER = Pattern.compile("^[*+-]( +|$)"); - private static Pattern ORDERED_LIST_MARKER = Pattern.compile("^(\\d{1,9})([.)])( +|$)"); + private static Pattern MARKER = Pattern.compile( + "^([*+-])(?= |\t|$)" + + "|^(\\d{1,9})([.)])(?= |\t|$)"); private final ListBlock block; @@ -46,34 +48,58 @@ public void setTight(boolean tight) { /** * Parse a list marker and return data on the marker or null. */ - private static ListData parseListMarker(CharSequence ln, int offset) { - CharSequence rest = ln.subSequence(offset, ln.length()); - int spacesAfterMarker; - ListBlock listBlock; - - Matcher match; - if ((match = BULLET_LIST_MARKER.matcher(rest)).find()) { - BulletList bulletList = new BulletList(); - bulletList.setBulletMarker(match.group(0).charAt(0)); - listBlock = bulletList; - spacesAfterMarker = match.group(1).length(); - } else if ((match = ORDERED_LIST_MARKER.matcher(rest)).find()) { - OrderedList orderedList = new OrderedList(); - orderedList.setStartNumber(Integer.parseInt(match.group(1))); - orderedList.setDelimiter(match.group(2).charAt(0)); - listBlock = orderedList; - spacesAfterMarker = match.group(3).length(); - } else { + private static ListData parseListMarker(CharSequence line, final int markerIndex, final int markerColumn) { + CharSequence rest = line.subSequence(markerIndex, line.length()); + Matcher matcher = MARKER.matcher(rest); + if (!matcher.find()) { return null; } - int padding; - boolean blankItem = match.group(0).length() == rest.length(); - if (spacesAfterMarker >= 5 || spacesAfterMarker < 1 || blankItem) { - padding = match.group(0).length() - spacesAfterMarker + 1; + + ListBlock listBlock = createListBlock(matcher); + + int markerLength = matcher.end() - matcher.start(); + int indexAfterMarker = markerIndex + markerLength; + // marker doesn't include tabs, so counting them as columns directly is ok + int columnAfterMarker = markerColumn + markerLength; + // the column within the line where the content starts + int contentColumn = columnAfterMarker; + + // See at which column the content starts if there is content + boolean hasContent = false; + for (int i = indexAfterMarker; i < line.length(); i++) { + char c = line.charAt(i); + if (c == '\t') { + contentColumn += Parsing.columnsToNextTabStop(contentColumn); + } else if (c == ' ') { + contentColumn++; + } else { + hasContent = true; + break; + } + } + + if (!hasContent || (contentColumn - columnAfterMarker) > Parsing.CODE_BLOCK_INDENT) { + // If this line is blank or has a code block, default to 1 space after marker + contentColumn = columnAfterMarker + 1; + } + + return new ListData(listBlock, contentColumn); + } + + private static ListBlock createListBlock(Matcher matcher) { + String bullet = matcher.group(1); + if (bullet != null) { + BulletList bulletList = new BulletList(); + bulletList.setBulletMarker(bullet.charAt(0)); + return bulletList; } else { - padding = match.group(0).length(); + String digit = matcher.group(2); + String delim = matcher.group(3); + OrderedList orderedList = new OrderedList(); + orderedList.setStartNumber(Integer.parseInt(digit)); + orderedList.setDelimiter(delim.charAt(0)); + return orderedList; } - return new ListData(listBlock, padding); } /** @@ -100,20 +126,17 @@ public static class Factory extends AbstractBlockParserFactory { public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { BlockParser matched = matchedBlockParser.getMatchedBlockParser(); - if (state.getIndent() >= 4 && !(matched instanceof ListBlockParser)) { + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT && !(matched instanceof ListBlockParser)) { return BlockStart.none(); } int nextNonSpace = state.getNextNonSpaceIndex(); - ListData listData = parseListMarker(state.getLine(), nextNonSpace); + ListData listData = parseListMarker(state.getLine(), nextNonSpace, state.getColumn() + state.getIndent()); if (listData == null) { return BlockStart.none(); } - // list item - int newIndex = nextNonSpace + listData.padding; - - int itemIndent = state.getIndent() + listData.padding; - ListItemParser listItemParser = new ListItemParser(itemIndent); + int newColumn = listData.contentColumn; + ListItemParser listItemParser = new ListItemParser(newColumn - state.getColumn()); // prepend the list block if needed if (!(matched instanceof ListBlockParser) || @@ -122,21 +145,20 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar ListBlockParser listBlockParser = new ListBlockParser(listData.listBlock); listBlockParser.setTight(true); - return BlockStart.of(listBlockParser, listItemParser).atIndex(newIndex); + return BlockStart.of(listBlockParser, listItemParser).atColumn(newColumn); } else { - return BlockStart.of(listItemParser).atIndex(newIndex); + return BlockStart.of(listItemParser).atColumn(newColumn); } } } private static class ListData { final ListBlock listBlock; - final int padding; + final int contentColumn; - public ListData(ListBlock listBlock, int padding) { + ListData(ListBlock listBlock, int contentColumn) { this.listBlock = listBlock; - this.padding = padding; + this.contentColumn = contentColumn; } } - } diff --git a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java index 27894aca3..8acafe15d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java @@ -10,10 +10,14 @@ public class ListItemParser extends AbstractBlockParser { private final ListItem block = new ListItem(); - private int itemIndent; - - public ListItemParser(int itemIndent) { - this.itemIndent = itemIndent; + /** + * Minimum number of columns that the content has to be indented (relative to the containing block) to be part of + * this list item. + */ + private int contentIndent; + + public ListItemParser(int contentIndent) { + this.contentIndent = contentIndent; } @Override @@ -42,11 +46,10 @@ public BlockContinue tryContinue(ParserState state) { } } - if (state.getIndent() >= itemIndent) { - return BlockContinue.atColumn(state.getColumn() + itemIndent); + if (state.getIndent() >= contentIndent) { + return BlockContinue.atColumn(state.getColumn() + contentIndent); } else { return BlockContinue.none(); } } - } 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 379173d71..046ced1e1 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -17,25 +17,11 @@ public class Parsing { public static final String OPENTAG = "<" + TAGNAME + ATTRIBUTE + "*" + "\\s*/?>"; public static final String CLOSETAG = "]"; - public static boolean isBlank(CharSequence s) { - return findNonSpace(s, 0) == -1; - } + public static int CODE_BLOCK_INDENT = 4; - private static int findNonSpace(CharSequence s, int startIndex) { - for (int i = startIndex; i < s.length(); i++) { - switch (s.charAt(i)) { - case ' ': - case '\t': - case '\n': - case '\u000B': - case '\f': - case '\r': - break; - default: - return i; - } - } - return -1; + public static int columnsToNextTabStop(int column) { + // Tab stop is 4 + return 4 - (column % 4); } public static int findLineBreak(CharSequence s, int startIndex) { @@ -49,11 +35,26 @@ public static int findLineBreak(CharSequence s, int startIndex) { return -1; } + public static boolean isBlank(CharSequence s) { + return findNonSpace(s, 0) == -1; + } + 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; + } + /** * Prepares the input line replacing {@code \0} */ @@ -83,4 +84,21 @@ public static CharSequence prepareLine(CharSequence line) { return line; } } + + private static int findNonSpace(CharSequence s, int startIndex) { + for (int i = startIndex; i < s.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/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 979cf9597..59326a841 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -66,6 +66,12 @@ public void lineWithOnlySpacesAfterListBullet() { assertRendering("- \n \n foo\n", "
      \n
    • \n
    \n

    foo

    \n"); } + @Test + public void listWithTwoSpacesForFirstBullet() { + // We have two spaces after the bullet, but no content. With content, the next line would be required + assertRendering("* \n foo\n", "
      \n
    • foo
    • \n
    \n"); + } + @Test public void orderedListMarkerOnly() { assertRendering("2.", "
      \n
    1. \n
    \n"); From 86d8efb81e2b457c586ad09e9eb3613c81416e57 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 6 Apr 2016 16:21:20 +1000 Subject: [PATCH 109/815] Make AttributeProvider work for Image nodes (#31) Instead of disabling tags in the writer and using the same visitor for rendering the alt text, just use a separate visitor. This allows us to get rid of some complicated logic too. The only downside is that we now have to build up the whole alt text in memory. --- .../org/commonmark/html/HtmlRenderer.java | 50 ++++++++++++++----- .../java/org/commonmark/html/HtmlWriter.java | 25 +--------- .../org/commonmark/test/HtmlRendererTest.java | 38 +++++++++++++- 3 files changed, 75 insertions(+), 38 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index 25e18a2ce..f9591596f 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -304,20 +304,20 @@ public void visit(OrderedList orderedList) { @Override public void visit(Image image) { - if (html.isTagAllowed()) { - String url = optionallyPercentEncodeUrl(image.getDestination()); - html.raw("\"");"); + String url = optionallyPercentEncodeUrl(image.getDestination()); + + AltTextVisitor altTextVisitor = new AltTextVisitor(); + image.accept(altTextVisitor); + String altText = altTextVisitor.getAltText(); + + Map attrs = new LinkedHashMap<>(); + attrs.put("src", url); + attrs.put("alt", altText); + if (image.getTitle() != null) { + attrs.put("title", image.getTitle()); } + + html.tag("img", getAttrs(image, attrs), true); } @Override @@ -434,4 +434,28 @@ private void setCustomAttributes(Node node, Map attrs) { } } } + + private static class AltTextVisitor extends AbstractVisitor { + + private final StringBuilder sb = new StringBuilder(); + + String getAltText() { + return sb.toString(); + } + + @Override + public void visit(Text text) { + sb.append(text.getLiteral()); + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + sb.append('\n'); + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + sb.append('\n'); + } + } } diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java index b849569ee..b70a597bf 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java @@ -5,15 +5,12 @@ import java.io.IOException; import java.util.Collections; import java.util.Map; -import java.util.regex.Pattern; public class HtmlWriter { private static final Map NO_ATTRIBUTES = Collections.emptyMap(); - private static final Pattern HTML_TAG_PATTERN = Pattern.compile("<[^>]*>"); private final Appendable buffer; - private int nesting = 0; private char lastChar = 0; public HtmlWriter(Appendable out) { @@ -21,23 +18,7 @@ public HtmlWriter(Appendable out) { } public void raw(String s) { - if (isTagAllowed()) { - append(s); - } else { - append(HTML_TAG_PATTERN.matcher(s).replaceAll("")); - } - } - - public boolean isTagAllowed() { - return nesting == 0; - } - - public void disableTags() { - nesting++; - } - - public void enableTags() { - nesting--; + append(s); } public void tag(String name) { @@ -50,10 +31,6 @@ public void tag(String name, Map attrs) { // Helper function to produce an HTML tag. public void tag(String name, Map attrs, boolean voidElement) { - if (!isTagAllowed()) { - return; - } - append("<"); append(name); if (attrs != null && !attrs.isEmpty()) { diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index e9d7aee3c..26d093956 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -3,6 +3,7 @@ import org.commonmark.html.AttributeProvider; import org.commonmark.html.HtmlRenderer; import org.commonmark.node.FencedCodeBlock; +import org.commonmark.node.Image; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.junit.Test; @@ -77,7 +78,7 @@ public void percentEncodeUrl() { } @Test - public void attributeProvider() { + public void attributeProviderForCodeBlock() { AttributeProvider custom = new AttributeProvider() { @Override public void setAttributes(Node node, Map attributes) { @@ -99,11 +100,46 @@ public void setAttributes(Node node, Map attributes) { assertEquals("
    content\n
    \n", rendered2); } + @Test + public void attributeProviderForImage() { + AttributeProvider custom = new AttributeProvider() { + @Override + public void setAttributes(Node node, Map attributes) { + if (node instanceof Image) { + attributes.remove("alt"); + attributes.put("test", "hey"); + } + } + }; + + HtmlRenderer renderer = HtmlRenderer.builder().attributeProvider(custom).build(); + String rendered = renderer.render(parse("![foo](/url)\n")); + assertEquals("

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

    \"foo\nbar\"

    \n", + defaultRenderer().render(parse("![foo\nbar](/url)\n"))); + } + + @Test + public void imageAltTextWithHardLineBreak() { + assertEquals("

    \"foo\nbar\"

    \n", + defaultRenderer().render(parse("![foo \nbar](/url)\n"))); + } + + @Test + public void imageAltTextWithEntities() { + assertEquals("

    \"foo

    \n", + defaultRenderer().render(parse("![foo ä](/url)\n"))); + } + private static HtmlRenderer defaultRenderer() { return HtmlRenderer.builder().build(); } From 29cdecdf77e7e7dbaba0a9b5051492e643b0ac8b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 8 Apr 2016 15:33:25 +1000 Subject: [PATCH 110/815] Make HTML rendering for nodes extensible (#35) We had `CustomHtmlRenderer` for custom nodes before. That was ok for that, but didn't allow clients to override rendering for core nodes. This introduces a new concept `NodeRenderer` (including factories, who doesn't like those?) that is also used for the core nodes. That allows overriding the rendering and allows to define the rendering for custom nodes at the same time. NodeRenderers have access to rendering configuration and functionality, such as extending attributes. That fixes the problem that the renderer for table nodes wasn't extensible via `AttributeProvider` (#31). --- .../strikethrough/StrikethroughExtension.java | 13 +- .../internal/StrikethroughHtmlRenderer.java | 31 -- .../internal/StrikethroughNodeRenderer.java | 44 +++ .../ext/gfm/tables/TablesExtension.java | 13 +- .../tables/internal/TableHtmlRenderer.java | 99 ------ .../tables/internal/TableNodeRenderer.java | 118 ++++++ .../commonmark/ext/gfm/tables/TablesTest.java | 39 ++ .../matter/YamlFrontMatterExtension.java | 10 +- .../YamlFrontMatterBlockRenderer.java | 15 - .../commonmark/html/CustomHtmlRenderer.java | 10 - .../org/commonmark/html/HtmlRenderer.java | 336 ++++-------------- .../java/org/commonmark/html/HtmlWriter.java | 5 +- .../html/renderer/CoreNodeRenderer.java | 307 ++++++++++++++++ .../html/renderer/NodeRenderer.java | 23 ++ .../html/renderer/NodeRendererContext.java | 50 +++ .../html/renderer/NodeRendererFactory.java | 16 + .../test/DelimiterProcessorTest.java | 50 ++- .../org/commonmark/test/HtmlRendererTest.java | 30 ++ 18 files changed, 752 insertions(+), 457 deletions(-) delete mode 100644 commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlRenderer.java create mode 100644 commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java delete mode 100644 commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlRenderer.java create mode 100644 commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java delete mode 100644 commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockRenderer.java delete mode 100644 commonmark/src/main/java/org/commonmark/html/CustomHtmlRenderer.java create mode 100644 commonmark/src/main/java/org/commonmark/html/renderer/CoreNodeRenderer.java create mode 100644 commonmark/src/main/java/org/commonmark/html/renderer/NodeRenderer.java create mode 100644 commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java create mode 100644 commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererFactory.java 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 c07756332..7827cd472 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 @@ -2,7 +2,10 @@ import org.commonmark.Extension; import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughDelimiterProcessor; -import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughHtmlRenderer; +import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughNodeRenderer; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.html.renderer.NodeRendererFactory; import org.commonmark.parser.Parser; import org.commonmark.html.HtmlRenderer; @@ -33,7 +36,11 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.customHtmlRenderer(new StrikethroughHtmlRenderer()); + rendererBuilder.nodeRendererFactory(new NodeRendererFactory() { + @Override + public NodeRenderer create(NodeRendererContext context) { + return new StrikethroughNodeRenderer(context); + } + }); } - } diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlRenderer.java deleted file mode 100644 index b89efc13a..000000000 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlRenderer.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.commonmark.ext.gfm.strikethrough.internal; - -import org.commonmark.ext.gfm.strikethrough.Strikethrough; -import org.commonmark.html.CustomHtmlRenderer; -import org.commonmark.html.HtmlWriter; -import org.commonmark.node.Node; -import org.commonmark.node.Visitor; - -public class StrikethroughHtmlRenderer implements CustomHtmlRenderer { - - @Override - public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { - if (node instanceof Strikethrough) { - htmlWriter.tag("del"); - visitChildren(node, visitor); - htmlWriter.tag("/del"); - return true; - } else { - return false; - } - } - - private void visitChildren(Node node, Visitor visitor) { - Node child = node.getFirstChild(); - while (child != null) { - child.accept(visitor); - child = child.getNext(); - } - } - -} 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 new file mode 100644 index 000000000..21aa115b6 --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java @@ -0,0 +1,44 @@ +package org.commonmark.ext.gfm.strikethrough.internal; + +import org.commonmark.ext.gfm.strikethrough.Strikethrough; +import org.commonmark.html.HtmlWriter; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.node.Node; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +public class StrikethroughNodeRenderer implements NodeRenderer { + + private final NodeRendererContext context; + private final HtmlWriter html; + + public StrikethroughNodeRenderer(NodeRendererContext context) { + this.context = context; + this.html = context.getHtmlWriter(); + } + + @Override + public Set> getNodeTypes() { + return Collections.>singleton(Strikethrough.class); + } + + @Override + public void render(Node node) { + Map attributes = context.extendAttributes(node, Collections.emptyMap()); + html.tag("del", attributes); + renderChildren(node); + html.tag("/del"); + } + + 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/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 84e58f391..064441167 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 @@ -2,7 +2,10 @@ import org.commonmark.Extension; import org.commonmark.ext.gfm.tables.internal.TableBlockParser; -import org.commonmark.ext.gfm.tables.internal.TableHtmlRenderer; +import org.commonmark.ext.gfm.tables.internal.TableNodeRenderer; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.html.renderer.NodeRendererFactory; import org.commonmark.parser.Parser; import org.commonmark.html.HtmlRenderer; @@ -33,7 +36,11 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.customHtmlRenderer(new TableHtmlRenderer()); + rendererBuilder.nodeRendererFactory(new NodeRendererFactory() { + @Override + public NodeRenderer create(NodeRendererContext context) { + return new TableNodeRenderer(context); + } + }); } - } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlRenderer.java deleted file mode 100644 index dbe6eb61a..000000000 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlRenderer.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.commonmark.ext.gfm.tables.internal; - -import org.commonmark.ext.gfm.tables.*; -import org.commonmark.html.CustomHtmlRenderer; -import org.commonmark.html.HtmlWriter; -import org.commonmark.node.Node; -import org.commonmark.node.Visitor; - -import java.util.Collections; -import java.util.Map; - -public class TableHtmlRenderer implements CustomHtmlRenderer { - - @Override - public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { - if (node instanceof TableBlock) { - renderBlock((TableBlock) node, htmlWriter, visitor); - } else if (node instanceof TableHead) { - renderHead((TableHead) node, htmlWriter, visitor); - } else if (node instanceof TableBody) { - renderBody((TableBody) node, htmlWriter, visitor); - } else if (node instanceof TableRow) { - renderRow((TableRow) node, htmlWriter, visitor); - } else if (node instanceof TableCell) { - renderCell((TableCell) node, htmlWriter, visitor); - } else { - return false; - } - return true; - } - - private void renderBlock(TableBlock tableBlock, HtmlWriter htmlWriter, Visitor visitor) { - htmlWriter.line(); - // TODO: What about attributes? If we got the renderer instead of the visitor, we could call getAttributes. - htmlWriter.tag("table"); - visitChildren(tableBlock, visitor); - htmlWriter.tag("/table"); - htmlWriter.line(); - } - - private void renderHead(TableHead tableHead, HtmlWriter htmlWriter, Visitor visitor) { - htmlWriter.line(); - htmlWriter.tag("thead"); - visitChildren(tableHead, visitor); - htmlWriter.tag("/thead"); - htmlWriter.line(); - } - - private void renderBody(TableBody tableBody, HtmlWriter htmlWriter, Visitor visitor) { - htmlWriter.line(); - htmlWriter.tag("tbody"); - visitChildren(tableBody, visitor); - htmlWriter.tag("/tbody"); - htmlWriter.line(); - } - - private void renderRow(TableRow tableRow, HtmlWriter htmlWriter, Visitor visitor) { - htmlWriter.line(); - htmlWriter.tag("tr"); - visitChildren(tableRow, visitor); - htmlWriter.tag("/tr"); - htmlWriter.line(); - } - - private void renderCell(TableCell tableCell, HtmlWriter htmlWriter, Visitor visitor) { - String tag = tableCell.isHeader() ? "th" : "td"; - htmlWriter.tag(tag, getAttributes(tableCell)); - visitChildren(tableCell, visitor); - htmlWriter.tag("/" + tag); - } - - private static Map getAttributes(TableCell tableCell) { - if (tableCell.getAlignment() != null) { - return Collections.singletonMap("align", getAlignValue(tableCell.getAlignment())); - } else { - return Collections.emptyMap(); - } - } - - private static String getAlignValue(TableCell.Alignment alignment) { - switch (alignment) { - case LEFT: - return "left"; - case CENTER: - return "center"; - case RIGHT: - return "right"; - } - throw new IllegalStateException("Unknown alignment: " + alignment); - } - - private void visitChildren(Node node, Visitor visitor) { - Node child = node.getFirstChild(); - while (child != null) { - child.accept(visitor); - child = child.getNext(); - } - } -} 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 new file mode 100644 index 000000000..429798360 --- /dev/null +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java @@ -0,0 +1,118 @@ +package org.commonmark.ext.gfm.tables.internal; + +import org.commonmark.ext.gfm.tables.*; +import org.commonmark.html.HtmlWriter; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.node.Node; + +import java.util.*; + +public class TableNodeRenderer implements NodeRenderer { + + private final HtmlWriter htmlWriter; + private final NodeRendererContext context; + + public TableNodeRenderer(NodeRendererContext context) { + this.htmlWriter = context.getHtmlWriter(); + this.context = context; + } + + @Override + public Set> getNodeTypes() { + return new HashSet<>(Arrays.asList( + TableBlock.class, + TableHead.class, + TableBody.class, + TableRow.class, + TableCell.class + )); + } + + @Override + public void render(Node node) { + if (node instanceof TableBlock) { + renderBlock((TableBlock) node); + } else if (node instanceof TableHead) { + renderHead((TableHead) node); + } else if (node instanceof TableBody) { + renderBody((TableBody) node); + } else if (node instanceof TableRow) { + renderRow((TableRow) node); + } else if (node instanceof TableCell) { + renderCell((TableCell) node); + } + } + + private void renderBlock(TableBlock tableBlock) { + htmlWriter.line(); + htmlWriter.tag("table", getAttributes(tableBlock)); + renderChildren(tableBlock); + htmlWriter.tag("/table"); + htmlWriter.line(); + } + + private void renderHead(TableHead tableHead) { + htmlWriter.line(); + htmlWriter.tag("thead", getAttributes(tableHead)); + renderChildren(tableHead); + htmlWriter.tag("/thead"); + htmlWriter.line(); + } + + private void renderBody(TableBody tableBody) { + htmlWriter.line(); + htmlWriter.tag("tbody", getAttributes(tableBody)); + renderChildren(tableBody); + htmlWriter.tag("/tbody"); + htmlWriter.line(); + } + + private void renderRow(TableRow tableRow) { + htmlWriter.line(); + htmlWriter.tag("tr", getAttributes(tableRow)); + renderChildren(tableRow); + htmlWriter.tag("/tr"); + htmlWriter.line(); + } + + private void renderCell(TableCell tableCell) { + String tag = tableCell.isHeader() ? "th" : "td"; + htmlWriter.tag(tag, getCellAttributes(tableCell)); + renderChildren(tableCell); + htmlWriter.tag("/" + tag); + } + + private Map getAttributes(Node node) { + return context.extendAttributes(node, Collections.emptyMap()); + } + + private Map getCellAttributes(TableCell tableCell) { + if (tableCell.getAlignment() != null) { + return context.extendAttributes(tableCell, Collections.singletonMap("align", getAlignValue(tableCell.getAlignment()))); + } else { + return context.extendAttributes(tableCell, Collections.emptyMap()); + } + } + + private static String getAlignValue(TableCell.Alignment alignment) { + switch (alignment) { + case LEFT: + return "left"; + case CENTER: + return "center"; + case RIGHT: + return "right"; + } + throw new IllegalStateException("Unknown alignment: " + alignment); + } + + 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/TablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index 03b650e31..85d84d64d 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,14 +1,20 @@ package org.commonmark.ext.gfm.tables; import org.commonmark.Extension; +import org.commonmark.html.AttributeProvider; import org.commonmark.html.HtmlRenderer; +import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; import org.junit.Test; import java.util.Collections; +import java.util.Map; import java.util.Set; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + public class TablesTest extends RenderingTestCase { private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); @@ -300,6 +306,39 @@ public void tableEndWithoutEmptyLine() { "

    table, you are over

    \n"); } + @Test + public void attributeProviderIsApplied() { + AttributeProvider attributeProvider = new AttributeProvider() { + @Override + public void setAttributes(Node node, Map attributes) { + if (node instanceof TableBlock) { + attributes.put("test", "block"); + } else if (node instanceof TableHead) { + attributes.put("test", "head"); + } else if (node instanceof TableBody) { + attributes.put("test", "body"); + } else if (node instanceof TableRow) { + attributes.put("test", "row"); + } else if (node instanceof TableCell) { + attributes.put("test", "cell"); + } + } + }; + HtmlRenderer renderer = HtmlRenderer.builder() + .attributeProvider(attributeProvider) + .extensions(EXTENSIONS) + .build(); + String rendered = renderer.render(PARSER.parse("Abc|Def\n---|---\n1|2")); + assertThat(rendered, is("\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n")); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java index ec01b3194..19b4984e4 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java @@ -2,8 +2,6 @@ import org.commonmark.Extension; import org.commonmark.ext.front.matter.internal.YamlFrontMatterBlockParser; -import org.commonmark.ext.front.matter.internal.YamlFrontMatterBlockRenderer; -import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; /** @@ -17,13 +15,9 @@ * The parsed metadata is turned into {@link YamlFrontMatterNode}. You can access the metadata using {@link YamlFrontMatterVisitor}. *

    */ -public class YamlFrontMatterExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { - private YamlFrontMatterExtension() { - } +public class YamlFrontMatterExtension implements Parser.ParserExtension { - @Override - public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.customHtmlRenderer(new YamlFrontMatterBlockRenderer()); + private YamlFrontMatterExtension() { } @Override diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockRenderer.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockRenderer.java deleted file mode 100644 index 436401194..000000000 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockRenderer.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.commonmark.ext.front.matter.internal; - -import org.commonmark.ext.front.matter.YamlFrontMatterBlock; -import org.commonmark.ext.front.matter.YamlFrontMatterNode; -import org.commonmark.html.CustomHtmlRenderer; -import org.commonmark.html.HtmlWriter; -import org.commonmark.node.Node; -import org.commonmark.node.Visitor; - -public class YamlFrontMatterBlockRenderer implements CustomHtmlRenderer { - @Override - public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { - return node instanceof YamlFrontMatterBlock || node instanceof YamlFrontMatterNode; - } -} diff --git a/commonmark/src/main/java/org/commonmark/html/CustomHtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/CustomHtmlRenderer.java deleted file mode 100644 index cf414a35e..000000000 --- a/commonmark/src/main/java/org/commonmark/html/CustomHtmlRenderer.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.commonmark.html; - -import org.commonmark.node.Node; -import org.commonmark.node.Visitor; - -public interface CustomHtmlRenderer { - // TODO: maybe pass renderer instead of visitor? - boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor); -} - diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index f9591596f..d36742680 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -1,8 +1,14 @@ package org.commonmark.html; import org.commonmark.Extension; +import org.commonmark.html.renderer.CoreNodeRenderer; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.html.renderer.NodeRendererFactory; import org.commonmark.internal.util.Escaping; -import org.commonmark.node.*; +import org.commonmark.node.HtmlBlock; +import org.commonmark.node.HtmlInline; +import org.commonmark.node.Node; import java.util.*; @@ -17,20 +23,27 @@ */ public class HtmlRenderer { - private static final Map NO_ATTRIBUTES = Collections.emptyMap(); - private final String softbreak; private final boolean escapeHtml; private final boolean percentEncodeUrls; - private final List customHtmlRenderers; private final List attributeProviders; + private final List nodeRendererFactories; private HtmlRenderer(Builder builder) { this.softbreak = builder.softbreak; this.escapeHtml = builder.escapeHtml; this.percentEncodeUrls = builder.percentEncodeUrls; - this.customHtmlRenderers = builder.customHtmlRenderers; this.attributeProviders = builder.attributeProviders; + + 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 NodeRendererFactory() { + @Override + public NodeRenderer create(NodeRendererContext context) { + return new CoreNodeRenderer(context); + } + }); } /** @@ -43,12 +56,13 @@ public static Builder builder() { } public void render(Node node, Appendable output) { - RendererVisitor rendererVisitor = new RendererVisitor(new HtmlWriter(output), customHtmlRenderers); - node.accept(rendererVisitor); + MainNodeRenderer renderer = new MainNodeRenderer(new HtmlWriter(output)); + renderer.render(node); } /** * Render the tree of nodes to HTML. + * * @param node the root node * @return the rendered HTML */ @@ -58,18 +72,6 @@ public String render(Node node) { return sb.toString(); } - private String escape(String input, boolean preserveEntities) { - return Escaping.escapeHtml(input, preserveEntities); - } - - private String optionallyPercentEncodeUrl(String url) { - if (percentEncodeUrls) { - return Escaping.percentEncodeUrl(url); - } else { - return url; - } - } - /** * Builder for configuring an {@link HtmlRenderer}. See methods for default configuration. */ @@ -78,8 +80,8 @@ public static class Builder { private String softbreak = "\n"; private boolean escapeHtml = false; private boolean percentEncodeUrls = false; - private List customHtmlRenderers = new ArrayList<>(); private List attributeProviders = new ArrayList<>(); + private List nodeRendererFactories = new ArrayList<>(); /** * @return the configured {@link HtmlRenderer} @@ -148,8 +150,18 @@ public Builder attributeProvider(AttributeProvider attributeProvider) { return this; } - public Builder customHtmlRenderer(CustomHtmlRenderer customHtmlRenderer) { - this.customHtmlRenderers.add(customHtmlRenderer); + /** + * 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(NodeRendererFactory nodeRendererFactory) { + this.nodeRendererFactories.add(nodeRendererFactory); return this; } @@ -175,257 +187,63 @@ public interface HtmlRendererExtension extends Extension { void extend(Builder rendererBuilder); } - private class RendererVisitor extends AbstractVisitor { - - private final HtmlWriter html; - private final List customHtmlRenderers; - - public RendererVisitor(HtmlWriter html, List customHtmlRenderers) { - this.html = html; - this.customHtmlRenderers = customHtmlRenderers; - } - - @Override - public void visit(Document document) { - visitChildren(document); - } - - @Override - public void visit(Heading heading) { - String htag = "h" + heading.getLevel(); - html.line(); - html.tag(htag, getAttrs(heading)); - visitChildren(heading); - html.tag('/' + htag); - html.line(); - } - - @Override - public void visit(Paragraph paragraph) { - boolean inTightList = isInTightList(paragraph); - if (!inTightList) { - html.line(); - html.tag("p", getAttrs(paragraph)); - } - visitChildren(paragraph); - if (!inTightList) { - html.tag("/p"); - html.line(); - } - } + private class MainNodeRenderer implements NodeRendererContext { - @Override - public void visit(BlockQuote blockQuote) { - html.line(); - html.tag("blockquote", getAttrs(blockQuote)); - html.line(); - visitChildren(blockQuote); - html.line(); - html.tag("/blockquote"); - html.line(); - } + private final HtmlWriter htmlWriter; + private final Map, NodeRenderer> renderers; - @Override - public void visit(BulletList bulletList) { - renderListBlock(bulletList, "ul", getAttrs(bulletList)); - } + private MainNodeRenderer(HtmlWriter htmlWriter) { + this.htmlWriter = htmlWriter; + this.renderers = new HashMap<>(32); - @Override - public void visit(FencedCodeBlock fencedCodeBlock) { - String literal = fencedCodeBlock.getLiteral(); - Map attributes = new LinkedHashMap<>(); - String info = fencedCodeBlock.getInfo(); - if (info != null && !info.isEmpty()) { - int space = info.indexOf(" "); - String language; - if (space == -1) { - language = info; - } else { - language = info.substring(0, space); + // The first node renderer for a node type "wins". + for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { + NodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); + NodeRenderer nodeRenderer = nodeRendererFactory.create(this); + for (Class nodeType : nodeRenderer.getNodeTypes()) { + // Overwrite existing renderer + renderers.put(nodeType, nodeRenderer); } - attributes.put("class", "language-" + language); - } - renderCodeBlock(literal, getAttrs(fencedCodeBlock, attributes)); - } - - @Override - public void visit(HtmlBlock htmlBlock) { - html.line(); - if (escapeHtml) { - html.raw(escape(htmlBlock.getLiteral(), false)); - } else { - html.raw(htmlBlock.getLiteral()); - } - html.line(); - } - - @Override - public void visit(ThematicBreak thematicBreak) { - html.line(); - html.tag("hr", getAttrs(thematicBreak), true); - html.line(); - } - - @Override - public void visit(IndentedCodeBlock indentedCodeBlock) { - renderCodeBlock(indentedCodeBlock.getLiteral(), getAttrs(indentedCodeBlock)); - } - - @Override - public void visit(Link link) { - Map attrs = new LinkedHashMap<>(); - String url = optionallyPercentEncodeUrl(link.getDestination()); - attrs.put("href", url); - if (link.getTitle() != null) { - attrs.put("title", link.getTitle()); - } - html.tag("a", getAttrs(link, attrs)); - visitChildren(link); - html.tag("/a"); - } - - @Override - public void visit(ListItem listItem) { - html.tag("li", getAttrs(listItem)); - visitChildren(listItem); - html.tag("/li"); - html.line(); - } - - @Override - public void visit(OrderedList orderedList) { - int start = orderedList.getStartNumber(); - Map attrs = new LinkedHashMap<>(); - if (start != 1) { - attrs.put("start", String.valueOf(start)); } - renderListBlock(orderedList, "ol", getAttrs(orderedList, attrs)); - } - - @Override - public void visit(Image image) { - String url = optionallyPercentEncodeUrl(image.getDestination()); - - AltTextVisitor altTextVisitor = new AltTextVisitor(); - image.accept(altTextVisitor); - String altText = altTextVisitor.getAltText(); - - Map attrs = new LinkedHashMap<>(); - attrs.put("src", url); - attrs.put("alt", altText); - if (image.getTitle() != null) { - attrs.put("title", image.getTitle()); - } - - html.tag("img", getAttrs(image, attrs), true); - } - - @Override - public void visit(Emphasis emphasis) { - html.tag("em"); - visitChildren(emphasis); - html.tag("/em"); } @Override - public void visit(StrongEmphasis strongEmphasis) { - html.tag("strong"); - visitChildren(strongEmphasis); - html.tag("/strong"); + public boolean shouldEscapeHtml() { + return escapeHtml; } @Override - public void visit(Text text) { - html.raw(escape(text.getLiteral(), false)); - } - - @Override - public void visit(Code code) { - html.tag("code"); - html.raw(escape(code.getLiteral(), false)); - html.tag("/code"); - } - - @Override - public void visit(HtmlInline htmlInline) { - if (escapeHtml) { - html.raw(escape(htmlInline.getLiteral(), false)); + public String encodeUrl(String url) { + if (percentEncodeUrls) { + return Escaping.percentEncodeUrl(url); } else { - html.raw(htmlInline.getLiteral()); + return url; } } @Override - public void visit(SoftLineBreak softLineBreak) { - html.raw(softbreak); + public Map extendAttributes(Node node, Map attributes) { + Map attrs = new LinkedHashMap<>(attributes); + setCustomAttributes(node, attrs); + return attrs; } @Override - public void visit(HardLineBreak hardLineBreak) { - html.tag("br", NO_ATTRIBUTES, true); - html.line(); + public HtmlWriter getHtmlWriter() { + return htmlWriter; } @Override - public void visit(CustomBlock customBlock) { - renderCustom(customBlock); + public String getSoftbreak() { + return softbreak; } @Override - public void visit(CustomNode customNode) { - renderCustom(customNode); - } - - private void renderCustom(Node node) { - for (CustomHtmlRenderer customHtmlRenderer : customHtmlRenderers) { - // TODO: Should we pass attributes here? - boolean handled = customHtmlRenderer.render(node, html, this); - if (handled) { - break; - } - } - } - - private void renderCodeBlock(String literal, Map attributes) { - html.line(); - html.tag("pre"); - html.tag("code", attributes); - html.raw(escape(literal, false)); - html.tag("/code"); - html.tag("/pre"); - html.line(); - } - - private void renderListBlock(ListBlock listBlock, String tagName, Map attributes) { - html.line(); - html.tag(tagName, attributes); - html.line(); - visitChildren(listBlock); - html.line(); - html.tag('/' + tagName); - html.line(); - } - - private boolean isInTightList(Paragraph paragraph) { - Node parent = paragraph.getParent(); - if (parent != null) { - Node gramps = parent.getParent(); - if (gramps != null && gramps instanceof ListBlock) { - ListBlock list = (ListBlock) gramps; - return list.isTight(); - } + public void render(Node node) { + NodeRenderer nodeRenderer = renderers.get(node.getClass()); + if (nodeRenderer != null) { + nodeRenderer.render(node); } - return false; - } - - private Map getAttrs(Node node) { - return getAttrs(node, Collections.emptyMap()); - } - - private Map getAttrs(Node node, Map defaultAttributes) { - Map attrs = new LinkedHashMap<>(defaultAttributes); - setCustomAttributes(node, attrs); - return attrs; } private void setCustomAttributes(Node node, Map attrs) { @@ -434,28 +252,4 @@ private void setCustomAttributes(Node node, Map attrs) { } } } - - private static class AltTextVisitor extends AbstractVisitor { - - private final StringBuilder sb = new StringBuilder(); - - String getAltText() { - return sb.toString(); - } - - @Override - public void visit(Text text) { - sb.append(text.getLiteral()); - } - - @Override - public void visit(SoftLineBreak softLineBreak) { - sb.append('\n'); - } - - @Override - public void visit(HardLineBreak hardLineBreak) { - sb.append('\n'); - } - } } diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java index b70a597bf..e69881601 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java @@ -21,6 +21,10 @@ public void raw(String s) { append(s); } + public void text(String text) { + append(Escaping.escapeHtml(text, false)); + } + public void tag(String name) { tag(name, NO_ATTRIBUTES); } @@ -29,7 +33,6 @@ public void tag(String name, Map attrs) { tag(name, attrs, false); } - // Helper function to produce an HTML tag. public void tag(String name, Map attrs, boolean voidElement) { append("<"); append(name); diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/CoreNodeRenderer.java b/commonmark/src/main/java/org/commonmark/html/renderer/CoreNodeRenderer.java new file mode 100644 index 000000000..51ad18e89 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/renderer/CoreNodeRenderer.java @@ -0,0 +1,307 @@ +package org.commonmark.html.renderer; + +import org.commonmark.html.HtmlWriter; +import org.commonmark.node.*; + +import java.util.*; + +/** + * The node renderer that renders all the core nodes (comes last in the order of node renderers). + */ +public class CoreNodeRenderer extends AbstractVisitor implements NodeRenderer { + + protected final NodeRendererContext context; + private final HtmlWriter html; + + public CoreNodeRenderer(NodeRendererContext context) { + this.context = context; + this.html = context.getHtmlWriter(); + } + + @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(Heading heading) { + String htag = "h" + heading.getLevel(); + html.line(); + html.tag(htag, getAttrs(heading)); + visitChildren(heading); + html.tag('/' + htag); + html.line(); + } + + @Override + public void visit(Paragraph paragraph) { + boolean inTightList = isInTightList(paragraph); + if (!inTightList) { + html.line(); + html.tag("p", getAttrs(paragraph)); + } + visitChildren(paragraph); + if (!inTightList) { + html.tag("/p"); + html.line(); + } + } + + @Override + public void visit(BlockQuote blockQuote) { + html.line(); + html.tag("blockquote", getAttrs(blockQuote)); + html.line(); + visitChildren(blockQuote); + html.line(); + html.tag("/blockquote"); + html.line(); + } + + @Override + public void visit(BulletList bulletList) { + renderListBlock(bulletList, "ul", getAttrs(bulletList)); + } + + @Override + public void visit(FencedCodeBlock fencedCodeBlock) { + String literal = fencedCodeBlock.getLiteral(); + Map attributes = new LinkedHashMap<>(); + String info = fencedCodeBlock.getInfo(); + if (info != null && !info.isEmpty()) { + int space = info.indexOf(" "); + String language; + if (space == -1) { + language = info; + } else { + language = info.substring(0, space); + } + attributes.put("class", "language-" + language); + } + renderCodeBlock(literal, getAttrs(fencedCodeBlock, attributes)); + } + + @Override + public void visit(HtmlBlock htmlBlock) { + html.line(); + if (context.shouldEscapeHtml()) { + html.text(htmlBlock.getLiteral()); + } else { + html.raw(htmlBlock.getLiteral()); + } + html.line(); + } + + @Override + public void visit(ThematicBreak thematicBreak) { + html.line(); + html.tag("hr", getAttrs(thematicBreak), true); + html.line(); + } + + @Override + public void visit(IndentedCodeBlock indentedCodeBlock) { + renderCodeBlock(indentedCodeBlock.getLiteral(), getAttrs(indentedCodeBlock)); + } + + @Override + public void visit(Link link) { + Map attrs = new LinkedHashMap<>(); + String url = context.encodeUrl(link.getDestination()); + attrs.put("href", url); + if (link.getTitle() != null) { + attrs.put("title", link.getTitle()); + } + html.tag("a", getAttrs(link, attrs)); + visitChildren(link); + html.tag("/a"); + } + + @Override + public void visit(ListItem listItem) { + html.tag("li", getAttrs(listItem)); + visitChildren(listItem); + html.tag("/li"); + html.line(); + } + + @Override + public void visit(OrderedList orderedList) { + int start = orderedList.getStartNumber(); + Map attrs = new LinkedHashMap<>(); + if (start != 1) { + attrs.put("start", String.valueOf(start)); + } + renderListBlock(orderedList, "ol", getAttrs(orderedList, attrs)); + } + + @Override + public void visit(Image image) { + String url = context.encodeUrl(image.getDestination()); + + AltTextVisitor altTextVisitor = new AltTextVisitor(); + image.accept(altTextVisitor); + String altText = altTextVisitor.getAltText(); + + Map attrs = new LinkedHashMap<>(); + attrs.put("src", url); + attrs.put("alt", altText); + if (image.getTitle() != null) { + attrs.put("title", image.getTitle()); + } + + html.tag("img", getAttrs(image, attrs), true); + } + + @Override + public void visit(Emphasis emphasis) { + html.tag("em"); + visitChildren(emphasis); + html.tag("/em"); + } + + @Override + public void visit(StrongEmphasis strongEmphasis) { + html.tag("strong"); + visitChildren(strongEmphasis); + html.tag("/strong"); + } + + @Override + public void visit(Text text) { + html.text(text.getLiteral()); + } + + @Override + public void visit(Code code) { + html.tag("code"); + html.text(code.getLiteral()); + html.tag("/code"); + } + + @Override + public void visit(HtmlInline htmlInline) { + if (context.shouldEscapeHtml()) { + html.text(htmlInline.getLiteral()); + } else { + html.raw(htmlInline.getLiteral()); + } + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + html.raw(context.getSoftbreak()); + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + html.tag("br", null, true); + html.line(); + } + + @Override + protected void visitChildren(Node parent) { + Node node = parent.getFirstChild(); + while (node != null) { + Node next = node.getNext(); + context.render(node); + node = next; + } + } + + private void renderCodeBlock(String literal, Map attributes) { + html.line(); + html.tag("pre"); + html.tag("code", attributes); + html.text(literal); + html.tag("/code"); + html.tag("/pre"); + html.line(); + } + + private void renderListBlock(ListBlock listBlock, String tagName, Map attributes) { + html.line(); + html.tag(tagName, attributes); + html.line(); + visitChildren(listBlock); + html.line(); + html.tag('/' + tagName); + html.line(); + } + + private boolean isInTightList(Paragraph paragraph) { + Node parent = paragraph.getParent(); + if (parent != null) { + Node gramps = parent.getParent(); + if (gramps != null && gramps instanceof ListBlock) { + ListBlock list = (ListBlock) gramps; + return list.isTight(); + } + } + return false; + } + + private Map getAttrs(Node node) { + return context.extendAttributes(node, Collections.emptyMap()); + } + + private Map getAttrs(Node node, Map defaultAttributes) { + return context.extendAttributes(node, defaultAttributes); + } + + private static class AltTextVisitor extends AbstractVisitor { + + private final StringBuilder sb = new StringBuilder(); + + String getAltText() { + return sb.toString(); + } + + @Override + public void visit(Text text) { + sb.append(text.getLiteral()); + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + sb.append('\n'); + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + sb.append('\n'); + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRenderer.java b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRenderer.java new file mode 100644 index 000000000..472bdc6ce --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRenderer.java @@ -0,0 +1,23 @@ +package org.commonmark.html.renderer; + +import org.commonmark.node.Node; + +import java.util.Set; + +/** + * A renderer for a set of node types. + */ +public interface NodeRenderer { + + /** + * @return the types of nodes that this renderer handles + */ + Set> getNodeTypes(); + + /** + * Render the specified node. + * + * @param node the node to render, will be an instance of one of {@link #getNodeTypes()} + */ + void render(Node node); +} diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java new file mode 100644 index 000000000..9b24c1240 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java @@ -0,0 +1,50 @@ +package org.commonmark.html.renderer; + +import org.commonmark.html.HtmlWriter; +import org.commonmark.node.Node; + +import java.util.Map; + +/** + * The context for node rendering, including configuration and functionality for the node renderer to use. + */ +public interface NodeRendererContext { + + /** + * @param url to be encoded + * @return an encoded URL (depending on the configuration) + */ + String encodeUrl(String url); + + /** + * Extend the attributes by extensions. + * + * @param node the node for which the attributes are applied + * @param attributes the attributes that were calculated by the renderer + * @return the extended attributes with added/updated/removed entries + */ + Map extendAttributes(Node node, Map attributes); + + /** + * @return the HTML writer to use + */ + HtmlWriter getHtmlWriter(); + + /** + * @return HTML that should be rendered for a soft line break + */ + String getSoftbreak(); + + /** + * Render the specified node and its children using the configured renderers. This should be used to render child + * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * + * @param node the node to render + */ + void render(Node node); + + /** + * @return whether HTML blocks and tags should be escaped or not + */ + boolean shouldEscapeHtml(); +} diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererFactory.java new file mode 100644 index 000000000..2080b0030 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererFactory.java @@ -0,0 +1,16 @@ +package org.commonmark.html.renderer; + +/** + * Factory for instantiating new node renderers when rendering is done. + */ +public interface NodeRendererFactory { + + /** + * 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(NodeRendererContext context); + +} diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 1f9ed95ed..590d45441 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -1,22 +1,24 @@ package org.commonmark.test; -import org.commonmark.html.CustomHtmlRenderer; import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.HtmlWriter; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.html.renderer.NodeRendererFactory; import org.commonmark.node.CustomNode; import org.commonmark.node.Node; import org.commonmark.node.Text; -import org.commonmark.node.Visitor; import org.commonmark.parser.DelimiterProcessor; import org.commonmark.parser.Parser; import org.junit.Test; +import java.util.Collections; import java.util.Locale; +import java.util.Set; public class DelimiterProcessorTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().customDelimiterProcessor(new AsymmetricDelimiterProcessor()).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().customHtmlRenderer(new UpperCaseNodeRenderer()).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().nodeRendererFactory(new UpperCaseNodeRendererFactory()).build(); @Test public void asymmetricDelimiter() { @@ -76,21 +78,37 @@ public void process(Text opener, Text closer, int delimiterUse) { private static class UpperCaseNode extends CustomNode { } - private static class UpperCaseNodeRenderer implements CustomHtmlRenderer { + private static class UpperCaseNodeRendererFactory implements NodeRendererFactory { + + @Override + public NodeRenderer create(NodeRendererContext context) { + return new UpperCaseNodeRenderer(context); + } + } + + private static class UpperCaseNodeRenderer implements NodeRenderer { + + private final NodeRendererContext context; + + private UpperCaseNodeRenderer(NodeRendererContext context) { + this.context = context; + } + + @Override + public Set> getNodeTypes() { + return Collections.>singleton(UpperCaseNode.class); + } + @Override - public boolean render(Node node, HtmlWriter htmlWriter, Visitor visitor) { - if (node instanceof UpperCaseNode) { - UpperCaseNode upperCaseNode = (UpperCaseNode) node; - for (Node child = upperCaseNode.getFirstChild(); child != null; child = child.getNext()) { - if (child instanceof Text) { - Text text = (Text) child; - text.setLiteral(text.getLiteral().toUpperCase(Locale.ENGLISH)); - } - child.accept(visitor); + public void render(Node node) { + UpperCaseNode upperCaseNode = (UpperCaseNode) node; + for (Node child = upperCaseNode.getFirstChild(); child != null; child = child.getNext()) { + if (child instanceof Text) { + Text text = (Text) child; + text.setLiteral(text.getLiteral().toUpperCase(Locale.ENGLISH)); } - return true; + context.render(child); } - return false; } } } diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 26d093956..6a77f8a0a 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -2,13 +2,19 @@ import org.commonmark.html.AttributeProvider; import org.commonmark.html.HtmlRenderer; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.html.renderer.NodeRendererFactory; import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.Image; +import org.commonmark.node.Link; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.junit.Test; +import java.util.Collections; import java.util.Map; +import java.util.Set; import static org.junit.Assert.assertEquals; @@ -117,6 +123,30 @@ public void setAttributes(Node node, Map attributes) { assertEquals("

    \n", rendered); } + @Test + public void overrideNodeRender() { + NodeRendererFactory nodeRendererFactory = new NodeRendererFactory() { + @Override + public NodeRenderer create(final NodeRendererContext context) { + return new NodeRenderer() { + @Override + public Set> getNodeTypes() { + return Collections.>singleton(Link.class); + } + + @Override + public void render(Node node) { + context.getHtmlWriter().text("test"); + } + }; + } + }; + + HtmlRenderer renderer = HtmlRenderer.builder().nodeRendererFactory(nodeRendererFactory).build(); + String rendered = renderer.render(parse("foo [bar](/url)")); + assertEquals("

    foo test

    \n", rendered); + } + @Test public void orderedListStartZero() { assertEquals("
      \n
    1. Test
    2. \n
    \n", defaultRenderer().render(parse("0. Test\n"))); From 1366034dd431f8cfe0a5ffd34e880f6166e42273 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 8 Apr 2016 19:47:09 +1000 Subject: [PATCH 111/815] travis: Try to get more console output for failed tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d0972af82..56f83970c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,5 +16,5 @@ android: - extra-android-m2repository - sys-img-armeabi-v7a-android-15 script: - - 'if [[ $TEST = java ]]; then mvn test; fi' + - 'if [[ $TEST = java ]]; then mvn test -Dsurefire.useFile=false; fi' - 'if [[ $TEST = android ]]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' From a67694568419c7d4899dbb62357a8321617dabf7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 8 Apr 2016 19:53:50 +1000 Subject: [PATCH 112/815] Lower limit for nestedStrongEmphasis because of stack size Now that there's an extra level of indirection, the stack requirements are higher. The test was failing on Java 7 but not on 8 because its default stack size is smaller. --- .../src/test/java/org/commonmark/test/PathologicalTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 20dd5ffd8..4f8dcfb3b 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -32,7 +32,7 @@ protected void finished(long nanos, Description description) { @Test public void nestedStrongEmphasis() { // this is limited by the stack size because visitor is recursive - x = 1000; + x = 500; assertRendering( repeat("*a **a ", x) + "b" + repeat(" a** a*", x), "

    " + repeat("a a ", x) + "b" + From 43beca886ae2db30cbe1ee1b980cf47266a6e986 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 16 Apr 2016 17:15:06 +1000 Subject: [PATCH 113/815] README: Add note about Android support (#30) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2cb3f98ed..0a82b5d42 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ library with a nice Java API and some optional extensions. Features: Requirements: * Java 7 or above +* Works on Android; minimum API level 15 (see [commonmark-android-test](commonmark-android-test) directory) * The core has no dependencies; for extensions, see below Coordinates for core library (see all on [Maven Central]): From 026c7d4c6af05e30efdee7066fefd76833ae7366 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 22 Apr 2016 15:56:41 +1000 Subject: [PATCH 114/815] [maven-release-plugin] prepare release commonmark-parent-0.5.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 6 ++---- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 8 files changed, 16 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index b4f65ae74..7b9512ad3 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.2-SNAPSHOT + 0.5.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 21cdc79c2..f6f3b965b 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.2-SNAPSHOT + 0.5.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 668839c40..46f1d4e40 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.2-SNAPSHOT + 0.5.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index ada9956bc..9543b67c6 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -1,12 +1,10 @@ - + 4.0.0 commonmark-parent com.atlassian.commonmark - 0.4.2-SNAPSHOT + 0.5.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 29c1861e8..9660eafae 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.2-SNAPSHOT + 0.5.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index b48c896e8..34bcdb368 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.2-SNAPSHOT + 0.5.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 326208c92..d58e688ef 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.2-SNAPSHOT + 0.5.0 commonmark diff --git a/pom.xml b/pom.xml index fa8200b76..4f54d60dc 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.2-SNAPSHOT + 0.5.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,32 +84,32 @@ com.atlassian.commonmark commonmark - 0.4.2-SNAPSHOT + 0.5.0 com.atlassian.commonmark commonmark-ext-autolink - 0.4.2-SNAPSHOT + 0.5.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.4.2-SNAPSHOT + 0.5.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.4.2-SNAPSHOT + 0.5.0 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.4.2-SNAPSHOT + 0.5.0 com.atlassian.commonmark commonmark-test-util - 0.4.2-SNAPSHOT + 0.5.0 @@ -152,7 +152,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.5.0 From 57990267fe6afec1c799ae5decba3985b29a693b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 22 Apr 2016 15:56:41 +1000 Subject: [PATCH 115/815] [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-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 7b9512ad3..59dc7dbd8 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.0 + 0.5.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index f6f3b965b..a16d8a3d5 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.0 + 0.5.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 46f1d4e40..4a94364bd 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.0 + 0.5.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 9543b67c6..5b57740dc 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.5.0 + 0.5.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 9660eafae..3a625370c 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.0 + 0.5.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 34bcdb368..94f41b32c 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.0 + 0.5.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index d58e688ef..fd4f25761 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.0 + 0.5.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 4f54d60dc..37e8520d9 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.0 + 0.5.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,32 +84,32 @@ com.atlassian.commonmark commonmark - 0.5.0 + 0.5.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.5.0 + 0.5.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.5.0 + 0.5.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.5.0 + 0.5.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.5.0 + 0.5.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.5.0 + 0.5.1-SNAPSHOT @@ -152,7 +152,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.5.0 + HEAD From 56561824259f400bc58be8f0d7380d2f236a9c3b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 22 Apr 2016 23:20:04 +1000 Subject: [PATCH 116/815] Bump versions --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0a82b5d42..db9a42352 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.4.1 + 0.5.0 ``` @@ -106,7 +106,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.4.1 + 0.5.0 ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index f84ad2b4c..ab9034954 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -7,7 +7,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.4.1" >> test.properties +echo "version.maven=0.5.0" >> test.properties echo "version.maven_autolink=0.4.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 4448e1c92..705219cab 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,7 +28,7 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.4.1 +version.maven=0.5.0 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.4.0 From 6c953e393cda34eef2b9e55091903f849ba377ac Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 27 Apr 2016 18:25:59 +1000 Subject: [PATCH 117/815] README: Tweak blurb --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index db9a42352..6489d9439 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,23 @@ commonmark-java =============== -Java implementation of [CommonMark], a specification of the [Markdown] format for turning plain text into formatted text. -Parses input to an AST (tree of nodes) and then renders to HTML. +Java library for parsing and rendering [Markdown] text according to the +[CommonMark] specification (and some extensions). -This started out as a port of [commonmark.js] and has evolved into a full -library with a nice Java API and some optional extensions. Features: +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: -* Small with minimal dependencies -* Extensible (see below) +* Small (minimal dependencies) * Fast (10-20 times faster than pegdown, see benchmarks in repo) +* Flexible (manipulate the AST after parsing, customize HTML rendering) +* Extensible (tables, strikethrough, autolinking and more, see below) Requirements: * Java 7 or above -* Works on Android; minimum API level 15 (see [commonmark-android-test](commonmark-android-test) directory) +* Works on Android, minimum API level 15 (see [commonmark-android-test](commonmark-android-test) directory) * The core has no dependencies; for extensions, see below Coordinates for core library (see all on [Maven Central]): @@ -162,7 +165,7 @@ literal: | document start here ``` - + Use class `YamlFrontMatterExtension` in artifact `commonmark-ext-yaml-front-matter`. To fetch metadata, use `YamlFrontMatterVisitor`. Contributing @@ -183,7 +186,7 @@ an issue and explaining the intended change. License ------- -Copyright (c) 2015 Atlassian and others. +Copyright (c) 2015-2016 Atlassian and others. BSD (2-clause) licensed, see LICENSE.txt file. From b29c0ec5aa187b251531bc8e3341800b80353735 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 20 May 2016 13:51:55 +1000 Subject: [PATCH 118/815] Bump exec-maven-plugin to 1.5.0 --- commonmark/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/pom.xml b/commonmark/pom.xml index fd4f25761..45f022d9b 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -38,7 +38,7 @@ org.codehaus.mojo exec-maven-plugin - 1.4.0 + 1.5.0 java test From 708718040a06557055c35dd9ff0e2aaec17a1b17 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 25 May 2016 13:15:45 +1000 Subject: [PATCH 119/815] Fix StringIndexOutOfBoundsException on line after tab (#52) This could occur in the following situation (\t is a tab): - foo \tbar # baz The tab on the bar line is a partially consumed tab, so columnIsInTab would be true. On the next line, we wouldn't reset columnIsInTab. By the time that addLine is called, we would try to get a substring starting from "after the tab", resulting in the StringIndexOutOfBoundsException. --- .../java/org/commonmark/internal/DocumentParser.java | 11 ++++++++--- .../java/org/commonmark/test/SpecialInputTest.java | 7 +++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 16919540b..af46f7664 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -33,11 +33,15 @@ public class DocumentParser implements ParserState { */ private int column = 0; + /** + * if the current column is within a tab character (partially consumed tab) + */ + private boolean columnIsInTab; + private int nextNonSpace = 0; private int nextNonSpaceColumn = 0; private int indent = 0; private boolean blank; - private boolean columnIsInTab; private final List blockParserFactories; private final InlineParserImpl inlineParser; @@ -144,8 +148,7 @@ private void incorporateLine(CharSequence ln) { line = Parsing.prepareLine(ln); index = 0; column = 0; - nextNonSpace = 0; - nextNonSpaceColumn = 0; + columnIsInTab = false; // For each containing block, try to parse the associated line start. // Bail out on failure: container will point to the last matching block. @@ -286,6 +289,8 @@ private void setNewIndex(int newIndex) { while (index < newIndex && index != line.length()) { advance(); } + // If we're going to an index as opposed to a column, we're never within a tab + columnIsInTab = false; } private void setNewColumn(int newColumn) { diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 59326a841..c71b0a7ff 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -77,4 +77,11 @@ public void orderedListMarkerOnly() { assertRendering("2.", "

      \n
    1. \n
    \n"); } + @Test + public void columnIsInTabOnPreviousLine() { + assertRendering("- foo\n\n\tbar\n\n# baz\n", + "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n

    baz

    \n"); + assertRendering("- foo\n\n\tbar\n# baz\n", + "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n

    baz

    \n"); + } } From 66523a4926cf0d8fa31df5d7a53bbe3ef05a2c4e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 25 May 2016 13:28:21 +1000 Subject: [PATCH 120/815] [maven-release-plugin] prepare release commonmark-parent-0.5.1 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/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 | 16 ++++++++-------- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 59dc7dbd8..712c4ffe4 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1-SNAPSHOT + 0.5.1 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index a16d8a3d5..6eb75c7dd 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1-SNAPSHOT + 0.5.1 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 4a94364bd..c35a37149 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1-SNAPSHOT + 0.5.1 commonmark-ext-gfm-tables diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 5b57740dc..229f34001 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.5.1-SNAPSHOT + 0.5.1 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 3a625370c..a61c8a2fd 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1-SNAPSHOT + 0.5.1 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 94f41b32c..3585ca989 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1-SNAPSHOT + 0.5.1 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 45f022d9b..d5d695556 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1-SNAPSHOT + 0.5.1 commonmark diff --git a/pom.xml b/pom.xml index 37e8520d9..735b4371a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1-SNAPSHOT + 0.5.1 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,32 +84,32 @@ com.atlassian.commonmark commonmark - 0.5.1-SNAPSHOT + 0.5.1 com.atlassian.commonmark commonmark-ext-autolink - 0.5.1-SNAPSHOT + 0.5.1 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.5.1-SNAPSHOT + 0.5.1 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.5.1-SNAPSHOT + 0.5.1 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.5.1-SNAPSHOT + 0.5.1 com.atlassian.commonmark commonmark-test-util - 0.5.1-SNAPSHOT + 0.5.1 @@ -152,7 +152,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.5.1 From b9074ed1bacb67566cee6f533cbe836af841319f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 25 May 2016 13:28:21 +1000 Subject: [PATCH 121/815] [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-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 712c4ffe4..55a3cc692 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1 + 0.5.2-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 6eb75c7dd..6c39d4f0f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1 + 0.5.2-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index c35a37149..313edfea8 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1 + 0.5.2-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 229f34001..f3d40307d 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.5.1 + 0.5.2-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index a61c8a2fd..cf18ffc60 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1 + 0.5.2-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 3585ca989..0eadb6307 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1 + 0.5.2-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index d5d695556..234edecc3 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1 + 0.5.2-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 735b4371a..434e225ce 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.1 + 0.5.2-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,32 +84,32 @@ com.atlassian.commonmark commonmark - 0.5.1 + 0.5.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.5.1 + 0.5.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.5.1 + 0.5.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.5.1 + 0.5.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.5.1 + 0.5.2-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.5.1 + 0.5.2-SNAPSHOT @@ -152,7 +152,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.5.1 + HEAD From 009b76362b4d575709f822612c7b6cae412c47fc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 25 May 2016 14:48:28 +1000 Subject: [PATCH 122/815] Bump version in READMEs --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6489d9439..8649f97d8 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.5.0 + 0.5.1 ``` @@ -109,7 +109,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.5.0 + 0.5.1 ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index ab9034954..3e1808cc5 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -7,7 +7,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.5.0" >> test.properties +echo "version.maven=0.5.1" >> test.properties echo "version.maven_autolink=0.4.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 705219cab..220b93f1c 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.5.0 +version.maven=0.5.1 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.4.0 # Version number of commonmark and extensions in project. -version.snapshot=0.4.2-SNAPSHOT +version.snapshot=0.5.2-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.4.0 ``` From b53d157ecd1ae6cebe6b27506be2603430d9301f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 16 Jun 2016 17:00:18 +1000 Subject: [PATCH 123/815] Fix max length for link labels (999, not 1000) --- .../main/java/org/commonmark/test/Strings.java | 12 ++++++++++++ .../org/commonmark/internal/InlineParserImpl.java | 4 ++-- .../org/commonmark/test/PathologicalTest.java | 10 ++-------- .../org/commonmark/test/SpecialInputTest.java | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 commonmark-test-util/src/main/java/org/commonmark/test/Strings.java diff --git a/commonmark-test-util/src/main/java/org/commonmark/test/Strings.java b/commonmark-test-util/src/main/java/org/commonmark/test/Strings.java new file mode 100644 index 000000000..79c91b592 --- /dev/null +++ b/commonmark-test-util/src/main/java/org/commonmark/test/Strings.java @@ -0,0 +1,12 @@ +package org.commonmark.test; + +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/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 09fcf2962..6ef6eca59 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -20,7 +20,7 @@ public class InlineParserImpl implements InlineParser { private static final String IN_PARENS_NOSP = "\\((" + REG_CHAR + '|' + ESCAPED_CHAR + ")*\\)"; private static final String HTMLCOMMENT = "|"; private static final String PROCESSINGINSTRUCTION = "[<][?].*?[?][>]"; - private static final String DECLARATION = "]*>"; + private static final String DECLARATION = "]*>"; private static final String CDATA = ""; private static final String HTMLTAG = "(?:" + Parsing.OPENTAG + "|" + Parsing.CLOSETAG + "|" + HTMLCOMMENT + "|" + PROCESSINGINSTRUCTION + "|" + DECLARATION + "|" + CDATA + ")"; @@ -46,7 +46,7 @@ public class InlineParserImpl implements InlineParser { "^(?:" + REG_CHAR + "+|" + ESCAPED_CHAR + "|\\\\|" + IN_PARENS_NOSP + ")*"); private static final Pattern LINK_LABEL = Pattern - .compile("^\\[(?:[^\\\\\\[\\]]|" + ESCAPED_CHAR + "|\\\\){0,1000}\\]"); + .compile("^\\[(?:[^\\\\\\[\\]]|" + ESCAPED_CHAR + "|\\\\){0,999}\\]"); private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 4f8dcfb3b..30b19ce86 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -10,6 +10,8 @@ import java.util.concurrent.TimeUnit; +import static org.commonmark.test.Strings.repeat; + /** * Pathological input cases (from commonmark.js). */ @@ -97,12 +99,4 @@ public void nestedBlockQuotes() { repeat("
    \n", x) + "

    a

    \n" + repeat("
    \n", x)); } - - 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(); - } } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index c71b0a7ff..454760f88 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -84,4 +84,19 @@ public void columnIsInTabOnPreviousLine() { assertRendering("- foo\n\n\tbar\n# baz\n", "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n

    baz

    \n"); } + + @Test + public void linkLabelLength() { + String label1 = Strings.repeat("a", 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); + assertRendering("[foo][" + label2 + "]\n\n[" + label2 + "]: /", "

    foo

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

    [foo][12" + label2 + "]

    \n

    [12" + label2 + "]: /

    \n"); + } } From c4d51424c017090ef1a266a43e3f63c7a21680e6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 16 Jun 2016 17:22:05 +1000 Subject: [PATCH 124/815] Simplify building of literal in FencedCodeBlockParser --- .../org/commonmark/internal/BlockContent.java | 4 --- .../internal/FencedCodeBlockParser.java | 26 +++++++------------ 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockContent.java b/commonmark/src/main/java/org/commonmark/internal/BlockContent.java index f278c20c0..9a9ce6f44 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockContent.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockContent.java @@ -22,10 +22,6 @@ public void add(CharSequence line) { lineCount++; } - public boolean hasSingleLine() { - return lineCount == 1; - } - public String getString() { return sb.toString(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index 15c430278..aa9f73daa 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -15,7 +15,9 @@ public class FencedCodeBlockParser extends AbstractBlockParser { private static final Pattern CLOSING_FENCE = Pattern.compile("^(?:`{3,}|~{3,})(?= *$)"); private final FencedCodeBlock block = new FencedCodeBlock(); - private BlockContent content = new BlockContent(); + + private String firstLine; + private StringBuilder otherLines = new StringBuilder(); public FencedCodeBlockParser(char fenceChar, int fenceLength, int fenceIndent) { block.setFenceChar(fenceChar); @@ -55,27 +57,19 @@ public BlockContinue tryContinue(ParserState state) { @Override public void addLine(CharSequence line) { - content.add(line); + if (firstLine == null) { + firstLine = line.toString(); + } else { + otherLines.append(line); + otherLines.append('\n'); + } } @Override public void closeBlock() { - boolean singleLine = content.hasSingleLine(); - // add trailing newline - content.add(""); - String contentString = content.getString(); - content = null; - // first line becomes info string - int firstNewline = contentString.indexOf('\n'); - String firstLine = contentString.substring(0, firstNewline); block.setInfo(unescapeString(firstLine.trim())); - if (singleLine) { - block.setLiteral(""); - } else { - String literal = contentString.substring(firstNewline + 1); - block.setLiteral(literal); - } + block.setLiteral(otherLines.toString()); } public static class Factory extends AbstractBlockParserFactory { From bc1165c32dc5f69f0dc751ffbab015feed86a771 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 17 Jun 2016 13:54:13 +1000 Subject: [PATCH 125/815] Simplify merging of adjacent text nodes Before, we would merge them while parsing. It was very complicated because we had to be careful to not add other nodes if we still had outstanding text nodes. Merging them at the end makes things simpler. It also means we can allocate a StringBuilder with the final size instead of having to expand it. We could make it even simpler by using a visitor at the very end of parsing, but I like the current approach because it doesn't require descending into child nodes. What motivated this change is work on source positions for inline nodes. This change should make it simpler to implement. --- .../org/commonmark/internal/Delimiter.java | 20 --- .../commonmark/internal/InlineParserImpl.java | 122 +++++++++--------- 2 files changed, 61 insertions(+), 81 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index 127a834b5..bdb30b21d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -1,6 +1,5 @@ package org.commonmark.internal; -import org.commonmark.node.Node; import org.commonmark.node.Text; class Delimiter { @@ -39,23 +38,4 @@ class Delimiter { this.previous = previous; this.index = index; } - - Text getPreviousNonDelimiterTextNode() { - Node previousNode = node.getPrevious(); - if (previousNode instanceof Text && (this.previous == null || this.previous.node != previousNode)) { - return (Text) previousNode; - } else { - return null; - } - } - - Text getNextNonDelimiterTextNode() { - Node nextNode = node.getNext(); - if (nextNode instanceof Text && (this.next == null || this.next.node != nextNode)) { - return (Text) nextNode; - } else { - return null; - } - } - } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 6ef6eca59..2f90ef602 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -96,8 +96,6 @@ public class InlineParserImpl implements InlineParser { */ private Delimiter bracketDelimiterBottom = null; - private StringBuilder currentText; - public InlineParserImpl(BitSet specialCharacters, BitSet delimiterCharacters, Map delimiterProcessors) { this.delimiterProcessors = delimiterProcessors; this.delimiterCharacters = delimiterCharacters; @@ -166,9 +164,9 @@ public void parse(String content, Node block) { do { moreToParse = parseInline(); } while (moreToParse); - flushTextNode(); processDelimiters(null); + mergeTextNodes(block.getFirstChild(), block.getLastChild()); } /** @@ -246,34 +244,18 @@ public int parseReference(String s) { return index - startIndex; } - private void appendText(CharSequence text) { - appendText(text, 0, text.length()); + private Text appendText(CharSequence text, int beginIndex, int endIndex) { + return appendText(text.subSequence(beginIndex, endIndex)); } - private void appendText(CharSequence text, int beginIndex, int endIndex) { - if (currentText == null) { - currentText = new StringBuilder(endIndex - beginIndex + 16); - } - currentText.append(text, beginIndex, endIndex); - } - - private void appendNode(Node node) { - flushTextNode(); - block.appendChild(node); - } - - // In some cases, we don't want the text to be appended to an existing node, we need it separate - private Text appendSeparateText(String text) { - Text node = new Text(text); + private Text appendText(CharSequence text) { + Text node = new Text(text.toString()); appendNode(node); return node; } - private void flushTextNode() { - if (currentText != null) { - block.appendChild(new Text(currentText.toString())); - currentText = null; - } + private void appendNode(Node node) { + block.appendChild(node); } /** @@ -376,9 +358,6 @@ private boolean spnl() { private boolean parseNewline() { index++; // assume we're at a \n - // We're gonna add a new node in any case and we need to check the last text node, so flush outstanding text. - flushTextNode(); - Node lastChild = block.getLastChild(); // Check previous text for trailing spaces. // The "endsWith" is an optimization to avoid an RE match in the common case. @@ -458,7 +437,7 @@ private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char deli int startIndex = index; index += numDelims; - Text node = appendSeparateText(input.substring(startIndex, index)); + Text node = appendText(input, startIndex, index); // Add entry to stack for this opener this.delimiter = new Delimiter(node, this.delimiter, startIndex); @@ -480,7 +459,7 @@ private boolean parseOpenBracket() { int startIndex = index; index++; - Text node = appendSeparateText("["); + Text node = appendText("["); // Add entry to stack for this opener this.delimiter = new Delimiter(node, this.delimiter, startIndex); @@ -506,7 +485,7 @@ private boolean parseBang() { if (peek() == '[') { index++; - Text node = appendSeparateText("!["); + Text node = appendText("!["); // Add entry to stack for this opener this.delimiter = new Delimiter(node, this.delimiter, startIndex + 1); @@ -611,10 +590,6 @@ private boolean parseCloseBracket() { boolean isImage = opener.delimiterChar == '!'; Node linkOrImage = isImage ? new Image(dest, title) : new Link(dest, title); - // Flush text now. We don't need to worry about combining it with adjacent text nodes, as we'll wrap it in a - // link or image node. - flushTextNode(); - Node node = opener.node.getNext(); while (node != null) { Node next = node.getNext(); @@ -625,6 +600,7 @@ private boolean parseCloseBracket() { // Process delimiters such as emphasis inside link/image processDelimiters(opener); + mergeTextNodes(linkOrImage.getFirstChild(), linkOrImage.getLastChild()); removeDelimiterAndNode(opener); // Links within links are not allowed. We found this link, so there can be no other link around it. @@ -879,9 +855,12 @@ private void processDelimiters(Delimiter stackBottom) { closerNode.getLiteral().length() - useDelims)); removeDelimitersBetween(opener, closer); + // The delimiter processor can re-parent the nodes between opener and closer, + // so make sure they're contiguous already. + mergeTextNodes(openerNode.getNext(), closerNode.getPrevious()); delimiterProcessor.process(openerNode, closerNode, useDelims); - // if opener has 0 delims, remove it and the inline + // No delimiter characters left to process, so we can remove delimiter and the now empty node. if (opener.numDelims == 0) { removeDelimiterAndNode(opener); } @@ -913,14 +892,6 @@ private void removeDelimitersBetween(Delimiter opener, Delimiter closer) { */ private void removeDelimiterAndNode(Delimiter delim) { Text node = delim.node; - Text previousText = delim.getPreviousNonDelimiterTextNode(); - Text nextText = delim.getNextNonDelimiterTextNode(); - if (previousText != null && nextText != null) { - // Merge adjacent text nodes - previousText.setLiteral(previousText.getLiteral() + nextText.getLiteral()); - nextText.unlink(); - } - node.unlink(); removeDelimiter(delim); } @@ -929,23 +900,6 @@ private void removeDelimiterAndNode(Delimiter delim) { * Remove the delimiter but keep the corresponding node as text. For unused delimiters such as `_` in `foo_bar`. */ private void removeDelimiterKeepNode(Delimiter delim) { - Text node = delim.node; - Text previousText = delim.getPreviousNonDelimiterTextNode(); - Text nextText = delim.getNextNonDelimiterTextNode(); - if (previousText != null || nextText != null) { - // Merge adjacent text nodes into one - StringBuilder sb = new StringBuilder(node.getLiteral()); - if (previousText != null) { - sb.insert(0, previousText.getLiteral()); - previousText.unlink(); - } - if (nextText != null) { - sb.append(nextText.getLiteral()); - nextText.unlink(); - } - node.setLiteral(sb.toString()); - } - removeDelimiter(delim); } @@ -960,4 +914,50 @@ private void removeDelimiter(Delimiter delim) { delim.next.previous = delim.previous; } } + + private void mergeTextNodes(Node fromNode, Node toNode) { + Text first = null; + Text last = null; + int length = 0; + + Node node = fromNode; + while (node != null) { + if (node instanceof Text) { + Text text = (Text) node; + if (first == null) { + first = text; + } + length += text.getLiteral().length(); + last = text; + } else { + mergeIfNeeded(first, last, length); + first = null; + last = null; + length = 0; + } + if (node == toNode) { + break; + } + node = node.getNext(); + } + + mergeIfNeeded(first, last, length); + } + + private void mergeIfNeeded(Text first, Text last, int textLength) { + if (first != null && last != null && first != last) { + StringBuilder sb = new StringBuilder(textLength); + sb.append(first.getLiteral()); + Node node = first.getNext(); + Node stop = last.getNext(); + while (node != stop) { + sb.append(((Text) node).getLiteral()); + Node unlink = node; + node = node.getNext(); + unlink.unlink(); + } + String literal = sb.toString(); + first.setLiteral(literal); + } + } } From 47b31c0e7c0997bae2c5400aa1beec0601054099 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 17 Jun 2016 18:31:23 +1000 Subject: [PATCH 126/815] Simplify and speed up brackets processing (links/images) The key insight for the simplification is that we don't need to keep the brackets in the same stack as the other delimiters. All we need to know is which delimiter came before the bracket, so that we can process the emphasis in the image/link text. A nice side effect of this is that we don't need to go through the delimiters when looking for the last opening bracket, we already have it. This means we can drop the `bracketDelimiterBottom` optimization. And by remembering the `bracketAfter` on the last bracket, we can immediately discard matched brackets instead of marking them as `matched` and keeping them in the stack. All this together allows us to properly fix the `nestedBrackets` pathological case, yay :). --- .../java/org/commonmark/internal/Bracket.java | 49 ++++++++++ .../org/commonmark/internal/Delimiter.java | 17 +--- .../commonmark/internal/InlineParserImpl.java | 94 ++++++++----------- .../org/commonmark/test/PathologicalTest.java | 2 +- .../org/commonmark/test/SpecialInputTest.java | 7 ++ 5 files changed, 98 insertions(+), 71 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/Bracket.java diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java new file mode 100644 index 000000000..08a7da088 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -0,0 +1,49 @@ +package org.commonmark.internal; + +import org.commonmark.node.Text; + +/** + * Opening bracket for links ([) or images (![). + */ +class Bracket { + + final Text node; + final int index; + final boolean image; + + /** + * Previous bracket. + */ + Bracket previous; + + /** + * Previous delimiter (emphasis, etc) before this bracket. + */ + Delimiter previousDelimiter; + + /** + * Whether this bracket is allowed to form a link/image (also known as "active"). + */ + boolean allowed = true; + + /** + * Whether there is an unescaped bracket (opening or closing) anywhere after this opening bracket. + */ + boolean bracketAfter = false; + + static Bracket link(Text node, int index, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(node, index, previous, previousDelimiter, false); + } + + static Bracket image(Text node, int index, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(node, index, previous, previousDelimiter, true); + } + + private Bracket(Text node, int index, Bracket previous, Delimiter previousDelimiter, boolean image) { + this.node = node; + this.index = index; + this.image = image; + this.previous = previous; + this.previousDelimiter = previousDelimiter; + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index bdb30b21d..5a715aaef 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -2,10 +2,12 @@ import org.commonmark.node.Text; +/** + * Delimiter (emphasis, strong emphasis or custom emphasis). + */ class Delimiter { final Text node; - final int index; Delimiter previous; Delimiter next; @@ -23,19 +25,8 @@ class Delimiter { */ boolean canClose = false; - /** - * Whether this delimiter is allowed to form a link/image. - */ - boolean allowed = true; - - /** - * Skip this delimiter when looking for a link/image opener because it was already matched. - */ - boolean matched = false; - - Delimiter(Text node, Delimiter previous, int index) { + Delimiter(Text node, Delimiter previous) { this.node = node; this.previous = previous; - this.index = index; } } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 2f90ef602..18a641c22 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -87,14 +87,15 @@ public class InlineParserImpl implements InlineParser { private int index; /** - * Stack of delimiters (emphasis, strong emphasis). + * Stack of delimiters (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different + * from the algorithm described in the spec.) */ private Delimiter delimiter; /** - * Earliest possible bracket delimiter to go back to when searching for opener. + * Last parsed opening bracket ([ or ![)). */ - private Delimiter bracketDelimiterBottom = null; + private Bracket lastBracket; public InlineParserImpl(BitSet specialCharacters, BitSet delimiterCharacters, Map delimiterProcessors) { this.delimiterProcessors = delimiterProcessors; @@ -158,7 +159,7 @@ public void parse(String content, Node block) { this.input = content.trim(); this.index = 0; this.delimiter = null; - this.bracketDelimiterBottom = null; + this.lastBracket = null; boolean moreToParse; do { @@ -440,7 +441,7 @@ private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char deli Text node = appendText(input, startIndex, index); // Add entry to stack for this opener - this.delimiter = new Delimiter(node, this.delimiter, startIndex); + this.delimiter = new Delimiter(node, this.delimiter); this.delimiter.delimiterChar = delimiterChar; this.delimiter.numDelims = numDelims; this.delimiter.canOpen = res.canOpen; @@ -462,15 +463,7 @@ private boolean parseOpenBracket() { Text node = appendText("["); // Add entry to stack for this opener - this.delimiter = new Delimiter(node, this.delimiter, startIndex); - this.delimiter.delimiterChar = '['; - this.delimiter.numDelims = 1; - this.delimiter.canOpen = true; - this.delimiter.canClose = false; - this.delimiter.allowed = true; - if (this.delimiter.previous != null) { - this.delimiter.previous.next = this.delimiter; - } + lastBracket = Bracket.link(node, startIndex, lastBracket, delimiter); return true; } @@ -488,15 +481,7 @@ private boolean parseBang() { Text node = appendText("!["); // Add entry to stack for this opener - this.delimiter = new Delimiter(node, this.delimiter, startIndex + 1); - this.delimiter.delimiterChar = '!'; - this.delimiter.numDelims = 1; - this.delimiter.canOpen = true; - this.delimiter.canClose = false; - this.delimiter.allowed = true; - if (this.delimiter.previous != null) { - this.delimiter.previous.next = this.delimiter; - } + lastBracket = Bracket.image(node, startIndex + 1, lastBracket, delimiter); } else { appendText("!"); } @@ -511,32 +496,18 @@ private boolean parseCloseBracket() { index++; int startIndex = index; - boolean containsBracket = false; - // look through stack of delimiters for a [ or ![ - Delimiter opener = this.delimiter; - while (opener != bracketDelimiterBottom) { - if (opener.delimiterChar == '[' || opener.delimiterChar == '!') { - if (!opener.matched) { - break; - } - containsBracket = true; - } - opener = opener.previous; - } - - if (opener == bracketDelimiterBottom) { - // No matched opener, just return a literal. + // Get previous `[` or `![` + Bracket opener = lastBracket; + if (opener == null) { + // No matching opener, just return a literal. appendText("]"); - // No need to search same delimiters for openers next time. - bracketDelimiterBottom = this.delimiter; return true; } if (!opener.allowed) { // Matching opener but it's not allowed, just return a literal. appendText("]"); - // We could remove the opener now, but that would complicate text node merging. So just skip it next time. - opener.matched = true; + removeLastBracket(); return true; } @@ -570,8 +541,10 @@ private boolean parseCloseBracket() { String ref = null; if (labelLength > 2) { ref = input.substring(beforeLabel, beforeLabel + labelLength); - } else if (!containsBracket) { - // Empty or missing second label can only be a reference if there's no unescaped bracket in it. + } else if (!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 = input.substring(opener.index, startIndex); } @@ -587,8 +560,7 @@ private boolean parseCloseBracket() { if (isLinkOrImage) { // If we got here, open is a potential opener - boolean isImage = opener.delimiterChar == '!'; - Node linkOrImage = isImage ? new Image(dest, title) : new Link(dest, title); + Node linkOrImage = opener.image ? new Image(dest, title) : new Link(dest, title); Node node = opener.node.getNext(); while (node != null) { @@ -599,19 +571,21 @@ private boolean parseCloseBracket() { appendNode(linkOrImage); // Process delimiters such as emphasis inside link/image - processDelimiters(opener); + processDelimiters(opener.previousDelimiter); mergeTextNodes(linkOrImage.getFirstChild(), linkOrImage.getLastChild()); - removeDelimiterAndNode(opener); + // 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 (!isImage) { - Delimiter delim = this.delimiter; - while (delim != null) { - if (delim.delimiterChar == '[') { + 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. - delim.allowed = false; + bracket.allowed = false; } - delim = delim.previous; + bracket = bracket.previous; } } @@ -620,9 +594,8 @@ private boolean parseCloseBracket() { } else { // no link or image appendText("]"); - // We could remove the opener now, but that would complicate text node merging. - // E.g. `[link] (/uri)` isn't a link because of the space, so we want to keep appending text. - opener.matched = true; + removeLastBracket(); + index = startIndex; return true; } @@ -915,6 +888,13 @@ private void removeDelimiter(Delimiter delim) { } } + private void removeLastBracket() { + lastBracket = lastBracket.previous; + if (lastBracket != null) { + lastBracket.bracketAfter = true; + } + } + private void mergeTextNodes(Node fromNode, Node toNode) { Text first = null; Text last = null; diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 30b19ce86..8b2125e8f 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -18,7 +18,7 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class PathologicalTest extends CoreRenderingTestCase { - private int x = 10_000; + private int x = 100_000; @Rule public Timeout timeout = new Timeout(3, TimeUnit.SECONDS); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 454760f88..3d0b6e536 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -85,6 +85,13 @@ public void columnIsInTabOnPreviousLine() { "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n

    baz

    \n"); } + @Test + public void linkLabelWithBracket() { + assertRendering("[a[b]\n\n[a[b]: /", "

    [a[b]

    \n

    [a[b]: /

    \n"); + assertRendering("[a]b]\n\n[a]b]: /", "

    [a]b]

    \n

    [a]b]: /

    \n"); + assertRendering("[a[b]]\n\n[a[b]]: /", "

    [a[b]]

    \n

    [a[b]]: /

    \n"); + } + @Test public void linkLabelLength() { String label1 = Strings.repeat("a", 999); From 46edaeda7c7d8da24d2cc28806ce517979350db5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 17 Jun 2016 19:11:25 +1000 Subject: [PATCH 127/815] Clean up Delimiter class and field name --- .../org/commonmark/internal/Delimiter.java | 21 +++++++------ .../commonmark/internal/InlineParserImpl.java | 31 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index 5a715aaef..9c42f6dea 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -8,25 +8,28 @@ class Delimiter { final Text node; - - Delimiter previous; - Delimiter next; - - char delimiterChar; - int numDelims = 1; + final char delimiterChar; /** * Can open emphasis, see spec. */ - boolean canOpen = true; + final boolean canOpen; /** * Can close emphasis, see spec. */ - boolean canClose = false; + final boolean canClose; + + Delimiter previous; + Delimiter next; + + int numDelims = 1; - Delimiter(Text node, Delimiter previous) { + Delimiter(Text node, char delimiterChar, boolean canOpen, boolean canClose, Delimiter previous) { this.node = node; + this.delimiterChar = delimiterChar; + this.canOpen = canOpen; + this.canClose = canClose; 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 18a641c22..ac6432527 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -87,13 +87,13 @@ public class InlineParserImpl implements InlineParser { private int index; /** - * Stack of delimiters (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different + * Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different * from the algorithm described in the spec.) */ - private Delimiter delimiter; + private Delimiter lastDelimiter; /** - * Last parsed opening bracket ([ or ![)). + * Top opening bracket ([ or ![)). */ private Bracket lastBracket; @@ -158,7 +158,7 @@ public void parse(String content, Node block) { this.block = block; this.input = content.trim(); this.index = 0; - this.delimiter = null; + this.lastDelimiter = null; this.lastBracket = null; boolean moreToParse; @@ -441,13 +441,10 @@ private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char deli Text node = appendText(input, startIndex, index); // Add entry to stack for this opener - this.delimiter = new Delimiter(node, this.delimiter); - this.delimiter.delimiterChar = delimiterChar; - this.delimiter.numDelims = numDelims; - this.delimiter.canOpen = res.canOpen; - this.delimiter.canClose = res.canClose; - if (this.delimiter.previous != null) { - this.delimiter.previous.next = this.delimiter; + lastDelimiter = new Delimiter(node, delimiterChar, res.canOpen, res.canClose, lastDelimiter); + lastDelimiter.numDelims = numDelims; + if (lastDelimiter.previous != null) { + lastDelimiter.previous.next = lastDelimiter; } return true; @@ -463,7 +460,7 @@ private boolean parseOpenBracket() { Text node = appendText("["); // Add entry to stack for this opener - lastBracket = Bracket.link(node, startIndex, lastBracket, delimiter); + lastBracket = Bracket.link(node, startIndex, lastBracket, lastDelimiter); return true; } @@ -481,7 +478,7 @@ private boolean parseBang() { Text node = appendText("!["); // Add entry to stack for this opener - lastBracket = Bracket.image(node, startIndex + 1, lastBracket, delimiter); + lastBracket = Bracket.image(node, startIndex + 1, lastBracket, lastDelimiter); } else { appendText("!"); } @@ -769,7 +766,7 @@ private void processDelimiters(Delimiter stackBottom) { Map openersBottom = new HashMap<>(); // find first closer above stackBottom: - Delimiter closer = this.delimiter; + Delimiter closer = lastDelimiter; while (closer != null && closer.previous != stackBottom) { closer = closer.previous; } @@ -846,8 +843,8 @@ private void processDelimiters(Delimiter stackBottom) { } // remove all delimiters - while (delimiter != null && delimiter != stackBottom) { - removeDelimiterKeepNode(delimiter); + while (lastDelimiter != null && lastDelimiter != stackBottom) { + removeDelimiterKeepNode(lastDelimiter); } } @@ -882,7 +879,7 @@ private void removeDelimiter(Delimiter delim) { } if (delim.next == null) { // top of stack - this.delimiter = delim.previous; + lastDelimiter = delim.previous; } else { delim.next.previous = delim.previous; } From b2043dd693b25af748ae3b3399c6193c65b04a1b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 20 Jun 2016 12:35:28 +1000 Subject: [PATCH 128/815] Set bracketAfter when adding bracket The end result is the same, just seems like a nicer place to put it. --- .../java/org/commonmark/internal/Bracket.java | 4 ++-- .../commonmark/internal/InlineParserImpl.java | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index 08a7da088..15b58c643 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -14,12 +14,12 @@ class Bracket { /** * Previous bracket. */ - Bracket previous; + final Bracket previous; /** * Previous delimiter (emphasis, etc) before this bracket. */ - Delimiter previousDelimiter; + final Delimiter previousDelimiter; /** * Whether this bracket is allowed to form a link/image (also known as "active"). diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index ac6432527..e131a793b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -460,7 +460,7 @@ private boolean parseOpenBracket() { Text node = appendText("["); // Add entry to stack for this opener - lastBracket = Bracket.link(node, startIndex, lastBracket, lastDelimiter); + addBracket(Bracket.link(node, startIndex, lastBracket, lastDelimiter)); return true; } @@ -478,7 +478,7 @@ private boolean parseBang() { Text node = appendText("!["); // Add entry to stack for this opener - lastBracket = Bracket.image(node, startIndex + 1, lastBracket, lastDelimiter); + addBracket(Bracket.image(node, startIndex + 1, lastBracket, lastDelimiter)); } else { appendText("!"); } @@ -598,6 +598,17 @@ private boolean parseCloseBracket() { } } + private void addBracket(Bracket bracket) { + if (lastBracket != null) { + lastBracket.bracketAfter = true; + } + lastBracket = bracket; + } + + private void removeLastBracket() { + lastBracket = lastBracket.previous; + } + /** * Attempt to parse link destination, returning the string or null if no match. */ @@ -885,13 +896,6 @@ private void removeDelimiter(Delimiter delim) { } } - private void removeLastBracket() { - lastBracket = lastBracket.previous; - if (lastBracket != null) { - lastBracket.bracketAfter = true; - } - } - private void mergeTextNodes(Node fromNode, Node toNode) { Text first = null; Text last = null; From 2fd3e85cf65b881ae1a65e10f50f4a37d6b3c463 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 26 Jun 2016 21:10:52 +1000 Subject: [PATCH 129/815] Update to autolink 0.5.0 * Stop URLs at more invalid characters, notably '<' and '>'. According to RFC 3987, angle brackets are not allowed in URLs, and other linkers don't seem to allow them either. --- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 ++-- commonmark-ext-autolink/pom.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index 3e1808cc5..426cab9ec 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -8,7 +8,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties echo "version.maven=0.5.1" >> test.properties -echo "version.maven_autolink=0.4.0" >> test.properties +echo "version.maven_autolink=0.5.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 220b93f1c..da415b332 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -30,12 +30,12 @@ path.report=../report # Version number of commonmark and extensions in maven central. version.maven=0.5.1 # Version number of autolink in maven central (not bundled with extension jar). -version.maven_autolink=0.4.0 +version.maven_autolink=0.5.0 # Version number of commonmark and extensions in project. version.snapshot=0.5.2-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). -version.snapshot_autolink=0.4.0 +version.snapshot_autolink=0.5.0 ``` If you're going to test on device with Android 15 then you can skip downloading emulator. diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 55a3cc692..5c1e6bb17 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.4.0 + 0.5.0 From 12168d4429f7d17fc77894ab47446efd371ffb1f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Jul 2016 17:58:14 +1000 Subject: [PATCH 130/815] travis: Add coverage reporting using coveralls --- .travis.yml | 6 ++++++ pom.xml | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/.travis.yml b/.travis.yml index 56f83970c..871705464 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,3 +18,9 @@ android: script: - 'if [[ $TEST = java ]]; then mvn test -Dsurefire.useFile=false; fi' - 'if [[ $TEST = android ]]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' +deploy: + provider: script + script: mvn clean test jacoco:report coveralls:report -Pcoverage + on: + jdk: oraclejdk8 + condition: "$TEST = java" diff --git a/pom.xml b/pom.xml index 434e225ce..6bf9ec6b9 100644 --- a/pom.xml +++ b/pom.xml @@ -131,6 +131,34 @@ + + + coverage + + + + org.jacoco + jacoco-maven-plugin + 0.7.7.201606060606 + + + prepare-agent + + prepare-agent + + + + + + org.eluder.coveralls + coveralls-maven-plugin + 4.2.0 + + + + + + BSD 2-Clause License From 5f511c721f484c72f2d590ea74bf799ff9fd1d3b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Jul 2016 18:09:43 +1000 Subject: [PATCH 131/815] Add coverage badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8649f97d8..fe9e65d3e 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ See the [spec.txt](commonmark-test-util/src/main/resources/spec.txt) file if you're wondering which version of the spec is currently implemented. [![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) +[![Coverage status](https://coveralls.io/repos/github/atlassian/commonmark-java/badge.svg?branch=master)](https://coveralls.io/github/atlassian/commonmark-java?branch=master) Usage From de46519f9b20c1620b2e0ca1535d871dc108fb84 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Jul 2016 18:13:54 +1000 Subject: [PATCH 132/815] Exclude classes from test-util from coverage --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 6bf9ec6b9..513d2834b 100644 --- a/pom.xml +++ b/pom.xml @@ -140,6 +140,13 @@ org.jacoco jacoco-maven-plugin 0.7.7.201606060606 + + + + org.commonmark.spec.* + org.commonmark.test.* + + prepare-agent From 400dc62ea92a451b81154b7465c2327a534a594d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Jul 2016 18:29:42 +1000 Subject: [PATCH 133/815] Use slashes instead of dots in excludes --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 513d2834b..af19260f5 100644 --- a/pom.xml +++ b/pom.xml @@ -143,8 +143,8 @@ - org.commonmark.spec.* - org.commonmark.test.* + org/commonmark/spec/* + org/commonmark/test/* From 28d278892fca162c71fea95173bc2d29fa7bb481 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 5 Jul 2016 16:38:52 +1000 Subject: [PATCH 134/815] Aggregate coverage using Jacoco Before, e.g. Parser.Builder.extensions wasn't shown as being covered at all, probably because the integration tests that use it are in a different module. By letting Jacoco aggregate the results and then have Coveralls submit that, the coverage seems to be correct now. --- .travis.yml | 2 +- commonmark-integration-test/pom.xml | 36 ++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 871705464..9dcfc6b62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ script: - 'if [[ $TEST = android ]]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' deploy: provider: script - script: mvn clean test jacoco:report coveralls:report -Pcoverage + script: mvn clean test coveralls:report -Pcoverage on: jdk: oraclejdk8 condition: "$TEST = java" diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index cf18ffc60..5aa653601 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 com.atlassian.commonmark @@ -15,28 +16,24 @@ com.atlassian.commonmark commonmark - test com.atlassian.commonmark commonmark-ext-autolink - test com.atlassian.commonmark commonmark-ext-gfm-strikethrough - test com.atlassian.commonmark commonmark-ext-gfm-tables - test com.atlassian.commonmark commonmark-ext-yaml-front-matter - test + org.pegdown pegdown @@ -61,6 +58,33 @@ + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + ${project.parent.reporting.outputDirectory}/jacoco + + + + report-aggregate + + report-aggregate + + test + + + + + + + + From d4d3814ff7b9918078af3f994de6721ee9c8aaca Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 5 Jul 2016 16:48:43 +1000 Subject: [PATCH 135/815] Add Maven Central badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fe9e65d3e..5c8b96748 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ you're wondering which version of the spec is currently implemented. [![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) [![Coverage status](https://coveralls.io/repos/github/atlassian/commonmark-java/badge.svg?branch=master)](https://coveralls.io/github/atlassian/commonmark-java?branch=master) +[![Maven Central status](https://img.shields.io/maven-central/v/com.atlassian.commonmark/commonmark.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.atlassian.commonmark%22) Usage From 08ee4daa56f16fc04ab2cbea9dac8449365e2e46 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Jul 2016 15:44:08 +0200 Subject: [PATCH 136/815] Update to CommonMark spec 0.26 (#55) --- .../src/main/resources/spec.txt | 477 ++++++++++-------- 1 file changed, 275 insertions(+), 202 deletions(-) diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index 1a4a7dc90..e2b6834d8 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.25 -date: '2016-03-24' +version: 0.26 +date: '2016-07-15' license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -13,12 +13,90 @@ license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' Markdown is a plain text format for writing structured documents, based on conventions used for indicating formatting in email and usenet posts. It was developed in 2004 by John Gruber, who wrote -the first Markdown-to-HTML converter in perl, and it soon became -widely used in websites. By 2014 there were dozens of -implementations in many languages. Some of them extended basic -Markdown syntax with conventions for footnotes, definition lists, -tables, and other constructs, and some allowed output not just in -HTML but in LaTeX and many other formats. +the first Markdown-to-HTML converter in Perl, and it soon became +ubiquitous. In the next decade, dozens of implementations were +developed in many languages. Some extended the original +Markdown syntax with conventions for footnotes, tables, and +other document elements. Some allowed Markdown documents to be +rendered in formats other than HTML. Websites like Reddit, +StackOverflow, and GitHub had millions of people using Markdown. +And Markdown started to be used beyond the web, to author books, +articles, slide shows, letters, and lecture notes. + +What distinguishes Markdown from many other lightweight markup +syntaxes, which are often easier to write, is its readability. +As Gruber writes: + +> The overriding design goal for Markdown's formatting syntax is +> to make it as readable as possible. The idea is that a +> 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 +an equivalent sample of Markdown. Here is a sample of +AsciiDoc from the AsciiDoc manual: + +``` +1. List item one. ++ +List item one continued with a second paragraph followed by an +Indented block. ++ +................. +$ ls *.sh +$ mv *.sh ~/tmp +................. ++ +List item continued with a third paragraph. + +2. List item two continued with an open block. ++ +-- +This paragraph is part of the preceding list item. + +a. This list is nested and does not require explicit item +continuation. ++ +This paragraph is part of the preceding list item. + +b. List item b. + +This paragraph belongs to item two of the outer list. +-- +``` + +And here is the equivalent in Markdown: +``` +1. List item one. + + List item one continued with a second paragraph followed by an + Indented block. + + $ ls *.sh + $ mv *.sh ~/tmp + + List item continued with a third paragraph. + +2. List item two continued with an open block. + + This paragraph is part of the preceding list item. + + 1. This list is nested and does not require explicit item continuation. + + This paragraph is part of the preceding list item. + + 2. List item b. + + This paragraph belongs to item two of the outer list. +``` + +The AsciiDoc version is, arguably, easier to write. You don't need +to worry about indentation. But the Markdown version is much easier +to read. The nesting of list items is apparent to the eye in the +source, not just in the processed document. ## Why is a spec needed? @@ -258,9 +336,14 @@ the Unicode classes `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. ## Tabs Tabs in lines are not expanded to [spaces]. However, -in contexts where indentation is significant for the -document's structure, tabs behave as if they were replaced -by spaces with a tab stop of 4 characters. +in contexts where whitespace helps to define block structure, +tabs behave as if they were replaced by spaces with a tab stop +of 4 characters. + +Thus, for example, a tab can be used instead of four spaces +in an indented code block. (Note, however, that internal +tabs are passed through as literal tabs, not expanded to +spaces.) ```````````````````````````````` example →foo→baz→→bim @@ -269,7 +352,6 @@ by spaces with a tab stop of 4 characters. ```````````````````````````````` - ```````````````````````````````` example →foo→baz→→bim . @@ -277,7 +359,6 @@ by spaces with a tab stop of 4 characters. ```````````````````````````````` - ```````````````````````````````` example a→a ὐ→a @@ -287,6 +368,9 @@ by spaces with a tab stop of 4 characters. ```````````````````````````````` +In the following example, a continuation paragraph of a list +item is indented with a tab; this has exactly the same effect +as indentation with four spaces would: ```````````````````````````````` example - foo @@ -315,6 +399,15 @@ by spaces with a tab stop of 4 characters. ```````````````````````````````` +Normally the `>` that begins a block quote may be followed +optionally by a space, which is not considered part of the +content. In the following case `>` is followed by a tab, +which is treated as if it were expanded into spaces. +Since one of theses spaces is considered part of the +delimiter, `foo` is considered to be indented six spaces +inside the block quote context, so we get an indented +code block starting with two spaces. + ```````````````````````````````` example >→→foo . @@ -363,6 +456,17 @@ bar ```````````````````````````````` +```````````````````````````````` example +#→Foo +. +

    Foo

    +```````````````````````````````` + +```````````````````````````````` example +*→*→*→ +. +
    +```````````````````````````````` ## Insecure characters @@ -701,15 +805,6 @@ headings: ```````````````````````````````` -A tab will not work: - -```````````````````````````````` example -#→foo -. -

    #→foo

    -```````````````````````````````` - - This is not a heading, because the first `#` is escaped: ```````````````````````````````` example @@ -1890,7 +1985,7 @@ by their start and end conditions. The block begins with a line that meets a [start condition](@) (after up to three spaces optional indentation). It ends with the first subsequent line that meets a matching [end condition](@), or the last line of -the document, if no line is encountered that meets the +the document or other [container block](@), if no line is encountered that meets the [end condition]. If the first line meets both the [start condition] and the [end condition], the block will contain just that line. @@ -2224,6 +2319,7 @@ import Text.HTML.TagSoup main :: IO () main = print $ parseTags tags +okay .
    
     import Text.HTML.TagSoup
    @@ -2231,6 +2327,7 @@ import Text.HTML.TagSoup
     main :: IO ()
     main = print $ parseTags tags
     
    +

    okay

    ```````````````````````````````` @@ -2242,12 +2339,14 @@ A script tag (type 1): document.getElementById("demo").innerHTML = "Hello JavaScript!"; +okay . +

    okay

    ```````````````````````````````` @@ -2260,6 +2359,7 @@ h1 {color:red;} p {color:blue;} +okay . +

    okay

    ```````````````````````````````` @@ -2355,11 +2456,13 @@ A comment (type 2): bar baz --> +okay . +

    okay

    ```````````````````````````````` @@ -2372,12 +2475,14 @@ A processing instruction (type 3): echo '>'; ?> +okay . '; ?> +

    okay

    ```````````````````````````````` @@ -2405,6 +2510,7 @@ function matchwo(a,b) } } ]]> +okay . +

    okay

    ```````````````````````````````` @@ -3162,8 +3269,8 @@ Four spaces gives us a code block: ```````````````````````````````` -The Laziness clause allows us to omit the `>` before a -paragraph continuation line: +The Laziness clause allows us to omit the `>` before +[paragraph continuation text]: ```````````````````````````````` example > # Foo @@ -3269,8 +3376,8 @@ foo ```````````````````````````````` -Note that in the following case, we have a paragraph -continuation line: +Note that in the following case, we have a [lazy +continuation line]: ```````````````````````````````` example > foo @@ -3292,7 +3399,7 @@ To see why, note that in the `- bar` is indented too far to start a list, and can't be an indented code block because indented code blocks cannot -interrupt paragraphs, so it is a [paragraph continuation line]. +interrupt paragraphs, so it is [paragraph continuation text]. A block quote can be empty: @@ -3521,7 +3628,7 @@ The following rules define [list items]: 1. **Basic case.** If a sequence of lines *Ls* constitute a sequence of blocks *Bs* starting with a [non-whitespace character] and not separated from each other by more than one blank line, and *M* is a list - marker of width *W* followed by 0 < *N* < 5 spaces, then the result + marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces, 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 list item with *Bs* as its contents. The type of the list item @@ -3529,6 +3636,12 @@ The following rules define [list items]: If the list item is ordered, then it is also assigned a start number, based on the ordered list marker. + Exceptions: When the list item interrupts a paragraph---that + is, when it starts on a line that would otherwise count as + [paragraph continuation text]---then (a) the lines *Ls* must + not begin with a blank line, and (b) if the list item is + ordered, the start number must be 1. + For example, let *Ls* be the lines ```````````````````````````````` example @@ -3703,66 +3816,20 @@ any following content, so these are not list items: ```````````````````````````````` -A list item may not contain blocks that are separated by more than -one blank line. Thus, two blank lines will end a list, unless the -two blanks are contained in a [fenced code block]. +A list item may contain blocks that are separated by more than +one blank line. ```````````````````````````````` example -- foo - - bar - - foo bar - -- ``` - foo - - - bar - ``` - -- baz - - + ``` - foo - - - bar - ``` .
    • foo

      bar

    • -
    • -

      foo

      -
    • -
    -

    bar

    -
      -
    • -
      foo
      -
      -
      -bar
      -
      -
    • -
    • -

      baz

      -
        -
      • -
        foo
        -
        -
        -bar
        -
        -
      • -
      -
    ```````````````````````````````` @@ -3795,15 +3862,14 @@ A list item may contain any kind of block: A list item that contains an indented code block will preserve -empty lines within the code block verbatim, unless there are two -or more empty lines in a row (since as described above, two -blank lines end the list): +empty lines within the code block verbatim. ```````````````````````````````` example - Foo bar + baz .
      @@ -3811,33 +3877,13 @@ blank lines end the list):

      Foo

      bar
       
      -baz
      -
      - -
    -```````````````````````````````` - -```````````````````````````````` example -- Foo - - bar - - - baz -. -
      -
    • -

      Foo

      -
      bar
      +baz
       
    -
      baz
    -
    ```````````````````````````````` - Note that ordered list start numbers must be nine digits or less: ```````````````````````````````` example @@ -4080,6 +4126,18 @@ Here are some list items that start with a blank line but are not empty: ```````````````````````````````` +When the list item starts with a blank line, the number of spaces +following the list marker doesn't change the required indentation: + +```````````````````````````````` example +- + foo +. +
      +
    • foo
    • +
    +```````````````````````````````` + A list item can begin with at most one blank line. In the following example, `foo` is not part of the list @@ -4152,6 +4210,20 @@ A list may start or end with an empty list item: ```````````````````````````````` +However, an empty list item cannot interrupt a paragraph: + +```````````````````````````````` example +foo +* + +foo +1. +. +

    foo +*

    +

    foo +1.

    +```````````````````````````````` 4. **Indentation.** If a sequence of lines *Ls* constitutes a list item @@ -4349,13 +4421,18 @@ So, in this case we need two spaces indent: - foo - bar - baz + - boo .
    • foo
      • bar
          -
        • baz
        • +
        • baz +
            +
          • boo
          • +
          +
      @@ -4370,11 +4447,13 @@ One is not enough: - foo - bar - baz + - boo .
      • foo
      • bar
      • baz
      • +
      • boo
      ```````````````````````````````` @@ -4727,28 +4806,20 @@ Foo
    ```````````````````````````````` - `Markdown.pl` does not allow this, through fear of triggering a list via a numeral in a hard-wrapped line: -```````````````````````````````` example +```````````````````````````````` markdown The number of windows in my house is 14. The number of doors is 6. -. -

    The number of windows in my house is

    -
      -
    1. The number of doors is 6.
    2. -
    ```````````````````````````````` +Oddly, though, `Markdown.pl` *does* allow a blockquote to +interrupt a paragraph, even though the same considerations might +apply. - -Oddly, `Markdown.pl` *does* allow a blockquote to interrupt a paragraph, -even though the same considerations might apply. We think that the two -cases should be treated the same. Here are two reasons for allowing -lists to interrupt paragraphs: - -First, it is natural and not uncommon for people to start lists without -blank lines: +In CommonMark, we do allow lists to interrupt paragraphs, for +two reasons. First, it is natural and not uncommon for people +to start lists without blank lines: I need to buy - new shoes @@ -4782,20 +4853,40 @@ then by itself should be a paragraph followed by a nested sublist. -Our adherence to the [principle of uniformity] -thus inclines us to think that there are two coherent packages: +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) +takes a different approach, requiring blank lines before lists +even inside other list items.) + +In order to solve of unwanted lists in paragraphs with +hard-wrapped numerals, we allow only lists starting with `1` to +interrupt paragraphs. Thus, + +```````````````````````````````` example +The number of windows in my house is +14. The number of doors is 6. +. +

    The number of windows in my house is +14. The number of doors is 6.

    +```````````````````````````````` -1. Require blank lines before *all* lists and blockquotes, - including lists that occur as sublists inside other list items. +We may still get an unintended result in cases like -2. Require blank lines in none of these places. +```````````````````````````````` example +The number of windows in my house is +1. The number of doors is 6. +. +

    The number of windows in my house is

    +
      +
    1. The number of doors is 6.
    2. +
    +```````````````````````````````` -[reStructuredText](http://docutils.sourceforge.net/rst.html) takes -the first approach, for which there is much to be said. But the second -seems more consistent with established practice with Markdown. +but this rule should prevent most spurious list captures. -There can be blank lines between items, but two blank lines end -a list: +There can be any number of blank lines between items: ```````````````````````````````` example - foo @@ -4812,36 +4903,12 @@ a list:
  • bar

  • - -
      -
    • baz
    • -
    -```````````````````````````````` - - -As illustrated above in the section on [list items], -two blank lines between blocks *within* a list item will also end a -list: - -```````````````````````````````` example -- foo - - - bar -- baz -. -
      -
    • foo
    • -
    -

    bar

    -
      -
    • baz
    • +
    • +

      baz

      +
    ```````````````````````````````` - -Indeed, two blank lines will end *all* containing lists: - ```````````````````````````````` example - foo - bar @@ -4855,26 +4922,28 @@ Indeed, two blank lines will end *all* containing lists:
    • bar
        -
      • baz
      • +
      • +

        baz

        +

        bim

        +
    -
      bim
    -
    ```````````````````````````````` -Thus, two blank lines can be used to separate consecutive lists of -the same type, or to separate a list from an indented code block -that would otherwise be parsed as a subparagraph of the final list -item: +To separate consecutive lists of the same type, or to separate a +list from an indented code block that would otherwise be parsed +as a subparagraph of the final list item, you can insert a blank HTML +comment: ```````````````````````````````` example - foo - bar + - baz - bim @@ -4883,6 +4952,7 @@ item:
  • foo
  • bar
  • +
    • baz
    • bim
    • @@ -4897,6 +4967,7 @@ item: - foo + code . @@ -4909,6 +4980,7 @@ item:

      foo

    +
    code
     
    ```````````````````````````````` @@ -5855,18 +5927,22 @@ The following rules define emphasis and strong emphasis: 9. Emphasis begins with a delimiter that [can open emphasis] and ends with a delimiter that [can close emphasis], and that uses the same - character (`_` or `*`) as the opening delimiter. There must - be a nonempty sequence of inlines between the open delimiter - and the closing delimiter; these form the contents of the emphasis - inline. + character (`_` or `*`) as the opening delimiter. The + opening and closing delimiters must belong to separate + [delimiter runs]. If one of the delimiters can both + open and close emphasis, then the sum of the lengths of the + delimiter runs containing the opening and closing delimiters + must not be a multiple of 3. 10. Strong emphasis begins with a delimiter that [can open strong emphasis] and ends with a delimiter that [can close strong emphasis], and that uses the same character - (`_` or `*`) as the opening delimiter. - There must be a nonempty sequence of inlines between the open - delimiter and the closing delimiter; these form the contents of - the strong emphasis inline. + (`_` or `*`) as the opening delimiter. The + opening and closing delimiters must belong to separate + [delimiter runs]. If one of the delimiters can both open + and close strong emphasis, then the sum of the lengths of + the delimiter runs containing the opening and closing + delimiters must not be a multiple of 3. 11. A literal `*` character cannot occur at the beginning or end of `*`-delimited emphasis or `**`-delimited strong emphasis, unless it @@ -5890,9 +5966,7 @@ the following principles resolve ambiguity: so that the second begins before the first ends and ends after the first ends, the first takes precedence. Thus, for example, `*foo _bar* baz_` is parsed as `foo _bar baz_` rather - than `*foo bar* baz`. For the same reason, - `**foo*bar**` is parsed as `foobar*` - rather than `foo*bar`. + than `*foo bar* baz`. 16. When there are two potential emphasis or strong emphasis spans with the same closing delimiter, the shorter one (the one that @@ -6065,10 +6139,8 @@ A newline also counts as whitespace: *foo bar * . -

    *foo bar

    -
      -
    • -
    +

    *foo bar +*

    ```````````````````````````````` @@ -6472,18 +6544,30 @@ __foo_ bar_

    foo bar baz

    ```````````````````````````````` - -But note: - ```````````````````````````````` example *foo**bar**baz* . -

    foobarbaz

    +

    foobarbaz

    ```````````````````````````````` +Note that in the preceding case, the interpretation + +``` markdown +

    foobarbaz

    +``` + + +is precluded by the condition that a delimiter that +can both open and close (like the `*` after `foo` +cannot form emphasis if the sum of the lengths of +the delimiter runs containing the opening and +closing delimiters is a multiple of 3. + +The same condition ensures that the following +cases are all strong emphasis nested inside +emphasis, even when the interior spaces are +omitted: -The difference is that in the preceding case, the internal delimiters -[can close emphasis], while in the cases with spaces, they cannot. ```````````````````````````````` example ***foo** bar* @@ -6499,17 +6583,18 @@ The difference is that in the preceding case, the internal delimiters ```````````````````````````````` -Note, however, that in the following case we get no strong -emphasis, because the opening delimiter is closed by the first -`*` before `bar`: - ```````````````````````````````` example *foo**bar*** . -

    foobar**

    +

    foobar

    ```````````````````````````````` +```````````````````````````````` example +*foo**bar*** +. +

    foobar

    +```````````````````````````````` Indefinite levels of nesting are possible: @@ -6603,18 +6688,13 @@ ____foo__ bar__ ```````````````````````````````` -But note: - ```````````````````````````````` example **foo*bar*baz** . -

    foobarbaz**

    +

    foobarbaz

    ```````````````````````````````` -The difference is that in the preceding case, the internal delimiters -[can close emphasis], while in the cases with spaces, they cannot. - ```````````````````````````````` example ***foo* bar** . @@ -6929,13 +7009,6 @@ Rule 15: ```````````````````````````````` -```````````````````````````````` example -**foo*bar** -. -

    foobar*

    -```````````````````````````````` - - ```````````````````````````````` example *foo __bar *baz bim__ bam* . @@ -7866,7 +7939,7 @@ 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, -which are used as the link's text. the link's URI and title +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][]`. @@ -8841,7 +8914,7 @@ foo A regular line break (not in a code span or HTML tag) that is not preceded by two or more spaces or a backslash is parsed as a -softbreak. (A softbreak may be rendered in HTML either as a +[softbreak](@). (A softbreak may be rendered in HTML either as a [line ending] or as a space. The result will be the same in browsers. In the examples here, a [line ending] will be used.) From 5f21970670802f4882dcb3abf696595070cc7f2f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Jul 2016 15:44:31 +0200 Subject: [PATCH 137/815] Adjust to spec changes for emphasis parsing (spec 0.26, #55) --- .../commonmark/internal/InlineParserImpl.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index e131a793b..13e53af9a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -793,24 +793,35 @@ private void processDelimiters(Delimiter stackBottom) { char openingDelimiterChar = delimiterProcessor.getOpeningDelimiterChar(); - // found delimiter closer. now look back for first matching opener: + // Found delimiter closer. Now look back for first matching opener. boolean openerFound = false; + boolean oddMatch = false; Delimiter opener = closer.previous; while (opener != null && opener != stackBottom && opener != openersBottom.get(delimiterChar)) { - if (opener.delimiterChar == openingDelimiterChar && opener.canOpen) { - openerFound = true; - break; + if (opener.delimiterChar == openingDelimiterChar) { + oddMatch = (closer.canOpen || opener.canClose) && + (opener.numDelims + closer.numDelims) % 3 == 0; + if (opener.canOpen && !oddMatch) { + openerFound = true; + break; + } } opener = opener.previous; } if (!openerFound) { - // Set lower bound for future searches for openers: - openersBottom.put(delimiterChar, closer.previous); - if (!closer.canOpen) { - // We can remove a closer that can't be an opener, - // once we've seen there's no matching opener: - removeDelimiterKeepNode(closer); + if (!oddMatch) { + // Set lower bound for future searches for openers. + // We don't do this with odd matches because a ** + // that doesn't match an earlier * might turn into + // an opener, and the * might be matched by something + // else. + openersBottom.put(delimiterChar, closer.previous); + if (!closer.canOpen) { + // We can remove a closer that can't be an opener, + // once we've seen there's no matching opener: + removeDelimiterKeepNode(closer); + } } closer = closer.next; continue; From 15f165bf88c148db37a2100433aaa9ab4979a344 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Jul 2016 15:47:05 +0200 Subject: [PATCH 138/815] Fix tabs in ATX headings and thematic breaks (spec 0.26, #55) --- .../src/main/java/org/commonmark/internal/HeadingParser.java | 2 +- .../main/java/org/commonmark/internal/ThematicBreakParser.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 7acd30137..3c26b1cd4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -10,7 +10,7 @@ public class HeadingParser extends AbstractBlockParser { - private static Pattern ATX_HEADING = Pattern.compile("^#{1,6}(?: +|$)"); + private static Pattern ATX_HEADING = Pattern.compile("^#{1,6}(?:[ \t]+|$)"); private static Pattern ATX_TRAILING = Pattern.compile("(^| ) *#+ *$"); private static Pattern SETEXT_HEADING = Pattern.compile("^(?:=+|-+) *$"); diff --git a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java index f02e2e90e..879d583c7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java @@ -8,7 +8,7 @@ public class ThematicBreakParser extends AbstractBlockParser { - private static Pattern PATTERN = Pattern.compile("^(?:(?:\\* *){3,}|(?:_ *){3,}|(?:- *){3,}) *$"); + private static Pattern PATTERN = Pattern.compile("^(?:(?:\\*[ \t]*){3,}|(?:_[ \t]*){3,}|(?:-[ \t]*){3,})[ \t]*$"); private final ThematicBreak block = new ThematicBreak(); From 7a4a974518338f2afaec6619df468e3fe3b491ad Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Jul 2016 15:57:10 +0200 Subject: [PATCH 139/815] Ordered lists can only interrupt paragraphs if they start with 1 (spec 0.26, #55) --- .../org/commonmark/internal/ListBlockParser.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index 36b91feaf..4800a30d7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -48,7 +48,8 @@ public void setTight(boolean tight) { /** * Parse a list marker and return data on the marker or null. */ - private static ListData parseListMarker(CharSequence line, final int markerIndex, final int markerColumn) { + private static ListData parseListMarker(CharSequence line, final int markerIndex, final int markerColumn, + final boolean inParagraph) { CharSequence rest = line.subSequence(markerIndex, line.length()); Matcher matcher = MARKER.matcher(rest); if (!matcher.find()) { @@ -56,6 +57,12 @@ private static ListData parseListMarker(CharSequence line, final int markerIndex } ListBlock listBlock = createListBlock(matcher); + if (inParagraph && listBlock instanceof OrderedList) { + // Ordered lists can only interrupt paragraphs with a start number of 1. + if (((OrderedList) listBlock).getStartNumber() != 1) { + return null; + } + } int markerLength = matcher.end() - matcher.start(); int indexAfterMarker = markerIndex + markerLength; @@ -129,8 +136,10 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT && !(matched instanceof ListBlockParser)) { return BlockStart.none(); } - int nextNonSpace = state.getNextNonSpaceIndex(); - ListData listData = parseListMarker(state.getLine(), nextNonSpace, state.getColumn() + state.getIndent()); + int markerIndex = state.getNextNonSpaceIndex(); + int markerColumn = state.getColumn() + state.getIndent(); + ListData listData = parseListMarker(state.getLine(), markerIndex, markerColumn, + matchedBlockParser.getParagraphContent() != null); if (listData == null) { return BlockStart.none(); } From af25da2524718c41ba0fca33894cd3a61ac1146e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Jul 2016 16:03:41 +0200 Subject: [PATCH 140/815] Remove "two blank lines breaks out of lists" feature (spec 0.26, #55) --- .../commonmark/internal/DocumentParser.java | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index af46f7664..942da3ca6 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -182,12 +182,6 @@ private void incorporateLine(CharSequence ln) { BlockParser blockParser = lastMatchedBlockParser; boolean allClosed = unmatchedBlockParsers.isEmpty(); - // Check to see if we've hit 2nd blank line; if so break out of list: - if (isBlank() && isLastLineBlank(blockParser.getBlock())) { - List matchedBlockParsers = new ArrayList<>(activeBlockParsers.subList(0, matches)); - breakOutOfLists(matchedBlockParsers); - } - // Unless last matched container is a code block, try new container starts, // adding children to the last matched container: boolean tryBlockStarts = blockParser.getBlock() instanceof Paragraph || blockParser.isContainer(); @@ -423,24 +417,6 @@ private boolean endsWithBlankLine(Node block) { return false; } - /** - * Break out of all containing lists, resetting the tip of the document to the parent of the highest list, - * and finalizing all the lists. (This is used to implement the "two blank lines break of of all lists" feature.) - */ - private void breakOutOfLists(List blockParsers) { - int lastList = -1; - for (int i = blockParsers.size() - 1; i >= 0; i--) { - BlockParser blockParser = blockParsers.get(i); - if (blockParser instanceof ListBlockParser) { - lastList = i; - } - } - - if (lastList != -1) { - finalizeBlocks(blockParsers.subList(lastList, blockParsers.size())); - } - } - /** * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try * its parent, and so on til we find a block that can accept children. @@ -480,10 +456,9 @@ private void propagateLastLineBlank(BlockParser blockParser, BlockParser lastMat Block block = blockParser.getBlock(); - // Block quote lines are never blank as they start with > - // and we don't count blanks in fenced code for purposes of tight/loose - // lists or breaking out of lists. We also don't set lastLineBlank - // on an empty list item. + // Block quote lines are never blank as they start with `>`. + // We don't count blanks in fenced code for purposes of tight/loose lists. + // We also don't set lastLineBlank on an empty list item. boolean lastLineBlank = isBlank() && !(block instanceof BlockQuote || block instanceof FencedCodeBlock || From 5afb621a4799927b12def1e4512ecf3c139c750d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Jul 2016 16:14:00 +0200 Subject: [PATCH 141/815] Empty list items can no longer interrupt a paragraph (spec 0.26, #55) --- .../commonmark/internal/ListBlockParser.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index 4800a30d7..a129b2abe 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -57,12 +57,6 @@ private static ListData parseListMarker(CharSequence line, final int markerIndex } ListBlock listBlock = createListBlock(matcher); - if (inParagraph && listBlock instanceof OrderedList) { - // Ordered lists can only interrupt paragraphs with a start number of 1. - if (((OrderedList) listBlock).getStartNumber() != 1) { - return null; - } - } int markerLength = matcher.end() - matcher.start(); int indexAfterMarker = markerIndex + markerLength; @@ -85,6 +79,17 @@ private static ListData parseListMarker(CharSequence line, final int markerIndex } } + 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) { + return null; + } + // Empty list item can not interrupt a paragraph. + if (!hasContent) { + return null; + } + } + if (!hasContent || (contentColumn - columnAfterMarker) > Parsing.CODE_BLOCK_INDENT) { // If this line is blank or has a code block, default to 1 space after marker contentColumn = columnAfterMarker + 1; @@ -138,8 +143,8 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } int markerIndex = state.getNextNonSpaceIndex(); int markerColumn = state.getColumn() + state.getIndent(); - ListData listData = parseListMarker(state.getLine(), markerIndex, markerColumn, - matchedBlockParser.getParagraphContent() != null); + boolean inParagraph = matchedBlockParser.getParagraphContent() != null; + ListData listData = parseListMarker(state.getLine(), markerIndex, markerColumn, inParagraph); if (listData == null) { return BlockStart.none(); } From 863f1b465fceac0821e93c3182865c95c160a378 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 22 Jul 2016 11:53:31 +0200 Subject: [PATCH 142/815] Move new emphasis rule into DelimiterProcessor (extension api break) The "multiple of 3" rule was breaking the strikethrough extension logic. By passing more information into getDelimiterUse and allowing the implementation to return 0, we can move the "multiple of 3" rule into the delimiter processor. For the strikethrough delimiter processor, we can have different rules and simplify the implementation. It's now also closer to GFM. Also clean up the other method names a bit while we are breaking the API: * getOpeningDelimiterChar -> getOpeningCharacter * getClosingDelimiterChar -> getClosingCharacter * getMinDelimiterCount -> getMinLength --- .../StrikethroughDelimiterProcessor.java | 28 +++----- .../gfm/strikethrough/StrikethroughTest.java | 8 ++- .../org/commonmark/internal/Delimiter.java | 18 +++++- .../org/commonmark/internal/DelimiterRun.java | 15 ----- .../commonmark/internal/InlineParserImpl.java | 64 +++++++++++-------- .../inline/EmphasisDelimiterProcessor.java | 25 +++++--- .../java/org/commonmark/parser/Parser.java | 1 + .../{ => delimiter}/DelimiterProcessor.java | 22 ++++--- .../parser/delimiter/DelimiterRun.java | 22 +++++++ .../test/DelimiterProcessorTest.java | 58 +++++++++++++++-- 10 files changed, 174 insertions(+), 87 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/DelimiterRun.java rename commonmark/src/main/java/org/commonmark/parser/{ => delimiter}/DelimiterProcessor.java (65%) create mode 100644 commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java 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 118f21cc4..dd881b419 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 @@ -3,47 +3,39 @@ import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.node.Node; import org.commonmark.node.Text; -import org.commonmark.parser.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; public class StrikethroughDelimiterProcessor implements DelimiterProcessor { @Override - public char getOpeningDelimiterChar() { + public char getOpeningCharacter() { return '~'; } @Override - public char getClosingDelimiterChar() { + public char getClosingCharacter() { return '~'; } @Override - public int getMinDelimiterCount() { + public int getMinLength() { return 2; } @Override - public int getDelimiterUse(int openerCount, int closerCount) { - if (openerCount >= 2 && closerCount >= 2) { + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + if (opener.length() >= 2 && closer.length() >= 2) { + // Use exactly two delimiters even if we have more, and don't care about internal openers/closers. return 2; } else { - // Can happen if a run had 3 delimiters before, and we removed 2 of them in an earlier processing step. - // So just use 1 of them, see corresponding handling in process method. - return 1; + return 0; } } @Override public void process(Text opener, Text closer, int delimiterCount) { - // Can happen if a run had 3 or more delimiters, so 1 is left over. Don't turn that into strikethrough, but - // preserve original character. - if (delimiterCount == 1) { - opener.insertAfter(new Text("~")); - closer.insertBefore(new Text("~")); - return; - } - - // Normal case, wrap nodes between delimiters in strikethrough. + // Wrap nodes between delimiters in strikethrough. Node strikethrough = new Strikethrough(); Node tmp = opener.getNext(); 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 12bcf2a49..70b7e4a33 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 @@ -50,8 +50,14 @@ public void twoInnerThree() { } @Test - public void twoStrikethroughsWithoutSpacing() { + public void tildesInside() { + assertRendering("~~foo~bar~~", "

    foo~bar

    \n"); + assertRendering("~~foo~~bar~~", "

    foobar~~

    \n"); + assertRendering("~~foo~~~bar~~", "

    foo~bar~~

    \n"); assertRendering("~~foo~~~~bar~~", "

    foobar

    \n"); + assertRendering("~~foo~~~~~bar~~", "

    foo~bar

    \n"); + assertRendering("~~foo~~~~~~bar~~", "

    foo~~bar

    \n"); + assertRendering("~~foo~~~~~~~bar~~", "

    foo~~~bar

    \n"); } @Test diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index 9c42f6dea..2c4094ef9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -1,11 +1,12 @@ package org.commonmark.internal; import org.commonmark.node.Text; +import org.commonmark.parser.delimiter.DelimiterRun; /** * Delimiter (emphasis, strong emphasis or custom emphasis). */ -class Delimiter { +class Delimiter implements DelimiterRun { final Text node; final char delimiterChar; @@ -32,4 +33,19 @@ class Delimiter { this.canClose = canClose; this.previous = previous; } + + @Override + public boolean canOpen() { + return canOpen; + } + + @Override + public boolean canClose() { + return canClose; + } + + @Override + public int length() { + return numDelims; + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/DelimiterRun.java b/commonmark/src/main/java/org/commonmark/internal/DelimiterRun.java deleted file mode 100644 index a8a363fa8..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/DelimiterRun.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.commonmark.internal; - -class DelimiterRun { - - final int count; - final boolean canClose; - final boolean canOpen; - - DelimiterRun(int count, boolean canOpen, boolean canClose) { - this.count = count; - this.canOpen = canOpen; - this.canClose = canClose; - } - -} diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 13e53af9a..cba11cb5a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -6,7 +6,7 @@ import org.commonmark.internal.util.Html5Entities; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; -import org.commonmark.parser.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.InlineParser; import java.util.*; @@ -134,9 +134,9 @@ public static Map calculateDelimiterProcessors(Li private static void addDelimiterProcessors(Iterable delimiterProcessors, Map map) { for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { - char opening = delimiterProcessor.getOpeningDelimiterChar(); + char opening = delimiterProcessor.getOpeningCharacter(); addDelimiterProcessorForChar(opening, delimiterProcessor, map); - char closing = delimiterProcessor.getClosingDelimiterChar(); + char closing = delimiterProcessor.getClosingCharacter(); if (opening != closing) { addDelimiterProcessorForChar(closing, delimiterProcessor, map); } @@ -430,7 +430,7 @@ private boolean parseBackticks() { * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { - DelimiterRun res = scanDelimiters(delimiterProcessor, delimiterChar); + DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar); if (res == null) { return false; } @@ -727,7 +727,7 @@ private boolean parseString() { * * @return information about delimiter run, or {@code null} */ - private DelimiterRun scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { int startIndex = index; int delimiterCount = 0; @@ -736,7 +736,7 @@ private DelimiterRun scanDelimiters(DelimiterProcessor delimiterProcessor, char index++; } - if (delimiterCount < delimiterProcessor.getMinDelimiterCount()) { + if (delimiterCount < delimiterProcessor.getMinLength()) { index = startIndex; return null; } @@ -764,12 +764,12 @@ private DelimiterRun scanDelimiters(DelimiterProcessor delimiterProcessor, char canOpen = leftFlanking && (!rightFlanking || beforeIsPunctuation); canClose = rightFlanking && (!leftFlanking || afterIsPunctuation); } else { - canOpen = leftFlanking && delimiterChar == delimiterProcessor.getOpeningDelimiterChar(); - canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingDelimiterChar(); + canOpen = leftFlanking && delimiterChar == delimiterProcessor.getOpeningCharacter(); + canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingCharacter(); } index = startIndex; - return new DelimiterRun(delimiterCount, canOpen, canClose); + return new DelimiterData(delimiterCount, canOpen, canClose); } private void processDelimiters(Delimiter stackBottom) { @@ -791,17 +791,18 @@ private void processDelimiters(Delimiter stackBottom) { continue; } - char openingDelimiterChar = delimiterProcessor.getOpeningDelimiterChar(); + char openingDelimiterChar = delimiterProcessor.getOpeningCharacter(); // Found delimiter closer. Now look back for first matching opener. + int useDelims = 0; boolean openerFound = false; - boolean oddMatch = false; + boolean potentialOpenerFound = false; Delimiter opener = closer.previous; while (opener != null && opener != stackBottom && opener != openersBottom.get(delimiterChar)) { - if (opener.delimiterChar == openingDelimiterChar) { - oddMatch = (closer.canOpen || opener.canClose) && - (opener.numDelims + closer.numDelims) % 3 == 0; - if (opener.canOpen && !oddMatch) { + if (opener.canOpen && opener.delimiterChar == openingDelimiterChar) { + potentialOpenerFound = true; + useDelims = delimiterProcessor.getDelimiterUse(opener, closer); + if (useDelims > 0) { openerFound = true; break; } @@ -810,12 +811,14 @@ private void processDelimiters(Delimiter stackBottom) { } if (!openerFound) { - if (!oddMatch) { + if (!potentialOpenerFound) { // Set lower bound for future searches for openers. - // We don't do this with odd matches because a ** - // that doesn't match an earlier * might turn into - // an opener, and the * might be matched by something - // else. + // Only do this when we didn't even have a potential + // opener (one that matches the character and can open). + // If an opener was rejected because of the number of + // delimiters (e.g. because of the "multiple of 3" rule), + // we want to consider it next time because the number + // of delimiters can change as we continue processing. openersBottom.put(delimiterChar, closer.previous); if (!closer.canOpen) { // We can remove a closer that can't be an opener, @@ -827,16 +830,10 @@ private void processDelimiters(Delimiter stackBottom) { continue; } - int useDelims = delimiterProcessor.getDelimiterUse(opener.numDelims, closer.numDelims); - if (useDelims <= 0) { - // nope - useDelims = 1; - } - Text openerNode = opener.node; Text closerNode = closer.node; - // remove used delimiters from stack elts and inlines + // Remove number of used delimiters from stack and inline nodes. opener.numDelims -= useDelims; closer.numDelims -= useDelims; openerNode.setLiteral( @@ -952,4 +949,17 @@ private void mergeIfNeeded(Text first, Text last, int textLength) { first.setLiteral(literal); } } + + private static class DelimiterData { + + final int count; + final boolean canClose; + final boolean canOpen; + + DelimiterData(int count, boolean canOpen, boolean canClose) { + this.count = count; + this.canOpen = canOpen; + this.canClose = canClose; + } + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 02260ea67..d116f6126 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -4,7 +4,8 @@ import org.commonmark.node.Node; import org.commonmark.node.StrongEmphasis; import org.commonmark.node.Text; -import org.commonmark.parser.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; public abstract class EmphasisDelimiterProcessor implements DelimiterProcessor { @@ -15,34 +16,38 @@ protected EmphasisDelimiterProcessor(char delimiterChar) { } @Override - public char getOpeningDelimiterChar() { + public char getOpeningCharacter() { return delimiterChar; } @Override - public char getClosingDelimiterChar() { + public char getClosingCharacter() { return delimiterChar; } @Override - public int getMinDelimiterCount() { + public int getMinLength() { return 1; } @Override - public int getDelimiterUse(int openerCount, int closerCount) { + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + // "multiple of 3" rule for internal delimiter runs + if ((opener.canClose() || closer.canOpen()) && (opener.length() + closer.length()) % 3 == 0) { + return 0; + } // calculate actual number of delimiters used from this closer - if (closerCount < 3 || openerCount < 3) { - return closerCount <= openerCount ? - closerCount : openerCount; + if (opener.length() < 3 || closer.length() < 3) { + return closer.length() <= opener.length() ? + closer.length() : opener.length(); } else { - return closerCount % 2 == 0 ? 2 : 1; + return closer.length() % 2 == 0 ? 2 : 1; } } @Override public void process(Text opener, Text closer, int delimiterUse) { - String singleDelimiter = String.valueOf(getOpeningDelimiterChar()); + String singleDelimiter = String.valueOf(getOpeningCharacter()); Node emphasis = delimiterUse == 1 ? new Emphasis(singleDelimiter) : new StrongEmphasis(singleDelimiter + singleDelimiter); diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 886990860..57ac0ad0c 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -7,6 +7,7 @@ import org.commonmark.internal.InlineParserImpl; import org.commonmark.node.Node; import org.commonmark.parser.block.BlockParserFactory; +import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.ArrayList; import java.util.BitSet; diff --git a/commonmark/src/main/java/org/commonmark/parser/DelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java similarity index 65% rename from commonmark/src/main/java/org/commonmark/parser/DelimiterProcessor.java rename to commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java index 13ec57cb0..0e8bc6fac 100644 --- a/commonmark/src/main/java/org/commonmark/parser/DelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java @@ -1,4 +1,4 @@ -package org.commonmark.parser; +package org.commonmark.parser.delimiter; import org.commonmark.node.Text; @@ -13,28 +13,30 @@ public interface DelimiterProcessor { * @return the character that marks the beginning of a delimited node, must not clash with any built-in special * characters */ - char getOpeningDelimiterChar(); + char getOpeningCharacter(); /** * @return the character that marks the the ending of a delimited node, must not clash with any built-in special * characters. Note that for a symmetric delimiter such as "*", this is the same as the opening. */ - char getClosingDelimiterChar(); + char getClosingCharacter(); /** * Minimum number of delimiter characters that are needed to activate this. Must be at least 1. */ - int getMinDelimiterCount(); + int getMinLength(); /** - * Determine how many of the delimiters should be used. Useful in case the same character with a different count - * should have a different meaning (e.g. with "*" for emphasis and "**" for strong emphasis). + * Determine how many (if any) of the delimiter characters should be used. + *

    + * This allows implementations to decide how many characters to use based on the properties of the delimiter runs. + * An implementation can also return 0 when it doesn't want to allow this particular combination of delimiter runs. * - * @param openerCount the delimiter count of the opening delimiter, at least 1 - * @param closerCount the delimiter count of the closing delimiter, at least 1 - * @return how many delimiters should be used; cannot be 0; must not be greater than either openerCount or closerCount + * @param opener the opening delimiter run + * @param closer the closing delimiter run + * @return how many delimiters should be used; must not be greater than length of either opener or closer */ - int getDelimiterUse(int openerCount, int closerCount); + int getDelimiterUse(DelimiterRun opener, DelimiterRun closer); /** * Process the matched delimiters, e.g. by wrapping the nodes between opener and closer in a new node, or appending diff --git a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java new file mode 100644 index 000000000..949b2cb29 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java @@ -0,0 +1,22 @@ +package org.commonmark.parser.delimiter; + +/** + * A delimiter run is one or more of the same delimiter character. + */ +public interface DelimiterRun { + + /** + * @return whether this can open a delimiter + */ + boolean canOpen(); + + /** + * @return whether this can close a delimiter + */ + boolean canClose(); + + /** + * @return the number of characters in this delimiter run (that are left for processing) + */ + int length(); +} diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 590d45441..bd58427c8 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -7,19 +7,32 @@ import org.commonmark.node.CustomNode; import org.commonmark.node.Node; import org.commonmark.node.Text; -import org.commonmark.parser.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.Parser; +import org.commonmark.parser.delimiter.DelimiterRun; import org.junit.Test; import java.util.Collections; import java.util.Locale; import java.util.Set; +import static org.junit.Assert.assertEquals; + public class DelimiterProcessorTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().customDelimiterProcessor(new AsymmetricDelimiterProcessor()).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().nodeRendererFactory(new UpperCaseNodeRendererFactory()).build(); + @Test + public void delimiterProcessorWithInvalidDelimiterUse() { + Parser parser = Parser.builder() + .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;"))); + } + @Test public void asymmetricDelimiter() { assertRendering("{foo} bar", "

    FOO bar

    \n"); @@ -40,25 +53,60 @@ protected String render(String source) { return RENDERER.render(node); } + private static class CustomDelimiterProcessor implements DelimiterProcessor { + + private final char delimiterChar; + private final int delimiterUse; + + private CustomDelimiterProcessor(char delimiterChar, int delimiterUse) { + this.delimiterChar = delimiterChar; + this.delimiterUse = delimiterUse; + } + + @Override + public char getOpeningCharacter() { + return delimiterChar; + } + + @Override + public char getClosingCharacter() { + return delimiterChar; + } + + @Override + public int getMinLength() { + return 1; + } + + @Override + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + return delimiterUse; + } + + @Override + public void process(Text opener, Text closer, int delimiterUse) { + } + } + private static class AsymmetricDelimiterProcessor implements DelimiterProcessor { @Override - public char getOpeningDelimiterChar() { + public char getOpeningCharacter() { return '{'; } @Override - public char getClosingDelimiterChar() { + public char getClosingCharacter() { return '}'; } @Override - public int getMinDelimiterCount() { + public int getMinLength() { return 1; } @Override - public int getDelimiterUse(int openerCount, int closerCount) { + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { return 1; } From be745629358fe6c3da3768fbd5cdee5a2c7d1e4a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 25 Jul 2016 13:55:24 +0200 Subject: [PATCH 143/815] Bump JMH --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index af19260f5..95654541b 100644 --- a/pom.xml +++ b/pom.xml @@ -121,12 +121,12 @@ org.openjdk.jmh jmh-core - 1.12 + 1.13 org.openjdk.jmh jmh-generator-annprocess - 1.12 + 1.13 From 5e2aaabf18a4c20c240018691254cc265c4a6215 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 25 Jul 2016 13:57:00 +0200 Subject: [PATCH 144/815] [maven-release-plugin] prepare release commonmark-parent-0.6.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 5 ++--- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 8 files changed, 16 insertions(+), 17 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 5c1e6bb17..1483d7d36 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.2-SNAPSHOT + 0.6.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 6c39d4f0f..c9b3d8ad1 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.2-SNAPSHOT + 0.6.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 313edfea8..c48c3d78c 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.2-SNAPSHOT + 0.6.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index f3d40307d..1752a70c4 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.5.2-SNAPSHOT + 0.6.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 5aa653601..177cdfe75 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -1,11 +1,10 @@ - + 4.0.0 com.atlassian.commonmark commonmark-parent - 0.5.2-SNAPSHOT + 0.6.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 0eadb6307..054c1af6b 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.2-SNAPSHOT + 0.6.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 234edecc3..c20c57b6a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.2-SNAPSHOT + 0.6.0 commonmark diff --git a/pom.xml b/pom.xml index 95654541b..26cd36f89 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.5.2-SNAPSHOT + 0.6.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,32 +84,32 @@ com.atlassian.commonmark commonmark - 0.5.2-SNAPSHOT + 0.6.0 com.atlassian.commonmark commonmark-ext-autolink - 0.5.2-SNAPSHOT + 0.6.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.5.2-SNAPSHOT + 0.6.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.5.2-SNAPSHOT + 0.6.0 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.5.2-SNAPSHOT + 0.6.0 com.atlassian.commonmark commonmark-test-util - 0.5.2-SNAPSHOT + 0.6.0 @@ -187,7 +187,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.6.0 From 524c838e76241fbeb438b7ec2693b33f0347e68a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 25 Jul 2016 13:57:00 +0200 Subject: [PATCH 145/815] [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-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 16 ++++++++-------- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 1483d7d36..313f6241f 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.0 + 0.6.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index c9b3d8ad1..9fea47115 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.0 + 0.6.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index c48c3d78c..be09a473c 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.0 + 0.6.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 1752a70c4..bc26788c4 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.6.0 + 0.6.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 177cdfe75..fe9b807b8 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.0 + 0.6.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 054c1af6b..d04482dd1 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.0 + 0.6.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index c20c57b6a..277349537 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.0 + 0.6.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 26cd36f89..42790d47a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.0 + 0.6.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -84,32 +84,32 @@ com.atlassian.commonmark commonmark - 0.6.0 + 0.6.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.6.0 + 0.6.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.6.0 + 0.6.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.6.0 + 0.6.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.6.0 + 0.6.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.6.0 + 0.6.1-SNAPSHOT @@ -187,7 +187,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.6.0 + HEAD From 11dda459a6cae8bcef2f43dc01e5d1ec083557d5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 25 Jul 2016 14:30:45 +0200 Subject: [PATCH 146/815] Bump versions --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5c8b96748..d59a25562 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.5.1 + 0.6.0 ``` @@ -111,7 +111,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.5.1 + 0.6.0 ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index 426cab9ec..aac87c1c5 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -7,7 +7,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.5.1" >> test.properties +echo "version.maven=0.6.0" >> test.properties echo "version.maven_autolink=0.5.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index da415b332..7af4200bc 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.5.1 +version.maven=0.6.0 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.5.0 # Version number of commonmark and extensions in project. -version.snapshot=0.5.2-SNAPSHOT +version.snapshot=0.6.1-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.5.0 ``` From 3778bf8e3c9079e74760ee9136e561320b4d00fa Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Fri, 2 Sep 2016 11:48:39 +1000 Subject: [PATCH 147/815] Update README with content for new extension --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 8a8e793a0..216644e2c 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,18 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. +### Header IDs + +Enables adding auto generated id attributes to header based on their content. + +`# Heading` will be rendered as + +``` +

    Heading

    +``` + +Use class `HeaderIdExtension` in artifact `commonmark-ext-header-ids` + Contributing ------------ From 89c37a10aa3a0a7b5f182a60c9ddd7507de83555 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Fri, 2 Sep 2016 11:49:14 +1000 Subject: [PATCH 148/815] Add UniqueID Provider as public class Previously this functionality existed internally in the HeaderIdAttributeProvider. This has been stripped out into its own functional class that can be used by other packages. The language attempts to divorce it from its original heading implementation to a more general identity provider --- .../headerids/UniqueIdentifierProvider.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/UniqueIdentifierProvider.java diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/UniqueIdentifierProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/UniqueIdentifierProvider.java new file mode 100644 index 000000000..f108d9f85 --- /dev/null +++ b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/UniqueIdentifierProvider.java @@ -0,0 +1,92 @@ +package org.commonmark.ext.headerids; + +import java.util.HashMap; +import java.util.Map; + +/** + * Provider of unique strings to be used as an identifier + */ +public class UniqueIdentifierProvider +{ + private String defaultIdentifier; + private final Map identityMap; + + /** + * Create a provider with the default identifier of "id" + */ + public UniqueIdentifierProvider() { + this("id"); + } + + /** + * @param defaultIdentifier default identifier to use if given a null, or empty {@code providedIdentity} + * in {@link #getUniqueIdentifier(String)} + */ + public UniqueIdentifierProvider(String defaultIdentifier) + { + this.defaultIdentifier = defaultIdentifier; + this.identityMap = new HashMap<>(); + } + + public String getDefaultIdentifier() + { + return defaultIdentifier; + } + + public void setDefaultIdentifier(String defaultIdentifier) + { + this.defaultIdentifier = defaultIdentifier; + } + + /** + *

    + * Provides unique strings over the lifetime of the instance + *

    + *

    + * This method is not thread safe, concurrent calls can end up + * with non-unique identifiers + *

    + *

    + * Note that collision can occur in the case that + *

      + *
    • String 'X' provided to getUniqueIdentifier
    • + *
    • String 'X' provided again to getUniqueIdentifier
    • + *
    • String 'X-1' provided to getUniqueIdentifier
    • + *
    + *

    + *

    + * In that case, the three ids provided will be + *

      + *
    • X
    • + *
    • X-1
    • + *
    • X-1
    • + *
    + *

    + *

    + * Therefore if collisions are unnacceptable you should ensure that + * numbers are stripped from end of {@code providedIdentity} + *

    + * + * @param providedIdentity tentative identifier to be used. Will be used to check if this is unique. Will be the + * basis for the returned identifier + * + * @return {@code providedIdentity} if this is the first instance that the {@code providedIdentity} has been passed + * to the method. Otherwise, {@code providedIdentity + "-" + X} will be returned, where X is the number of times + * that {@code providedIdentity} has previously been passed in. If {@code providedIdentity} is empty, the default + * identifier given in the constructor (or "id" if none given) will be used. + */ + public String getUniqueIdentifier(String providedIdentity) { + if (providedIdentity == null || providedIdentity.trim().length() == 0) { + providedIdentity = defaultIdentifier; + } + + if (!identityMap.containsKey(providedIdentity)) { + identityMap.put(providedIdentity, 1); + return providedIdentity; + } else { + int currentCount = identityMap.get(providedIdentity); + identityMap.put(providedIdentity, currentCount + 1); + return providedIdentity + "-" +currentCount; + } + } +} From 71a7e35832db272688b619f0fa95b38015173fcf Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Fri, 2 Sep 2016 11:50:34 +1000 Subject: [PATCH 149/815] Add new tests to check for unicode support An original assumption was that the id should be ascii. This assumption has been removed and support should be added for international charcaters and diacritics. Rather than attempt to support all combining characters, tests are written to ensure that there is support for the diacritical marks. Namely those in the Combining Diactirical Marks unicode, specifically (0x0300-0x036F) --- .../commonmark/ext/header/HeaderIdTest.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java index 31ea1b976..14696dbe5 100644 --- a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java +++ b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java @@ -1,6 +1,6 @@ package org.commonmark.ext.headerids; -import org.commonmark.html.AttributeProvider; +import org.commonmark.ext.headerids.internal.HeaderIdAttributeProvider; import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; @@ -35,14 +35,15 @@ public void singleHeaderWithCodeBlock() { @Test public void duplicateHeadersMakeUniqueIds() { assertRendering("# Heading here\n# Heading here", - "

    Heading here

    \n

    Heading here

    \n"); + "

    Heading here

    \n

    Heading here

    \n"); } @Test - public void duplicateHeadersOnceDissalowedCharactersMakeUniqueIds() { - assertRendering("# Hi there\n" + - "# ∂Hi å∂There˚∂ˆ´¨", "

    Hi there

    \n" + - "

    ∂Hi å∂There˚∂ˆ´¨

    \n"); + public void testExplicitHeaderCollision() { + assertRendering("# Header\n# Header\n# Header-1", + "

    Header

    \n" + + "

    Header

    \n" + + "

    Header-1

    \n"); } @Test @@ -50,7 +51,7 @@ public void testCaseIsIgnoredWhenComparingIds() { assertRendering("# HEADING here\n" + "# heading here", "

    HEADING here

    \n" + - "

    heading here

    \n"); + "

    heading here

    \n"); } @Test @@ -59,12 +60,6 @@ public void testNestedBlocks() { "

    h e l l o

    \n"); } - @Test - public void noPrintableCharacters() { - assertRendering("# ∂∂ƒƒ", - "

    ∂∂ƒƒ

    \n"); - } - @Test public void boldEmphasisCharacters() { assertRendering("# _hello_ **there**", "

    hello there

    \n"); @@ -75,6 +70,16 @@ public void testStrongEmphasis() { assertRendering("# _**Hi there**_", "

    Hi there

    \n"); } + @Test + public void testNonAsciiCharacterHeading() { + assertRendering("# bär", "

    bär

    \n"); + } + + @Test + public void testCombiningDiaeresis() { + assertRendering("# Product\u036D\u036B", "

    Product\u036D\u036B

    \n"); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); From 71ead55f8382d6c857b1fb56bd46ec2c511d5d03 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Fri, 2 Sep 2016 11:53:05 +1000 Subject: [PATCH 150/815] Move id provider internally, add unicode support --- .../headerids/HeaderIdAttributeProvider.java | 104 ------------------ .../internal/HeaderIdAttributeProvider.java | 76 +++++++++++++ 2 files changed, 76 insertions(+), 104 deletions(-) delete mode 100644 commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java create mode 100644 commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/internal/HeaderIdAttributeProvider.java diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java deleted file mode 100644 index 353312a33..000000000 --- a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdAttributeProvider.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.commonmark.ext.headerids; - -import org.commonmark.html.AttributeProvider; -import org.commonmark.node.*; - -import java.util.*; - -/** - * Extension for automatically adding ids to every h tag - *

    - * Create via {@link #create()} and add it to your {@link org.commonmark.html.HtmlRenderer} - * ({@link org.commonmark.html.HtmlRenderer.Builder#attributeProvider(AttributeProvider)} - */ -public class HeaderIdAttributeProvider implements AttributeProvider { - - private final Map headingMap; - private String defaultHeading; - - private HeaderIdAttributeProvider() { - this("heading"); - } - - private HeaderIdAttributeProvider(String defaultHeading) { - headingMap = new HashMap<>(); - this.defaultHeading = defaultHeading; - } - - public static HeaderIdAttributeProvider create() { - return new HeaderIdAttributeProvider(); - } - - public String getDefaultHeading() { - return defaultHeading; - } - - public void setDefaultHeading(String defaultHeading) { - this.defaultHeading = defaultHeading; - } - - @Override - public void setAttributes(Node node, final Map attributes) { - - if (node instanceof Heading) { - - final IdAttribute idAttribute = new IdAttribute(); - - node.accept(new AbstractVisitor() { - @Override - public void visit(Text text) { - idAttribute.add(text.getLiteral()); - } - - @Override - public void visit(Code code) { - idAttribute.add(code.getLiteral()); - } - - - - - }); - - attributes.put("id", idAttribute.getUniqueHeader(headingMap)); - } - } - - private class IdAttribute { - StringBuilder sb = new StringBuilder(); - - public void add(String s) { - - // Do some basic substitution - s = s.toLowerCase(); - s = s.replaceAll(" +", "-"); - - for (char c : s.toCharArray()) { - if (0x0041 < c && c < 0x007A || c == '-' || c == '_') { - sb.append(c); - } - } - } - - public String getUniqueHeader(Map headingMap) { - String currentValue = toString(); - if (currentValue.length() == 0) { - currentValue = defaultHeading; - } - - if (!headingMap.containsKey(currentValue)) { - headingMap.put(currentValue, 1); - return currentValue; - } else { - int currentCount = headingMap.get(currentValue); - headingMap.put(currentValue, currentCount + 1); - return currentValue + currentCount; - } - } - - @Override - public String toString() { - return sb.toString(); - } - } -} diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/internal/HeaderIdAttributeProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/internal/HeaderIdAttributeProvider.java new file mode 100644 index 000000000..f627b49a8 --- /dev/null +++ b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/internal/HeaderIdAttributeProvider.java @@ -0,0 +1,76 @@ +package org.commonmark.ext.headerids.internal; + +import java.util.Map; + +import org.commonmark.ext.headerids.UniqueIdentifierProvider; +import org.commonmark.html.AttributeProvider; +import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.Code; +import org.commonmark.node.Heading; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + +public class HeaderIdAttributeProvider implements AttributeProvider { + + private final UniqueIdentifierProvider idProvider; + + private HeaderIdAttributeProvider() { + idProvider = new UniqueIdentifierProvider("heading"); + } + + public static HeaderIdAttributeProvider create() { + return new HeaderIdAttributeProvider(); + } + + @Override + public void setAttributes(Node node, final Map attributes) { + + if (node instanceof Heading) { + + final IdAttribute idAttribute = new IdAttribute(); + + node.accept(new AbstractVisitor() { + @Override + public void visit(Text text) { + idAttribute.add(text.getLiteral()); + } + + @Override + public void visit(Code code) { + idAttribute.add(code.getLiteral()); + } + }); + + attributes.put("id", idProvider.getUniqueIdentifier(idAttribute.toString())); + } + } + + private class IdAttribute { + StringBuilder sb = new StringBuilder(); + + public void add(String s) { + + // Do some basic substitution + s = s.toLowerCase(); + s = s.replaceAll(" +", "-"); + + for (char c : s.toCharArray()) { + if (isAllowedCharacter(c)) { + sb.append(c); + } + } + } + + public boolean isAllowedCharacter(char c) { + return Character.isAlphabetic(c) + || Character.isDigit(c) + || c == '_' || c == '-' + || (0x0300 <= c && c <= 0x036F); // Combining diacritical marks + } + + @Override + public String toString() { + return sb.toString(); + } + } +} From 260beed745740db49374ec794e488c59f0e72275 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Fri, 2 Sep 2016 11:53:47 +1000 Subject: [PATCH 151/815] Create extension point to be used when registering this in the renderer --- .../ext/headerids/HeaderIdExtension.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdExtension.java diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdExtension.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdExtension.java new file mode 100644 index 000000000..4534923b5 --- /dev/null +++ b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdExtension.java @@ -0,0 +1,46 @@ +package org.commonmark.ext.headerids; + +import org.commonmark.Extension; +import org.commonmark.ext.headerids.internal.HeaderIdAttributeProvider; +import org.commonmark.html.HtmlRenderer; + +/** + * Extension for adding auto generated ids to headings + *

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

    + *

    + * The heading text will be used to create the id. Multiple headings with the + * same text will result in appending a hyphen and number. For example: + *

    + * + *
    + * # Heading
    + * # Heading
    + * 
    + *
    + * will result in + * + *
    + * <h1 id="heading">Heading</h1>
    + * <h1 id="heading-1">Heading</h1>
    + * 
    + *
    + * + */ +public class HeaderIdExtension implements HtmlRenderer.HtmlRendererExtension +{ + private HeaderIdExtension() { + } + + public static Extension create() { + return new HeaderIdExtension(); + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) + { + rendererBuilder.attributeProvider(HeaderIdAttributeProvider.create()); + } +} From 311475a9409471b9de72c3d9387bf1f93b7aa30b Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Tue, 6 Sep 2016 16:33:59 +1000 Subject: [PATCH 152/815] Removing files from old package structure. Moving to using ext.heading.anchor --- .../ext/headerids/HeaderIdExtension.java | 46 ---------- .../headerids/UniqueIdentifierProvider.java | 92 ------------------- .../internal/HeaderIdAttributeProvider.java | 76 --------------- .../commonmark/ext/header/HeaderIdTest.java | 87 ------------------ 4 files changed, 301 deletions(-) delete mode 100644 commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdExtension.java delete mode 100644 commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/UniqueIdentifierProvider.java delete mode 100644 commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/internal/HeaderIdAttributeProvider.java delete mode 100644 commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdExtension.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdExtension.java deleted file mode 100644 index 4534923b5..000000000 --- a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/HeaderIdExtension.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.commonmark.ext.headerids; - -import org.commonmark.Extension; -import org.commonmark.ext.headerids.internal.HeaderIdAttributeProvider; -import org.commonmark.html.HtmlRenderer; - -/** - * Extension for adding auto generated ids to headings - *

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

    - *

    - * The heading text will be used to create the id. Multiple headings with the - * same text will result in appending a hyphen and number. For example: - *

    - * - *
    - * # Heading
    - * # Heading
    - * 
    - *
    - * will result in - * - *
    - * <h1 id="heading">Heading</h1>
    - * <h1 id="heading-1">Heading</h1>
    - * 
    - *
    - * - */ -public class HeaderIdExtension implements HtmlRenderer.HtmlRendererExtension -{ - private HeaderIdExtension() { - } - - public static Extension create() { - return new HeaderIdExtension(); - } - - @Override - public void extend(HtmlRenderer.Builder rendererBuilder) - { - rendererBuilder.attributeProvider(HeaderIdAttributeProvider.create()); - } -} diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/UniqueIdentifierProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/UniqueIdentifierProvider.java deleted file mode 100644 index f108d9f85..000000000 --- a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/UniqueIdentifierProvider.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.commonmark.ext.headerids; - -import java.util.HashMap; -import java.util.Map; - -/** - * Provider of unique strings to be used as an identifier - */ -public class UniqueIdentifierProvider -{ - private String defaultIdentifier; - private final Map identityMap; - - /** - * Create a provider with the default identifier of "id" - */ - public UniqueIdentifierProvider() { - this("id"); - } - - /** - * @param defaultIdentifier default identifier to use if given a null, or empty {@code providedIdentity} - * in {@link #getUniqueIdentifier(String)} - */ - public UniqueIdentifierProvider(String defaultIdentifier) - { - this.defaultIdentifier = defaultIdentifier; - this.identityMap = new HashMap<>(); - } - - public String getDefaultIdentifier() - { - return defaultIdentifier; - } - - public void setDefaultIdentifier(String defaultIdentifier) - { - this.defaultIdentifier = defaultIdentifier; - } - - /** - *

    - * Provides unique strings over the lifetime of the instance - *

    - *

    - * This method is not thread safe, concurrent calls can end up - * with non-unique identifiers - *

    - *

    - * Note that collision can occur in the case that - *

      - *
    • String 'X' provided to getUniqueIdentifier
    • - *
    • String 'X' provided again to getUniqueIdentifier
    • - *
    • String 'X-1' provided to getUniqueIdentifier
    • - *
    - *

    - *

    - * In that case, the three ids provided will be - *

      - *
    • X
    • - *
    • X-1
    • - *
    • X-1
    • - *
    - *

    - *

    - * Therefore if collisions are unnacceptable you should ensure that - * numbers are stripped from end of {@code providedIdentity} - *

    - * - * @param providedIdentity tentative identifier to be used. Will be used to check if this is unique. Will be the - * basis for the returned identifier - * - * @return {@code providedIdentity} if this is the first instance that the {@code providedIdentity} has been passed - * to the method. Otherwise, {@code providedIdentity + "-" + X} will be returned, where X is the number of times - * that {@code providedIdentity} has previously been passed in. If {@code providedIdentity} is empty, the default - * identifier given in the constructor (or "id" if none given) will be used. - */ - public String getUniqueIdentifier(String providedIdentity) { - if (providedIdentity == null || providedIdentity.trim().length() == 0) { - providedIdentity = defaultIdentifier; - } - - if (!identityMap.containsKey(providedIdentity)) { - identityMap.put(providedIdentity, 1); - return providedIdentity; - } else { - int currentCount = identityMap.get(providedIdentity); - identityMap.put(providedIdentity, currentCount + 1); - return providedIdentity + "-" +currentCount; - } - } -} diff --git a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/internal/HeaderIdAttributeProvider.java b/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/internal/HeaderIdAttributeProvider.java deleted file mode 100644 index f627b49a8..000000000 --- a/commonmark-ext-header-ids/src/main/java/org/commonmark/ext/headerids/internal/HeaderIdAttributeProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.commonmark.ext.headerids.internal; - -import java.util.Map; - -import org.commonmark.ext.headerids.UniqueIdentifierProvider; -import org.commonmark.html.AttributeProvider; -import org.commonmark.node.AbstractVisitor; -import org.commonmark.node.Code; -import org.commonmark.node.Heading; -import org.commonmark.node.Node; -import org.commonmark.node.Text; - -public class HeaderIdAttributeProvider implements AttributeProvider { - - private final UniqueIdentifierProvider idProvider; - - private HeaderIdAttributeProvider() { - idProvider = new UniqueIdentifierProvider("heading"); - } - - public static HeaderIdAttributeProvider create() { - return new HeaderIdAttributeProvider(); - } - - @Override - public void setAttributes(Node node, final Map attributes) { - - if (node instanceof Heading) { - - final IdAttribute idAttribute = new IdAttribute(); - - node.accept(new AbstractVisitor() { - @Override - public void visit(Text text) { - idAttribute.add(text.getLiteral()); - } - - @Override - public void visit(Code code) { - idAttribute.add(code.getLiteral()); - } - }); - - attributes.put("id", idProvider.getUniqueIdentifier(idAttribute.toString())); - } - } - - private class IdAttribute { - StringBuilder sb = new StringBuilder(); - - public void add(String s) { - - // Do some basic substitution - s = s.toLowerCase(); - s = s.replaceAll(" +", "-"); - - for (char c : s.toCharArray()) { - if (isAllowedCharacter(c)) { - sb.append(c); - } - } - } - - public boolean isAllowedCharacter(char c) { - return Character.isAlphabetic(c) - || Character.isDigit(c) - || c == '_' || c == '-' - || (0x0300 <= c && c <= 0x036F); // Combining diacritical marks - } - - @Override - public String toString() { - return sb.toString(); - } - } -} diff --git a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java b/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java deleted file mode 100644 index 14696dbe5..000000000 --- a/commonmark-ext-header-ids/src/test/java/org/commonmark/ext/header/HeaderIdTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.commonmark.ext.headerids; - -import org.commonmark.ext.headerids.internal.HeaderIdAttributeProvider; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.parser.Parser; -import org.commonmark.test.RenderingTestCase; -import org.junit.Before; -import org.junit.Test; - -public class HeaderIdTest extends RenderingTestCase { - - private static final Parser PARSER = Parser.builder().build(); - private static final HeaderIdAttributeProvider attributeProvider = HeaderIdAttributeProvider.create(); - private static HtmlRenderer RENDERER = HtmlRenderer.builder().attributeProvider(attributeProvider).build(); - - @Before - public void resetHeader() { - RENDERER = HtmlRenderer.builder() - .attributeProvider(HeaderIdAttributeProvider.create()) - .build(); - } - - @Test - public void baseCaseSingleHeader() { - assertRendering("# Heading here\n", - "

    Heading here

    \n"); - } - - @Test - public void singleHeaderWithCodeBlock() { - assertRendering("Hi there\n# Heading `here`\n", - "

    Hi there

    \n

    Heading here

    \n"); - } - - @Test - public void duplicateHeadersMakeUniqueIds() { - assertRendering("# Heading here\n# Heading here", - "

    Heading here

    \n

    Heading here

    \n"); - } - - @Test - public void testExplicitHeaderCollision() { - assertRendering("# Header\n# Header\n# Header-1", - "

    Header

    \n" + - "

    Header

    \n" + - "

    Header-1

    \n"); - } - - @Test - public void testCaseIsIgnoredWhenComparingIds() { - assertRendering("# HEADING here\n" + - "# heading here", - "

    HEADING here

    \n" + - "

    heading here

    \n"); - } - - @Test - public void testNestedBlocks() { - assertRendering("## `h` `e` **l** *l* o", - "

    h e l l o

    \n"); - } - - @Test - public void boldEmphasisCharacters() { - assertRendering("# _hello_ **there**", "

    hello there

    \n"); - } - - @Test - public void testStrongEmphasis() { - assertRendering("# _**Hi there**_", "

    Hi there

    \n"); - } - - @Test - public void testNonAsciiCharacterHeading() { - assertRendering("# bär", "

    bär

    \n"); - } - - @Test - public void testCombiningDiaeresis() { - assertRendering("# Product\u036D\u036B", "

    Product\u036D\u036B

    \n"); - } - - @Override - protected String render(String source) { - return RENDERER.render(PARSER.parse(source)); - } -} From a46ac271660715370fb406f8f68f81f9525e5f76 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Tue, 6 Sep 2016 16:34:24 +1000 Subject: [PATCH 153/815] Changing artifact id to new ext-heading-anchor --- commonmark-ext-header-ids/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-header-ids/pom.xml b/commonmark-ext-header-ids/pom.xml index bdf6f2f79..5885bf697 100644 --- a/commonmark-ext-header-ids/pom.xml +++ b/commonmark-ext-header-ids/pom.xml @@ -7,7 +7,7 @@ 0.4.2-SNAPSHOT
    - commonmark-ext-header-ids + ext-heading-anchor commonmark-java extension for adding id attributes to h tags commonmark-java extension for adding unique id attributes to header tags From 5266bd89cb87dd12eda2e0306554fbfb934a10f4 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Tue, 6 Sep 2016 16:34:40 +1000 Subject: [PATCH 154/815] Rename test file and add new Unicode tests New tests have been added for extra diacritics. This will not have worked in the previous version on the module --- .../java/ext/heading/anchor/HeaderIdTest.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 commonmark-ext-header-ids/src/test/java/ext/heading/anchor/HeaderIdTest.java diff --git a/commonmark-ext-header-ids/src/test/java/ext/heading/anchor/HeaderIdTest.java b/commonmark-ext-header-ids/src/test/java/ext/heading/anchor/HeaderIdTest.java new file mode 100644 index 000000000..012aebfd3 --- /dev/null +++ b/commonmark-ext-header-ids/src/test/java/ext/heading/anchor/HeaderIdTest.java @@ -0,0 +1,102 @@ +package ext.heading.anchor; + +import ext.heading.anchor.internal.HeaderIdAttributeProvider; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.parser.Parser; +import org.commonmark.test.RenderingTestCase; +import org.junit.Before; +import org.junit.Test; + +public class HeaderIdTest extends RenderingTestCase { + + private static final Parser PARSER = Parser.builder().build(); + private static final HeaderIdAttributeProvider attributeProvider = HeaderIdAttributeProvider.create(); + private static HtmlRenderer RENDERER = HtmlRenderer.builder().attributeProvider(attributeProvider).build(); + + @Before + public void resetHeader() { + RENDERER = HtmlRenderer.builder() + .attributeProvider(HeaderIdAttributeProvider.create()) + .build(); + } + + @Test + public void baseCaseSingleHeader() { + assertRendering("# Heading here\n", + "

    Heading here

    \n"); + } + + @Test + public void singleHeaderWithCodeBlock() { + assertRendering("Hi there\n# Heading `here`\n", + "

    Hi there

    \n

    Heading here

    \n"); + } + + @Test + public void duplicateHeadersMakeUniqueIds() { + assertRendering("# Heading here\n# Heading here", + "

    Heading here

    \n

    Heading here

    \n"); + } + + @Test + public void testSupplementalDiacriticalMarks() { + assertRendering("# a\u1DC0", "

    a\u1DC0

    \n"); + } + + @Test + public void testUndertieUnicodeDisplayed() { + assertRendering("# undertie \u203F", "

    undertie \u203F

    \n"); + } + + @Test + public void testExplicitHeaderCollision() { + assertRendering("# Header\n# Header\n# Header-1", + "

    Header

    \n" + + "

    Header

    \n" + + "

    Header-1

    \n"); + } + + @Test + public void testCaseIsIgnoredWhenComparingIds() { + assertRendering("# HEADING here\n" + + "# heading here", + "

    HEADING here

    \n" + + "

    heading here

    \n"); + } + + @Test + public void testNestedBlocks() { + assertRendering("## `h` `e` **l** *l* o", + "

    h e l l o

    \n"); + } + + @Test + public void boldEmphasisCharacters() { + assertRendering("# _hello_ **there**", "

    hello there

    \n"); + } + + @Test + public void testStrongEmphasis() { + assertRendering("# _**Hi there**_", "

    Hi there

    \n"); + } + + @Test + public void testMultipleSpacesKept() { + assertRendering("# Hi There", "

    Hi There

    \n"); + } + + @Test + public void testNonAsciiCharacterHeading() { + assertRendering("# bär", "

    bär

    \n"); + } + + @Test + public void testCombiningDiaeresis() { + assertRendering("# Product\u036D\u036B", "

    Product\u036D\u036B

    \n"); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} From bed559a6941694c1585d65d7aefabb5286bafdfc Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Tue, 6 Sep 2016 16:36:57 +1000 Subject: [PATCH 155/815] Move files to new package strucutre, support new unicode Due to the rename this commit contains slightly more than i expected * Rename the modules that these are within to ext.heading.anchor * Remove IdAttribute, move its functionality into UniqueIndentifierProvider * Change regex used in the UIProvider to support additional unicode symbols --- .../ext/heading/anchor/HeaderIdExtension.java | 45 +++++++ .../anchor/UniqueIdentifierProvider.java | 115 ++++++++++++++++++ .../internal/HeaderIdAttributeProvider.java | 51 ++++++++ 3 files changed, 211 insertions(+) create mode 100644 commonmark-ext-header-ids/src/main/java/ext/heading/anchor/HeaderIdExtension.java create mode 100644 commonmark-ext-header-ids/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java create mode 100644 commonmark-ext-header-ids/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java diff --git a/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/HeaderIdExtension.java b/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/HeaderIdExtension.java new file mode 100644 index 000000000..18287f16e --- /dev/null +++ b/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/HeaderIdExtension.java @@ -0,0 +1,45 @@ +package ext.heading.anchor; + +import org.commonmark.Extension; +import org.commonmark.html.HtmlRenderer; + +import ext.heading.anchor.internal.HeaderIdAttributeProvider; + +/** + * Extension for adding auto generated ids to headings + *

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

    + *

    + * The heading text will be used to create the id. Multiple headings with the + * same text will result in appending a hyphen and number. For example: + *

    + * + *
    + * # Heading
    + * # Heading
    + * 
    + *
    + * will result in + * + *
    + * <h1 id="heading">Heading</h1>
    + * <h1 id="heading-1">Heading</h1>
    + * 
    + *
    + */ +public class HeaderIdExtension implements HtmlRenderer.HtmlRendererExtension { + + private HeaderIdExtension() { + } + + public static Extension create() { + return new HeaderIdExtension(); + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.attributeProvider(HeaderIdAttributeProvider.create()); + } +} diff --git a/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java b/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java new file mode 100644 index 000000000..5c711b260 --- /dev/null +++ b/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java @@ -0,0 +1,115 @@ +package ext.heading.anchor; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provider of unique strings to be used as an identifier + */ +public class UniqueIdentifierProvider { + private final Pattern allowedCharacters = Pattern.compile("[\\w\\-_]+", Pattern.UNICODE_CHARACTER_CLASS); + private final Map identityMap; + private String defaultIdentifier; + + /** + * Create a provider with the default identifier of "id" + */ + public UniqueIdentifierProvider() { + this("id"); + } + + /** + * @param defaultIdentifier default identifier to use if given a null, or empty {@code providedIdentity} + * in {@link #getUniqueIdentifier(String)} + */ + public UniqueIdentifierProvider(String defaultIdentifier) { + this.defaultIdentifier = defaultIdentifier; + this.identityMap = new HashMap<>(); + } + + public String getDefaultIdentifier() { + return defaultIdentifier; + } + + public void setDefaultIdentifier(String defaultIdentifier) { + this.defaultIdentifier = defaultIdentifier; + } + + /** + *

    + * Provides unique strings over the lifetime of the instance + *

    + *

    + * This method is not thread safe, concurrent calls can end up + * with non-unique identifiers + *

    + *

    + * Note that collision can occur in the case that + *

      + *
    • String 'X' provided to getUniqueIdentifier
    • + *
    • String 'X' provided again to getUniqueIdentifier
    • + *
    • String 'X-1' provided to getUniqueIdentifier
    • + *
    + *

    + *

    + * In that case, the three ids provided will be + *

      + *
    • X
    • + *
    • X-1
    • + *
    • X-1
    • + *
    + *

    + *

    + * Therefore if collisions are unnacceptable you should ensure that + * numbers are stripped from end of {@code providedIdentity} + *

    + * + * @param providedIdentity tentative identifier to be used. Will be normalised, then used to generate the + * identifier. + * Becomes the basis for the returned identifier. + * @return {@code providedIdentity} if this is the first instance that the {@code providedIdentity} has been passed + * to the method. Otherwise, {@code providedIdentity + "-" + X} will be returned, where X is the number of times + * that {@code providedIdentity} has previously been passed in. If {@code providedIdentity} is empty, the default + * identifier given in the constructor (or "id" if none given) will be used. + */ + public String getUniqueIdentifier(String providedIdentity) { + String normalizedIdentity = + providedIdentity != null ? + normalizeProvidedIdentity(providedIdentity) : defaultIdentifier; + + if (normalizedIdentity.trim().length() == 0) { + normalizedIdentity = defaultIdentifier; + } + + if (!identityMap.containsKey(normalizedIdentity)) { + identityMap.put(normalizedIdentity, 1); + return normalizedIdentity; + } + else { + int currentCount = identityMap.get(normalizedIdentity); + identityMap.put(normalizedIdentity, currentCount + 1); + return normalizedIdentity + "-" + currentCount; + } + } + + /** + * Assume we've been given a space separated + * + * @param providedIdentity + */ + private String normalizeProvidedIdentity(String providedIdentity) { + String firstPassNormalising = providedIdentity.toLowerCase() + .replace(" ", "-"); + + StringBuilder sb = new StringBuilder(); + Matcher matcher = allowedCharacters.matcher(firstPassNormalising); + + while (matcher.find()) { + sb.append(matcher.group()); + } + + return sb.toString(); + } +} diff --git a/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java b/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java new file mode 100644 index 000000000..f291bcb5b --- /dev/null +++ b/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java @@ -0,0 +1,51 @@ +package ext.heading.anchor.internal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.commonmark.html.AttributeProvider; +import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.Code; +import org.commonmark.node.Heading; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + +import ext.heading.anchor.UniqueIdentifierProvider; + +public class HeaderIdAttributeProvider implements AttributeProvider { + + private final UniqueIdentifierProvider idProvider; + + private HeaderIdAttributeProvider() { + idProvider = new UniqueIdentifierProvider("heading"); + } + + public static HeaderIdAttributeProvider create() { + return new HeaderIdAttributeProvider(); + } + + @Override + public void setAttributes(Node node, final Map attributes) { + + if (node instanceof Heading) { + + final List wordList = new ArrayList<>(); + + node.accept(new AbstractVisitor() { + @Override + public void visit(Text text) { + wordList.add(text.getLiteral()); + } + + @Override + public void visit(Code code) { + wordList.add(code.getLiteral()); + } + }); + + attributes.put("id", idProvider.getUniqueIdentifier(String.join("", wordList)).toLowerCase()); + } + } + +} From 326383f052f5ad60e68846c603aa3efc297e7ec0 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Tue, 6 Sep 2016 16:40:49 +1000 Subject: [PATCH 156/815] Change directory to match artifact name --- .../pom.xml | 0 .../src/main/java/ext/heading/anchor/HeaderIdExtension.java | 0 .../main/java/ext/heading/anchor/UniqueIdentifierProvider.java | 0 .../ext/heading/anchor/internal/HeaderIdAttributeProvider.java | 0 .../src/main/javadoc/overview.html | 0 .../src/test/java/ext/heading/anchor/HeaderIdTest.java | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {commonmark-ext-header-ids => commonmark-ext-heading-anchor}/pom.xml (100%) rename {commonmark-ext-header-ids => commonmark-ext-heading-anchor}/src/main/java/ext/heading/anchor/HeaderIdExtension.java (100%) rename {commonmark-ext-header-ids => commonmark-ext-heading-anchor}/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java (100%) rename {commonmark-ext-header-ids => commonmark-ext-heading-anchor}/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java (100%) rename {commonmark-ext-header-ids => commonmark-ext-heading-anchor}/src/main/javadoc/overview.html (100%) rename {commonmark-ext-header-ids => commonmark-ext-heading-anchor}/src/test/java/ext/heading/anchor/HeaderIdTest.java (100%) diff --git a/commonmark-ext-header-ids/pom.xml b/commonmark-ext-heading-anchor/pom.xml similarity index 100% rename from commonmark-ext-header-ids/pom.xml rename to commonmark-ext-heading-anchor/pom.xml diff --git a/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/HeaderIdExtension.java b/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/HeaderIdExtension.java similarity index 100% rename from commonmark-ext-header-ids/src/main/java/ext/heading/anchor/HeaderIdExtension.java rename to commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/HeaderIdExtension.java diff --git a/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java b/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java similarity index 100% rename from commonmark-ext-header-ids/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java rename to commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java diff --git a/commonmark-ext-header-ids/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java similarity index 100% rename from commonmark-ext-header-ids/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java rename to commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java diff --git a/commonmark-ext-header-ids/src/main/javadoc/overview.html b/commonmark-ext-heading-anchor/src/main/javadoc/overview.html similarity index 100% rename from commonmark-ext-header-ids/src/main/javadoc/overview.html rename to commonmark-ext-heading-anchor/src/main/javadoc/overview.html diff --git a/commonmark-ext-header-ids/src/test/java/ext/heading/anchor/HeaderIdTest.java b/commonmark-ext-heading-anchor/src/test/java/ext/heading/anchor/HeaderIdTest.java similarity index 100% rename from commonmark-ext-header-ids/src/test/java/ext/heading/anchor/HeaderIdTest.java rename to commonmark-ext-heading-anchor/src/test/java/ext/heading/anchor/HeaderIdTest.java From dbba1fb3a26303d2243c89a02fb0539a9a57547d Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Tue, 6 Sep 2016 16:41:01 +1000 Subject: [PATCH 157/815] Update pom artifacts for new identifier Note that i kept it as commonmark-ext-heading-anchor rather than going with ext-heading-anchor. This was just due to keeping in style with the other ext modules. Happy to change this if required --- commonmark-ext-heading-anchor/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 5885bf697..792a9e30e 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -7,7 +7,7 @@ 0.4.2-SNAPSHOT - ext-heading-anchor + commonmark-ext-heading-anchor commonmark-java extension for adding id attributes to h tags commonmark-java extension for adding unique id attributes to header tags diff --git a/pom.xml b/pom.xml index a1f400099..2bd8cd903 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ commonmark-ext-autolink commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables - commonmark-ext-header-ids + commonmark-ext-heading-anchor commonmark-integration-test commonmark-test-util From 1a8cc3e8049e03348dd1608f88770fb3a429979f Mon Sep 17 00:00:00 2001 From: Perry Branch Date: Wed, 7 Sep 2016 03:25:29 +0000 Subject: [PATCH 158/815] create extension (underline) #54 copied from `commonmark-ext-gfm-strikethrough` and renamed --- README.md | 9 +- commonmark-ext-ins/pom.xml | 27 ++++++ .../main/java/org/commonmark/ext/ins/Ins.java | 22 +++++ .../org/commonmark/ext/ins/InsExtension.java | 46 ++++++++++ .../ins/internal/InsDelimiterProcessor.java | 50 +++++++++++ .../ext/ins/internal/InsNodeRenderer.java | 44 ++++++++++ .../src/main/javadoc/overview.html | 6 ++ .../java/org/commonmark/ext/ins/InsTest.java | 87 +++++++++++++++++++ pom.xml | 1 + 9 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 commonmark-ext-ins/pom.xml create mode 100644 commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/Ins.java create mode 100644 commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java create mode 100644 commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java create mode 100644 commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java create mode 100644 commonmark-ext-ins/src/main/javadoc/overview.html create mode 100644 commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java diff --git a/README.md b/README.md index d59a25562..736db781e 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,13 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. +### Ins + +Enables underlining of text by enclosing it in `++`. For example, in +`hey ++you++`, `you` will be rendered as underline text. Uses the <ins> tag. + +Use class `InsExtension` in artifact `commonmark-ext-ins`. + ### YAML front matter Adds support for metadata through a YAML front matter block. This extension only supports a subset of YAML syntax. Here's an example of what's supported: @@ -161,7 +168,7 @@ list: - value 2 literal: | this is literal value. - + literal values 2 --- diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml new file mode 100644 index 000000000..0bce1524d --- /dev/null +++ b/commonmark-ext-ins/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + com.atlassian.commonmark + commonmark-parent + 0.6.1-SNAPSHOT + + + commonmark-ext-ins + commonmark-java extension for <ins> (underline) + commonmark-java extension for <ins> using ++ + + + + com.atlassian.commonmark + commonmark + + + + com.atlassian.commonmark + commonmark-test-util + test + + + + diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/Ins.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/Ins.java new file mode 100644 index 000000000..2ebd4f5ca --- /dev/null +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/Ins.java @@ -0,0 +1,22 @@ +package org.commonmark.ext.ins; + +import org.commonmark.node.CustomNode; +import org.commonmark.node.Delimited; + +/** + * An ins node containing text and other inline nodes as children. + */ +public class Ins extends CustomNode implements Delimited { + + private static final String DELIMITER = "++"; + + @Override + public String getOpeningDelimiter() { + return DELIMITER; + } + + @Override + public String getClosingDelimiter() { + return DELIMITER; + } +} diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java new file mode 100644 index 000000000..edcfb0373 --- /dev/null +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/InsExtension.java @@ -0,0 +1,46 @@ +package org.commonmark.ext.ins; + +import org.commonmark.Extension; +import org.commonmark.ext.ins.internal.InsDelimiterProcessor; +import org.commonmark.ext.ins.internal.InsNodeRenderer; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.html.renderer.NodeRendererFactory; +import org.commonmark.parser.Parser; +import org.commonmark.html.HtmlRenderer; + +/** + * Extension for ins using ++ + *

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

    + *

    + * The parsed ins text regions are turned into {@link Ins} nodes. + *

    + */ +public class InsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { + + private InsExtension() { + } + + public static Extension create() { + return new InsExtension(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customDelimiterProcessor(new InsDelimiterProcessor()); + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new NodeRendererFactory() { + @Override + public NodeRenderer create(NodeRendererContext context) { + return new InsNodeRenderer(context); + } + }); + } +} diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java new file mode 100644 index 000000000..9a4ad383c --- /dev/null +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java @@ -0,0 +1,50 @@ +package org.commonmark.ext.ins.internal; + +import org.commonmark.ext.ins.Ins; +import org.commonmark.node.Node; +import org.commonmark.node.Text; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; + +public class InsDelimiterProcessor implements DelimiterProcessor { + + @Override + public char getOpeningCharacter() { + return '+'; + } + + @Override + public char getClosingCharacter() { + return '+'; + } + + @Override + public int getMinLength() { + return 2; + } + + @Override + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + if (opener.length() >= 2 && closer.length() >= 2) { + // Use exactly two delimiters even if we have more, and don't care about internal openers/closers. + return 2; + } else { + return 0; + } + } + + @Override + public void process(Text opener, Text closer, int delimiterCount) { + // Wrap nodes between delimiters in ins. + Node ins = new Ins(); + + Node tmp = opener.getNext(); + while (tmp != null && tmp != closer) { + Node next = tmp.getNext(); + ins.appendChild(tmp); + tmp = next; + } + + opener.insertAfter(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 new file mode 100644 index 000000000..1830988dd --- /dev/null +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java @@ -0,0 +1,44 @@ +package org.commonmark.ext.ins.internal; + +import org.commonmark.ext.ins.Ins; +import org.commonmark.html.HtmlWriter; +import org.commonmark.html.renderer.NodeRenderer; +import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.node.Node; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +public class InsNodeRenderer implements NodeRenderer { + + private final NodeRendererContext context; + private final HtmlWriter html; + + public InsNodeRenderer(NodeRendererContext context) { + this.context = context; + this.html = context.getHtmlWriter(); + } + + @Override + public Set> getNodeTypes() { + return Collections.>singleton(Ins.class); + } + + @Override + public void render(Node node) { + Map attributes = context.extendAttributes(node, Collections.emptyMap()); + html.tag("ins", attributes); + renderChildren(node); + html.tag("/ins"); + } + + 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/main/javadoc/overview.html b/commonmark-ext-ins/src/main/javadoc/overview.html new file mode 100644 index 000000000..6dad34d3f --- /dev/null +++ b/commonmark-ext-ins/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for ins (underline) using ++ +

    See {@link org.commonmark.ext.ins.InsExtension}

    + + 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 new file mode 100644 index 000000000..474c23786 --- /dev/null +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java @@ -0,0 +1,87 @@ +package org.commonmark.ext.ins; + +import org.commonmark.Extension; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.test.RenderingTestCase; +import org.junit.Test; + +import java.util.Collections; +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 Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void onePlusIsNotEnough() { + assertRendering("+foo+", "

    +foo+

    \n"); + } + + @Test + public void twoPlusesYay() { + assertRendering("++foo++", "

    foo

    \n"); + } + + @Test + public void fourPlusesNope() { + assertRendering("foo ++++", "

    foo ++++

    \n"); + } + + @Test + public void unmatched() { + assertRendering("++foo", "

    ++foo

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

    foo++

    \n"); + } + + @Test + public void threeInnerThree() { + assertRendering("+++foo+++", "

    +foo+

    \n"); + } + + @Test + public void twoInnerThree() { + assertRendering("++foo+++", "

    foo+

    \n"); + } + + @Test + public void plusesInside() { + assertRendering("++foo+bar++", "

    foo+bar

    \n"); + assertRendering("++foo++bar++", "

    foobar++

    \n"); + assertRendering("++foo+++bar++", "

    foo+bar++

    \n"); + assertRendering("++foo++++bar++", "

    foobar

    \n"); + assertRendering("++foo+++++bar++", "

    foo+bar

    \n"); + assertRendering("++foo++++++bar++", "

    foo++bar

    \n"); + assertRendering("++foo+++++++bar++", "

    foo+++bar

    \n"); + } + + @Test + public void insWholeParagraphWithOtherDelimiters() { + assertRendering("++Paragraph with *emphasis* and __strong emphasis__++", + "

    Paragraph with emphasis and strong emphasis

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

    underline that

    \n
    \n"); + } + + @Test + public void delimited() { + Node document = PARSER.parse("++foo++"); + Ins ins = (Ins) document.getFirstChild().getFirstChild(); + assertEquals("++", ins.getOpeningDelimiter()); + assertEquals("++", ins.getClosingDelimiter()); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/pom.xml b/pom.xml index 42790d47a..ee87aefcf 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ commonmark-ext-autolink commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables + commonmark-ext-ins commonmark-ext-yaml-front-matter commonmark-integration-test commonmark-test-util From 55099a2fec12e0bc0e107eedd923834e2ae9c5d1 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Mon, 12 Sep 2016 16:05:11 +1000 Subject: [PATCH 159/815] Update readme to new module name change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 216644e2c..20850547d 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Enables adding auto generated id attributes to header based on their content.

    Heading

    ``` -Use class `HeaderIdExtension` in artifact `commonmark-ext-header-ids` +Use class `HeaderIdExtension` in artifact `commonmark-ext-heading-anchor` Contributing From 263f0e5e7d92e136beb27f0c9e0f04e72fe0a74e Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Mon, 12 Sep 2016 16:06:26 +1000 Subject: [PATCH 160/815] Move package e.h.anchor to org.commonmark.e.h.anchor --- .../commonmark}/ext/heading/anchor/HeaderIdExtension.java | 4 ++-- .../ext/heading/anchor/UniqueIdentifierProvider.java | 2 +- .../heading/anchor/internal/HeaderIdAttributeProvider.java | 4 ++-- .../{ => org/commonmark}/ext/heading/anchor/HeaderIdTest.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename commonmark-ext-heading-anchor/src/main/java/{ => org/commonmark}/ext/heading/anchor/HeaderIdExtension.java (89%) rename commonmark-ext-heading-anchor/src/main/java/{ => org/commonmark}/ext/heading/anchor/UniqueIdentifierProvider.java (98%) rename commonmark-ext-heading-anchor/src/main/java/{ => org/commonmark}/ext/heading/anchor/internal/HeaderIdAttributeProvider.java (91%) rename commonmark-ext-heading-anchor/src/test/java/{ => org/commonmark}/ext/heading/anchor/HeaderIdTest.java (96%) diff --git a/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/HeaderIdExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeaderIdExtension.java similarity index 89% rename from commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/HeaderIdExtension.java rename to commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeaderIdExtension.java index 18287f16e..81875f0bf 100644 --- a/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/HeaderIdExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeaderIdExtension.java @@ -1,9 +1,9 @@ -package ext.heading.anchor; +package org.commonmark.ext.heading.anchor; import org.commonmark.Extension; import org.commonmark.html.HtmlRenderer; -import ext.heading.anchor.internal.HeaderIdAttributeProvider; +import org.commonmark.ext.heading.anchor.internal.HeaderIdAttributeProvider; /** * Extension for adding auto generated ids to headings diff --git a/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/UniqueIdentifierProvider.java similarity index 98% rename from commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java rename to commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/UniqueIdentifierProvider.java index 5c711b260..09247674e 100644 --- a/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/UniqueIdentifierProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/UniqueIdentifierProvider.java @@ -1,4 +1,4 @@ -package ext.heading.anchor; +package org.commonmark.ext.heading.anchor; import java.util.HashMap; import java.util.Map; diff --git a/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeaderIdAttributeProvider.java similarity index 91% rename from commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java rename to commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeaderIdAttributeProvider.java index f291bcb5b..c60e79a46 100644 --- a/commonmark-ext-heading-anchor/src/main/java/ext/heading/anchor/internal/HeaderIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeaderIdAttributeProvider.java @@ -1,4 +1,4 @@ -package ext.heading.anchor.internal; +package org.commonmark.ext.heading.anchor.internal; import java.util.ArrayList; import java.util.List; @@ -11,7 +11,7 @@ import org.commonmark.node.Node; import org.commonmark.node.Text; -import ext.heading.anchor.UniqueIdentifierProvider; +import org.commonmark.ext.heading.anchor.UniqueIdentifierProvider; public class HeaderIdAttributeProvider implements AttributeProvider { diff --git a/commonmark-ext-heading-anchor/src/test/java/ext/heading/anchor/HeaderIdTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java similarity index 96% rename from commonmark-ext-heading-anchor/src/test/java/ext/heading/anchor/HeaderIdTest.java rename to commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java index 012aebfd3..96ff05c0d 100644 --- a/commonmark-ext-heading-anchor/src/test/java/ext/heading/anchor/HeaderIdTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java @@ -1,6 +1,6 @@ -package ext.heading.anchor; +package org.commonmark.ext.heading.anchor; -import ext.heading.anchor.internal.HeaderIdAttributeProvider; +import org.commonmark.ext.heading.anchor.internal.HeaderIdAttributeProvider; import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; From f30940461dc4f75fff57078bca71708f43fbaf1d Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Mon, 12 Sep 2016 16:08:01 +1000 Subject: [PATCH 161/815] Rename Header references to Heading for consistency --- ...derIdExtension.java => HeadingAnchorExtension.java} | 10 +++++----- ...teProvider.java => HeadingIdAttributeProvider.java} | 8 ++++---- .../commonmark/ext/heading/anchor/HeaderIdTest.java | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) rename commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/{HeaderIdExtension.java => HeadingAnchorExtension.java} (72%) rename commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/{HeaderIdAttributeProvider.java => HeadingIdAttributeProvider.java} (84%) diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeaderIdExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java similarity index 72% rename from commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeaderIdExtension.java rename to commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index 81875f0bf..86411ca18 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeaderIdExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -3,7 +3,7 @@ import org.commonmark.Extension; import org.commonmark.html.HtmlRenderer; -import org.commonmark.ext.heading.anchor.internal.HeaderIdAttributeProvider; +import org.commonmark.ext.heading.anchor.internal.HeadingIdAttributeProvider; /** * Extension for adding auto generated ids to headings @@ -29,17 +29,17 @@ * * */ -public class HeaderIdExtension implements HtmlRenderer.HtmlRendererExtension { +public class HeadingAnchorExtension implements HtmlRenderer.HtmlRendererExtension { - private HeaderIdExtension() { + private HeadingAnchorExtension() { } public static Extension create() { - return new HeaderIdExtension(); + return new HeadingAnchorExtension(); } @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.attributeProvider(HeaderIdAttributeProvider.create()); + rendererBuilder.attributeProvider(HeadingIdAttributeProvider.create()); } } diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeaderIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java similarity index 84% rename from commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeaderIdAttributeProvider.java rename to commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index c60e79a46..9e3a2d353 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeaderIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -13,16 +13,16 @@ import org.commonmark.ext.heading.anchor.UniqueIdentifierProvider; -public class HeaderIdAttributeProvider implements AttributeProvider { +public class HeadingIdAttributeProvider implements AttributeProvider { private final UniqueIdentifierProvider idProvider; - private HeaderIdAttributeProvider() { + private HeadingIdAttributeProvider() { idProvider = new UniqueIdentifierProvider("heading"); } - public static HeaderIdAttributeProvider create() { - return new HeaderIdAttributeProvider(); + public static HeadingIdAttributeProvider create() { + return new HeadingIdAttributeProvider(); } @Override diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java index 96ff05c0d..97c28cc95 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java @@ -1,6 +1,6 @@ package org.commonmark.ext.heading.anchor; -import org.commonmark.ext.heading.anchor.internal.HeaderIdAttributeProvider; +import org.commonmark.ext.heading.anchor.internal.HeadingIdAttributeProvider; import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; @@ -10,13 +10,13 @@ public class HeaderIdTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().build(); - private static final HeaderIdAttributeProvider attributeProvider = HeaderIdAttributeProvider.create(); + private static final HeadingIdAttributeProvider attributeProvider = HeadingIdAttributeProvider.create(); private static HtmlRenderer RENDERER = HtmlRenderer.builder().attributeProvider(attributeProvider).build(); @Before public void resetHeader() { RENDERER = HtmlRenderer.builder() - .attributeProvider(HeaderIdAttributeProvider.create()) + .attributeProvider(HeadingIdAttributeProvider.create()) .build(); } From 2ebfe7a9e8a53f2498c938337876c1f27ae34ea7 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Mon, 12 Sep 2016 16:08:20 +1000 Subject: [PATCH 162/815] Fix incorrect link to link to heading Extension --- commonmark-ext-heading-anchor/src/main/javadoc/overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-heading-anchor/src/main/javadoc/overview.html b/commonmark-ext-heading-anchor/src/main/javadoc/overview.html index e98452c0c..cae7fdd09 100644 --- a/commonmark-ext-heading-anchor/src/main/javadoc/overview.html +++ b/commonmark-ext-heading-anchor/src/main/javadoc/overview.html @@ -1,6 +1,6 @@ Extension for automatically adding Id attributes to all headers -

    See {@link org.commonmark.ext.autolink.AutolinkExtension}

    +

    See {@link org.commonmark.ext.heading.anchor.HeadingAnchorExtension

    From dfa2fec6b1cd3669204c93330695809c2571ad7b Mon Sep 17 00:00:00 2001 From: Perry Branch Date: Mon, 12 Sep 2016 11:52:43 +0000 Subject: [PATCH 163/815] add InsExtension to SpecIntegrationTest #54 --- commonmark-integration-test/pom.xml | 4 ++++ .../java/org/commonmark/integration/SpecIntegrationTest.java | 2 ++ pom.xml | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index fe9b807b8..7260f7ca6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -20,6 +20,10 @@ com.atlassian.commonmark commonmark-ext-autolink + + com.atlassian.commonmark + commonmark-ext-ins + com.atlassian.commonmark commonmark-ext-gfm-strikethrough 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 a45e0375d..6e728bc22 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java @@ -2,6 +2,7 @@ import org.commonmark.Extension; import org.commonmark.ext.autolink.AutolinkExtension; +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; @@ -20,6 +21,7 @@ public class SpecIntegrationTest extends SpecTestCase { private static final List EXTENSIONS = Arrays.asList( AutolinkExtension.create(), + InsExtension.create(), StrikethroughExtension.create(), TablesExtension.create(), YamlFrontMatterExtension.create()); diff --git a/pom.xml b/pom.xml index ee87aefcf..e5bc1c9b8 100644 --- a/pom.xml +++ b/pom.xml @@ -92,6 +92,11 @@ commonmark-ext-autolink 0.6.1-SNAPSHOT + + com.atlassian.commonmark + commonmark-ext-ins + 0.6.1-SNAPSHOT + com.atlassian.commonmark commonmark-ext-gfm-strikethrough From 0430c8b6c5f7f6b2762a854bf2529b6b4914bdeb Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 3 Aug 2016 16:30:15 +0300 Subject: [PATCH 164/815] Add text content renderer --- .../content/TextContentRenderer.java | 253 ++++++++++++++++++ .../commonmark/content/TextContentWriter.java | 67 +++++ .../org/commonmark/content/package-info.java | 4 + .../test/TextContentRendererTest.java | 149 +++++++++++ .../test/TextContentWriterTest.java | 55 ++++ 5 files changed, 528 insertions(+) create mode 100644 commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java create mode 100644 commonmark/src/main/java/org/commonmark/content/TextContentWriter.java create mode 100644 commonmark/src/main/java/org/commonmark/content/package-info.java create mode 100644 commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java create mode 100644 commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java diff --git a/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java new file mode 100644 index 000000000..4d81ab0a1 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java @@ -0,0 +1,253 @@ +package org.commonmark.content; + +import org.commonmark.node.*; + +public class TextContentRenderer { + private final boolean stripNewlines; + + private int orderedListCounter = 0; + + private TextContentRenderer(Builder builder) { + this.stripNewlines = builder.stripNewlines; + } + + /** + * Create a new builder for configuring an {@link TextContentRenderer}. + * + * @return a builder + */ + public static Builder builder() { + return new Builder(); + } + + public void render(Node node, Appendable output) { + RendererVisitor rendererVisitor = new RendererVisitor(new TextContentWriter(output)); + node.accept(rendererVisitor); + } + + /** + * Render the tree of nodes to text content. + * + * @param node the root node + * @return the rendered text content + */ + public String render(Node node) { + StringBuilder sb = new StringBuilder(); + render(node, sb); + return sb.toString(); + } + + /** + * Builder for configuring an {@link TextContentRenderer}. See methods for default configuration. + */ + public static class Builder { + + private boolean stripNewlines = false; + + /** + * @return the configured {@link TextContentRenderer} + */ + public TextContentRenderer build() { + return new TextContentRenderer(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} + */ + public Builder stripNewlines(boolean stripNewlines) { + this.stripNewlines = stripNewlines; + return this; + } + } + + private class RendererVisitor extends AbstractVisitor { + private final TextContentWriter textContent; + + public RendererVisitor(TextContentWriter textContentWriter) { + textContent = textContentWriter; + } + + @Override + public void visit(BlockQuote blockQuote) { + textContent.write('«'); + visitChildren(blockQuote); + textContent.write('»'); + + writeEndOfLine(blockQuote, null); + } + + @Override + public void visit(BulletList bulletList) { + visitChildren(bulletList); + writeEndOfLine(bulletList, null); + } + + @Override + public void visit(Code code) { + textContent.write('\"'); + textContent.write(code.getLiteral()); + textContent.write('\"'); + } + + @Override + public void visit(FencedCodeBlock fencedCodeBlock) { + if (stripNewlines) { + textContent.writeStripped(fencedCodeBlock.getLiteral()); + writeEndOfLine(fencedCodeBlock, null); + } else { + textContent.write(fencedCodeBlock.getLiteral()); + } + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + writeEndOfLine(hardLineBreak, null); + } + + @Override + public void visit(Heading heading) { + visitChildren(heading); + writeEndOfLine(heading, ':'); + } + + @Override + public void visit(ThematicBreak thematicBreak) { + if (!stripNewlines) { + textContent.write("***"); + } + writeEndOfLine(thematicBreak, null); + } + + @Override + public void visit(HtmlInline htmlInline) { + writeText(htmlInline.getLiteral()); + } + + @Override + public void visit(HtmlBlock htmlBlock) { + writeText(htmlBlock.getLiteral()); + } + + @Override + public void visit(Image image) { + writeLink(image, image.getTitle(), image.getDestination()); + } + + @Override + public void visit(IndentedCodeBlock indentedCodeBlock) { + if (stripNewlines) { + textContent.writeStripped(indentedCodeBlock.getLiteral()); + writeEndOfLine(indentedCodeBlock, null); + } else { + textContent.write(indentedCodeBlock.getLiteral()); + } + } + + @Override + public void visit(Link link) { + writeLink(link, link.getTitle(), link.getDestination()); + } + + @Override + public void visit(ListItem listItem) { + if (orderedListCounter > 0) { + textContent.write(String.valueOf(orderedListCounter) + ". "); + visitChildren(listItem); + writeEndOfLine(listItem, null); + orderedListCounter++; + } else { + if (!stripNewlines) { + textContent.write("- "); + } + visitChildren(listItem); + writeEndOfLine(listItem, null); + } + } + + @Override + public void visit(OrderedList orderedList) { + orderedListCounter = 1; + visitChildren(orderedList); + writeEndOfLine(orderedList, null); + orderedListCounter = 0; + } + + @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) { + writeEndOfLine(paragraph, null); + } + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + writeEndOfLine(softLineBreak, null); + } + + @Override + public void visit(Text text) { + writeText(text.getLiteral()); + } + + private void writeText(String text) { + if (stripNewlines) { + textContent.writeStripped(text); + } else { + textContent.write(text); + } + } + + private void writeLink(Node node, String title, String destination) { + boolean hasChild = node.getFirstChild() != null; + boolean hasTitle = title != null; + boolean hasDestination = destination != null && !destination.equals(""); + + if (hasChild) { + textContent.write('"'); + visitChildren(node); + textContent.write('"'); + if (hasTitle || hasDestination) { + textContent.whitespace(); + textContent.write('('); + } + } + + if (hasTitle) { + textContent.write(title); + if (hasDestination) { + textContent.colon(); + textContent.whitespace(); + } + } + + if (hasDestination) { + textContent.write(destination); + } + + if (hasChild && (hasTitle || hasDestination)) { + textContent.write(')'); + } + } + + private void writeEndOfLine(Node node, Character c) { + if (stripNewlines) { + if (c != null) { + textContent.write(c); + } + if (node.getNext() != null) { + textContent.whitespace(); + } + } else { + if (node.getNext() != null) { + textContent.line(); + } + } + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java b/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java new file mode 100644 index 000000000..90c6cdc07 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java @@ -0,0 +1,67 @@ +package org.commonmark.content; + +import java.io.IOException; + +public class TextContentWriter { + + private final Appendable buffer; + + private char lastChar; + + public TextContentWriter(Appendable out) { + buffer = out; + } + + public void whitespace() { + if (lastChar != 0 && lastChar != ' ') { + append(' '); + } + } + + public void colon() { + if (lastChar != 0 && lastChar != ':') { + append(':'); + } + } + + public void line() { + if (lastChar != 0 && lastChar != '\n') { + append('\n'); + } + } + + public void writeStripped(String s) { + append(s.replaceAll("[\\r\\n\\s]+", " ").trim()); + } + + public void write(String s) { + append(s); + } + + public void write(char c) { + append(c); + } + + private void append(String s) { + try { + buffer.append(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) { + try { + buffer.append(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + + lastChar = c; + } +} diff --git a/commonmark/src/main/java/org/commonmark/content/package-info.java b/commonmark/src/main/java/org/commonmark/content/package-info.java new file mode 100644 index 000000000..414b8f5b7 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/content/package-info.java @@ -0,0 +1,4 @@ +/** + * Text content rendering (see {@link org.commonmark.content.TextContentRenderer}) + */ +package org.commonmark.content; diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java new file mode 100644 index 000000000..6e980d8ad --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -0,0 +1,149 @@ +package org.commonmark.test; + +import org.commonmark.content.TextContentRenderer; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TextContentRendererTest { + + @Test + public void textContentQuotes() { + String rendered; + + rendered = defaultRenderer().render(parse("foo\n>foo\nbar\n\nbar")); + assertEquals("foo\n«foo\nbar»\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo\n>foo\nbar\n\nbar")); + assertEquals("foo «foo bar» bar", rendered); + } + + @Test + public void textContentLinks() { + String rendered; + + rendered = defaultRenderer().render(parse("foo [text](http://link \"title\") bar")); + assertEquals("foo \"text\" (title: http://link) bar", rendered); + + rendered = defaultRenderer().render(parse("foo [text](http://link) bar")); + assertEquals("foo \"text\" (http://link) bar", rendered); + + rendered = defaultRenderer().render(parse("foo [text]() bar")); + assertEquals("foo \"text\" bar", rendered); + + rendered = defaultRenderer().render(parse("foo http://link bar")); + assertEquals("foo http://link bar", rendered); + } + + @Test + public void textContentImages() { + String rendered; + + rendered = defaultRenderer().render(parse("foo ![text](http://link \"title\") bar")); + assertEquals("foo \"text\" (title: http://link) bar", rendered); + + rendered = defaultRenderer().render(parse("foo ![text](http://link) bar")); + assertEquals("foo \"text\" (http://link) bar", rendered); + + rendered = defaultRenderer().render(parse("foo ![text]() bar")); + assertEquals("foo \"text\" bar", rendered); + } + + @Test + public void textContentLists() { + String rendered; + + rendered = defaultRenderer().render(parse("foo\n* foo\n* bar\n\nbar")); + assertEquals("foo\n- foo\n- bar\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo\n* foo\n* bar\n\nbar")); + assertEquals("foo foo bar bar", rendered); + + rendered = defaultRenderer().render(parse("foo\n1. foo\n2. bar\n\nbar")); + assertEquals("foo\n1. foo\n2. bar\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo\n1. foo\n2. bar\n\nbar")); + assertEquals("foo 1. foo 2. bar bar", rendered); + } + + @Test + public void textContentCode() { + String rendered; + + rendered = defaultRenderer().render(parse("foo `code` bar")); + assertEquals("foo \"code\" bar", rendered); + } + + @Test + public void textContentCodeBlock() { + String rendered; + + rendered = defaultRenderer().render(parse("foo\n```\nfoo\nbar\n```\nbar")); + assertEquals("foo\nfoo\nbar\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo\n```\nfoo\nbar\n```\nbar")); + assertEquals("foo foo bar bar", rendered); + + rendered = defaultRenderer().render(parse("foo\n\n foo\n bar\nbar")); + assertEquals("foo\nfoo\n bar\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo\n\n foo\n bar\nbar")); + assertEquals("foo foo bar bar", rendered); + } + + @Test + public void textContentBrakes() { + String rendered; + + rendered = defaultRenderer().render(parse("foo\nbar")); + assertEquals("foo\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo\nbar")); + assertEquals("foo bar", rendered); + + rendered = defaultRenderer().render(parse("foo \nbar")); + assertEquals("foo\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo \nbar")); + assertEquals("foo bar", rendered); + + rendered = defaultRenderer().render(parse("foo\n___\nbar")); + assertEquals("foo\n***\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo\n___\nbar")); + assertEquals("foo bar", rendered); + } + + @Test + public void textContentHtml() { + String rendered; + + String html = "\n" + + " \n" + + " \n" + + " \n" + + "
    \n" + + " foobar\n" + + "
    "; + rendered = defaultRenderer().render(parse(html)); + assertEquals(html, rendered); + + html = "foo foobar bar"; + rendered = defaultRenderer().render(parse(html)); + assertEquals(html, rendered); + } + + private TextContentRenderer defaultRenderer() { + return TextContentRenderer.builder().build(); + } + + private TextContentRenderer strippedRenderer() { + return TextContentRenderer.builder().stripNewlines(true).build(); + } + + private Node parse(String source) { + return Parser.builder().build().parse(source); + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java new file mode 100644 index 000000000..a0bf41b18 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java @@ -0,0 +1,55 @@ +package org.commonmark.test; + +import org.commonmark.content.TextContentWriter; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class TextContentWriterTest { + + @Test + public void whitespace() throws Exception { + StringBuilder stringBuilder = new StringBuilder(); + TextContentWriter writer = new TextContentWriter(stringBuilder); + writer.write("foo"); + writer.whitespace(); + writer.write("bar"); + assertEquals("foo bar", stringBuilder.toString()); + } + + @Test + public void colon() throws Exception { + StringBuilder stringBuilder = new StringBuilder(); + TextContentWriter writer = new TextContentWriter(stringBuilder); + writer.write("foo"); + writer.colon(); + writer.write("bar"); + assertEquals("foo:bar", stringBuilder.toString()); + } + + @Test + public void line() throws Exception { + StringBuilder stringBuilder = new StringBuilder(); + TextContentWriter writer = new TextContentWriter(stringBuilder); + writer.write("foo"); + writer.line(); + writer.write("bar"); + assertEquals("foo\nbar", stringBuilder.toString()); + } + + @Test + public void writeStripped() throws Exception { + StringBuilder stringBuilder = new StringBuilder(); + TextContentWriter writer = new TextContentWriter(stringBuilder); + writer.writeStripped("foo\n bar\n"); + assertEquals("foo bar", stringBuilder.toString()); + } + + @Test + public void write() throws Exception { + StringBuilder stringBuilder = new StringBuilder(); + TextContentWriter writer = new TextContentWriter(stringBuilder); + writer.writeStripped("foo bar"); + assertEquals("foo bar", stringBuilder.toString()); + } +} From 32771fa2cbd5f96809fa64329417e4e8a25fb588 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 14 Sep 2016 15:56:31 +0300 Subject: [PATCH 165/815] Improve support of lists --- .../content/TextContentRenderer.java | 21 ++++++++++++------- .../test/TextContentRendererTest.java | 9 ++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java index 4d81ab0a1..95ddd9082 100644 --- a/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java @@ -5,7 +5,10 @@ public class TextContentRenderer { private final boolean stripNewlines; - private int orderedListCounter = 0; + private Integer orderedListCounter; + private Character orderedListDelimiter; + + private Character bulletListMarker; private TextContentRenderer(Builder builder) { this.stripNewlines = builder.stripNewlines; @@ -82,8 +85,10 @@ public void visit(BlockQuote blockQuote) { @Override public void visit(BulletList bulletList) { + bulletListMarker = bulletList.getBulletMarker(); visitChildren(bulletList); writeEndOfLine(bulletList, null); + bulletListMarker = null; } @Override @@ -154,14 +159,14 @@ public void visit(Link link) { @Override public void visit(ListItem listItem) { - if (orderedListCounter > 0) { - textContent.write(String.valueOf(orderedListCounter) + ". "); + if (orderedListCounter != null) { + textContent.write(String.valueOf(orderedListCounter) + orderedListDelimiter + " "); visitChildren(listItem); writeEndOfLine(listItem, null); orderedListCounter++; - } else { + } else if (bulletListMarker != null) { if (!stripNewlines) { - textContent.write("- "); + textContent.write(bulletListMarker + " "); } visitChildren(listItem); writeEndOfLine(listItem, null); @@ -170,10 +175,12 @@ public void visit(ListItem listItem) { @Override public void visit(OrderedList orderedList) { - orderedListCounter = 1; + orderedListCounter = orderedList.getStartNumber(); + orderedListDelimiter = orderedList.getDelimiter(); visitChildren(orderedList); writeEndOfLine(orderedList, null); - orderedListCounter = 0; + orderedListCounter = null; + orderedListDelimiter = null; } @Override diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 6e980d8ad..afebc8ab8 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -56,6 +56,9 @@ public void textContentLists() { String rendered; rendered = defaultRenderer().render(parse("foo\n* foo\n* bar\n\nbar")); + assertEquals("foo\n* foo\n* bar\nbar", rendered); + + rendered = defaultRenderer().render(parse("foo\n- foo\n- bar\n\nbar")); assertEquals("foo\n- foo\n- bar\nbar", rendered); rendered = strippedRenderer().render(parse("foo\n* foo\n* bar\n\nbar")); @@ -64,8 +67,14 @@ public void textContentLists() { rendered = defaultRenderer().render(parse("foo\n1. foo\n2. bar\n\nbar")); assertEquals("foo\n1. foo\n2. bar\nbar", rendered); + rendered = defaultRenderer().render(parse("foo\n0) foo\n1) bar\n\nbar")); + assertEquals("foo\n0) foo\n1) bar\nbar", rendered); + rendered = strippedRenderer().render(parse("foo\n1. foo\n2. bar\n\nbar")); assertEquals("foo 1. foo 2. bar bar", rendered); + + rendered = strippedRenderer().render(parse("foo\n0) foo\n1) bar\n\nbar")); + assertEquals("foo 0) foo 1) bar bar", rendered); } @Test From 40ac153e78191711fd19ed51ba8b13fb0657b090 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 14 Sep 2016 16:46:25 +0300 Subject: [PATCH 166/815] Refactor HtmlRenderer for code reuse in TextContentRenderer --- .../org/commonmark/html/HtmlRenderer.java | 78 +++++++------------ .../java/org/commonmark/html/HtmlWriter.java | 3 +- ...odeRenderer.java => HtmlNodeRenderer.java} | 9 ++- .../renderer/HtmlNodeRendererContext.java | 35 +++++++++ .../renderer/HtmlNodeRendererFactory.java | 6 ++ .../html/renderer/NodeRendererContext.java | 50 ------------ .../renderer/BaseNodeRendererContext.java | 31 ++++++++ .../org/commonmark/renderer/BaseRenderer.java | 28 +++++++ .../{html => }/renderer/NodeRenderer.java | 2 +- .../renderer/NodeRendererContext.java | 22 ++++++ .../renderer/NodeRendererFactory.java | 7 +- .../org/commonmark/renderer/Renderer.java | 22 ++++++ .../java/org/commonmark/renderer/Writer.java | 4 + .../test/DelimiterProcessorTest.java | 13 ++-- .../org/commonmark/test/HtmlRendererTest.java | 12 +-- 15 files changed, 201 insertions(+), 121 deletions(-) rename commonmark/src/main/java/org/commonmark/html/renderer/{CoreNodeRenderer.java => HtmlNodeRenderer.java} (96%) create mode 100644 commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java create mode 100644 commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java delete mode 100644 commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/BaseNodeRendererContext.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/BaseRenderer.java rename commonmark/src/main/java/org/commonmark/{html => }/renderer/NodeRenderer.java (92%) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/NodeRendererContext.java rename commonmark/src/main/java/org/commonmark/{html => }/renderer/NodeRendererFactory.java (68%) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/Renderer.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/Writer.java diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index d36742680..d65933f0a 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -1,16 +1,22 @@ package org.commonmark.html; import org.commonmark.Extension; -import org.commonmark.html.renderer.CoreNodeRenderer; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; -import org.commonmark.html.renderer.NodeRendererFactory; +import org.commonmark.html.renderer.HtmlNodeRenderer; +import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.html.renderer.HtmlNodeRendererFactory; import org.commonmark.internal.util.Escaping; import org.commonmark.node.HtmlBlock; import org.commonmark.node.HtmlInline; import org.commonmark.node.Node; +import org.commonmark.renderer.BaseRenderer; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.NodeRendererContext; +import org.commonmark.renderer.NodeRendererFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; /** * Renders a tree of nodes to HTML. @@ -21,13 +27,13 @@ * renderer.render(node); * */ -public class HtmlRenderer { +public class HtmlRenderer extends BaseRenderer { private final String softbreak; private final boolean escapeHtml; private final boolean percentEncodeUrls; private final List attributeProviders; - private final List nodeRendererFactories; + private final List> nodeRendererFactories; private HtmlRenderer(Builder builder) { this.softbreak = builder.softbreak; @@ -38,10 +44,10 @@ private HtmlRenderer(Builder builder) { this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); this.nodeRendererFactories.addAll(builder.nodeRendererFactories); // Add as last. This means clients can override the rendering of core nodes if they want. - this.nodeRendererFactories.add(new NodeRendererFactory() { + this.nodeRendererFactories.add(new HtmlNodeRendererFactory() { @Override - public NodeRenderer create(NodeRendererContext context) { - return new CoreNodeRenderer(context); + public NodeRenderer create(HtmlNodeRendererContext context) { + return new HtmlNodeRenderer(context); } }); } @@ -55,21 +61,9 @@ public static Builder builder() { return new Builder(); } - public void render(Node node, Appendable output) { - MainNodeRenderer renderer = new MainNodeRenderer(new HtmlWriter(output)); - renderer.render(node); - } - - /** - * Render the tree of nodes to HTML. - * - * @param node the root node - * @return the rendered HTML - */ - public String render(Node node) { - StringBuilder sb = new StringBuilder(); - render(node, sb); - return sb.toString(); + @Override + public NodeRendererContext createContext(Appendable out) { + return new RendererContext(new HtmlWriter(out)); } /** @@ -81,7 +75,7 @@ public static class Builder { private boolean escapeHtml = false; private boolean percentEncodeUrls = false; private List attributeProviders = new ArrayList<>(); - private List nodeRendererFactories = new ArrayList<>(); + private List> nodeRendererFactories = new ArrayList<>(); /** * @return the configured {@link HtmlRenderer} @@ -160,7 +154,7 @@ public Builder attributeProvider(AttributeProvider attributeProvider) { * @param nodeRendererFactory the factory for creating a node renderer * @return {@code this} */ - public Builder nodeRendererFactory(NodeRendererFactory nodeRendererFactory) { + public Builder nodeRendererFactory(HtmlNodeRendererFactory nodeRendererFactory) { this.nodeRendererFactories.add(nodeRendererFactory); return this; } @@ -187,24 +181,18 @@ public interface HtmlRendererExtension extends Extension { void extend(Builder rendererBuilder); } - private class MainNodeRenderer implements NodeRendererContext { + private class RendererContext extends HtmlNodeRendererContext { private final HtmlWriter htmlWriter; - private final Map, NodeRenderer> renderers; - private MainNodeRenderer(HtmlWriter htmlWriter) { + private RendererContext(HtmlWriter htmlWriter) { this.htmlWriter = htmlWriter; - this.renderers = new HashMap<>(32); - - // The first node renderer for a node type "wins". - for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { - NodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); - NodeRenderer nodeRenderer = nodeRendererFactory.create(this); - for (Class nodeType : nodeRenderer.getNodeTypes()) { - // Overwrite existing renderer - renderers.put(nodeType, nodeRenderer); - } + + List renderers = new ArrayList<>(nodeRendererFactories.size()); + for (NodeRendererFactory nodeRendererFactory : nodeRendererFactories) { + renderers.add(nodeRendererFactory.create(this)); } + addNodeRenderers(renderers); } @Override @@ -229,7 +217,7 @@ public Map extendAttributes(Node node, Map attri } @Override - public HtmlWriter getHtmlWriter() { + public HtmlWriter getWriter() { return htmlWriter; } @@ -238,14 +226,6 @@ public String getSoftbreak() { return softbreak; } - @Override - public void render(Node node) { - NodeRenderer nodeRenderer = renderers.get(node.getClass()); - if (nodeRenderer != null) { - nodeRenderer.render(node); - } - } - private void setCustomAttributes(Node node, Map attrs) { for (AttributeProvider attributeProvider : attributeProviders) { attributeProvider.setAttributes(node, attrs); diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java index e69881601..0677f740a 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java @@ -1,12 +1,13 @@ package org.commonmark.html; import org.commonmark.internal.util.Escaping; +import org.commonmark.renderer.Writer; import java.io.IOException; import java.util.Collections; import java.util.Map; -public class HtmlWriter { +public class HtmlWriter implements Writer { private static final Map NO_ATTRIBUTES = Collections.emptyMap(); diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/CoreNodeRenderer.java b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRenderer.java similarity index 96% rename from commonmark/src/main/java/org/commonmark/html/renderer/CoreNodeRenderer.java rename to commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRenderer.java index 51ad18e89..cdb91befe 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/CoreNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRenderer.java @@ -2,20 +2,21 @@ import org.commonmark.html.HtmlWriter; import org.commonmark.node.*; +import org.commonmark.renderer.NodeRenderer; import java.util.*; /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). */ -public class CoreNodeRenderer extends AbstractVisitor implements NodeRenderer { +public class HtmlNodeRenderer extends AbstractVisitor implements NodeRenderer { - protected final NodeRendererContext context; + protected final HtmlNodeRendererContext context; private final HtmlWriter html; - public CoreNodeRenderer(NodeRendererContext context) { + public HtmlNodeRenderer(HtmlNodeRendererContext context) { this.context = context; - this.html = context.getHtmlWriter(); + this.html = context.getWriter(); } @Override diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java new file mode 100644 index 000000000..9b633c36d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java @@ -0,0 +1,35 @@ +package org.commonmark.html.renderer; + +import org.commonmark.html.HtmlWriter; +import org.commonmark.node.Node; +import org.commonmark.renderer.BaseNodeRendererContext; + +import java.util.Map; + +public abstract class HtmlNodeRendererContext extends BaseNodeRendererContext { + + /** + * @param url to be encoded + * @return an encoded URL (depending on the configuration) + */ + public abstract String encodeUrl(String url); + + /** + * Extend the attributes by extensions. + * + * @param node the node for which the attributes are applied + * @param attributes the attributes that were calculated by the renderer + * @return the extended attributes with added/updated/removed entries + */ + public abstract Map extendAttributes(Node node, Map attributes); + + /** + * @return HTML that should be rendered for a soft line break + */ + public abstract String getSoftbreak(); + + /** + * @return whether HTML blocks and tags should be escaped or not + */ + public abstract boolean shouldEscapeHtml(); +} diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java new file mode 100644 index 000000000..8467b3722 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java @@ -0,0 +1,6 @@ +package org.commonmark.html.renderer; + +import org.commonmark.renderer.NodeRendererFactory; + +public interface HtmlNodeRendererFactory extends NodeRendererFactory { +} diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java deleted file mode 100644 index 9b24c1240..000000000 --- a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.commonmark.html.renderer; - -import org.commonmark.html.HtmlWriter; -import org.commonmark.node.Node; - -import java.util.Map; - -/** - * The context for node rendering, including configuration and functionality for the node renderer to use. - */ -public interface NodeRendererContext { - - /** - * @param url to be encoded - * @return an encoded URL (depending on the configuration) - */ - String encodeUrl(String url); - - /** - * Extend the attributes by extensions. - * - * @param node the node for which the attributes are applied - * @param attributes the attributes that were calculated by the renderer - * @return the extended attributes with added/updated/removed entries - */ - Map extendAttributes(Node node, Map attributes); - - /** - * @return the HTML writer to use - */ - HtmlWriter getHtmlWriter(); - - /** - * @return HTML that should be rendered for a soft line break - */ - String getSoftbreak(); - - /** - * Render the specified node and its children using the configured renderers. This should be used to render child - * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. - * - * @param node the node to render - */ - void render(Node node); - - /** - * @return whether HTML blocks and tags should be escaped or not - */ - boolean shouldEscapeHtml(); -} diff --git a/commonmark/src/main/java/org/commonmark/renderer/BaseNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/BaseNodeRendererContext.java new file mode 100644 index 000000000..69f98be00 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/BaseNodeRendererContext.java @@ -0,0 +1,31 @@ +package org.commonmark.renderer; + +import org.commonmark.node.Node; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class BaseNodeRendererContext implements NodeRendererContext { + + private final Map, NodeRenderer> renderers = new HashMap<>(32); + + @Override + public void render(Node node) { + NodeRenderer nodeRenderer = renderers.get(node.getClass()); + if (nodeRenderer != null) { + nodeRenderer.render(node); + } + } + + protected void addNodeRenderers(List renderers) { + // The first node renderer for a node type "wins". + for (int i = renderers.size() - 1; i >= 0; i--) { + NodeRenderer nodeRenderer = renderers.get(i); + for (Class nodeType : nodeRenderer.getNodeTypes()) { + // Overwrite existing renderer + this.renderers.put(nodeType, nodeRenderer); + } + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/BaseRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/BaseRenderer.java new file mode 100644 index 000000000..d78a741b3 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/BaseRenderer.java @@ -0,0 +1,28 @@ +package org.commonmark.renderer; + +import org.commonmark.node.Node; + +public abstract class BaseRenderer implements Renderer { + + @Override + public void render(Node node, Appendable output) { + NodeRendererContext context = createContext(output); + context.render(node); + } + + + @Override + public String render(Node node) { + StringBuilder sb = new StringBuilder(); + render(node, sb); + return sb.toString(); + } + + /** + * Create context for renderer. + * + * @param out the output for rendering + * @return context for renderer + */ + protected abstract NodeRendererContext createContext(Appendable out); +} diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java similarity index 92% rename from commonmark/src/main/java/org/commonmark/html/renderer/NodeRenderer.java rename to commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java index 472bdc6ce..e2d5ebc96 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/NodeRenderer.java @@ -1,4 +1,4 @@ -package org.commonmark.html.renderer; +package org.commonmark.renderer; import org.commonmark.node.Node; diff --git a/commonmark/src/main/java/org/commonmark/renderer/NodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRendererContext.java new file mode 100644 index 000000000..22e349284 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/NodeRendererContext.java @@ -0,0 +1,22 @@ +package org.commonmark.renderer; + +import org.commonmark.node.Node; + +/** + * The context for node rendering, including configuration and functionality for the node renderer to use. + */ +public interface NodeRendererContext { + + /** + * Render the specified node and its children using the configured renderers. This should be used to render child + * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * + * @param node the node to render + */ + void render(Node node); + + /** + * @return the writer to use + */ + W getWriter(); +} diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRendererFactory.java similarity index 68% rename from commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererFactory.java rename to commonmark/src/main/java/org/commonmark/renderer/NodeRendererFactory.java index 2080b0030..f4f5e7a44 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/NodeRendererFactory.java @@ -1,9 +1,9 @@ -package org.commonmark.html.renderer; +package org.commonmark.renderer; /** * Factory for instantiating new node renderers when rendering is done. */ -public interface NodeRendererFactory { +public interface NodeRendererFactory { /** * Create a new node renderer for the specified rendering context. @@ -11,6 +11,5 @@ public interface NodeRendererFactory { * @param context the context for rendering (normally passed on to the node renderer) * @return a node renderer */ - NodeRenderer create(NodeRendererContext context); - + NodeRenderer create(C context); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/Renderer.java b/commonmark/src/main/java/org/commonmark/renderer/Renderer.java new file mode 100644 index 000000000..e954439a6 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/Renderer.java @@ -0,0 +1,22 @@ +package org.commonmark.renderer; + +import org.commonmark.node.Node; + +public interface Renderer { + + /** + * Render the tree of nodes to output. + * + * @param node the root node + * @param output output for rendering + */ + void render(Node node, Appendable output); + + /** + * Render the tree of nodes to string. + * + * @param node the root node + * @return the rendered string + */ + String render(Node node); +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/Writer.java b/commonmark/src/main/java/org/commonmark/renderer/Writer.java new file mode 100644 index 000000000..8eba55305 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/Writer.java @@ -0,0 +1,4 @@ +package org.commonmark.renderer; + +public interface Writer { +} diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index bd58427c8..631192fb4 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -1,15 +1,16 @@ package org.commonmark.test; import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; -import org.commonmark.html.renderer.NodeRendererFactory; +import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.html.renderer.HtmlNodeRendererFactory; import org.commonmark.node.CustomNode; import org.commonmark.node.Node; import org.commonmark.node.Text; -import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.Parser; +import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.NodeRendererContext; import org.junit.Test; import java.util.Collections; @@ -126,10 +127,10 @@ public void process(Text opener, Text closer, int delimiterUse) { private static class UpperCaseNode extends CustomNode { } - private static class UpperCaseNodeRendererFactory implements NodeRendererFactory { + private static class UpperCaseNodeRendererFactory implements HtmlNodeRendererFactory { @Override - public NodeRenderer create(NodeRendererContext context) { + public NodeRenderer create(HtmlNodeRendererContext context) { return new UpperCaseNodeRenderer(context); } } diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 6a77f8a0a..6d2bbff14 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -2,14 +2,14 @@ import org.commonmark.html.AttributeProvider; import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; -import org.commonmark.html.renderer.NodeRendererFactory; +import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.html.renderer.HtmlNodeRendererFactory; import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.Image; import org.commonmark.node.Link; import org.commonmark.node.Node; import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; import org.junit.Test; import java.util.Collections; @@ -125,9 +125,9 @@ public void setAttributes(Node node, Map attributes) { @Test public void overrideNodeRender() { - NodeRendererFactory nodeRendererFactory = new NodeRendererFactory() { + HtmlNodeRendererFactory nodeRendererFactory = new HtmlNodeRendererFactory() { @Override - public NodeRenderer create(final NodeRendererContext context) { + public NodeRenderer create(final HtmlNodeRendererContext context) { return new NodeRenderer() { @Override public Set> getNodeTypes() { @@ -136,7 +136,7 @@ public Set> getNodeTypes() { @Override public void render(Node node) { - context.getHtmlWriter().text("test"); + context.getWriter().text("test"); } }; } From 37dcfe3e8e3a6adb54496c637429f1f44dd5ecc9 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 14 Sep 2016 16:47:29 +0300 Subject: [PATCH 167/815] Support extensions in TextContentRenderer --- .../content/TextContentRenderer.java | 268 +++++------------- .../commonmark/content/TextContentWriter.java | 4 +- .../renderer/TextContentNodeRenderer.java | 258 +++++++++++++++++ .../TextContentNodeRendererContext.java | 13 + .../TextContentNodeRendererFactory.java | 6 + .../test/TextContentRendererTest.java | 11 + 6 files changed, 363 insertions(+), 197 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRenderer.java create mode 100644 commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java create mode 100644 commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java diff --git a/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java index 95ddd9082..989406446 100644 --- a/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java @@ -1,17 +1,34 @@ package org.commonmark.content; -import org.commonmark.node.*; - -public class TextContentRenderer { +import org.commonmark.Extension; +import org.commonmark.content.renderer.TextContentNodeRenderer; +import org.commonmark.content.renderer.TextContentNodeRendererContext; +import org.commonmark.content.renderer.TextContentNodeRendererFactory; +import org.commonmark.renderer.BaseRenderer; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.NodeRendererContext; +import org.commonmark.renderer.NodeRendererFactory; + +import java.util.ArrayList; +import java.util.List; + +public class TextContentRenderer extends BaseRenderer { private final boolean stripNewlines; - private Integer orderedListCounter; - private Character orderedListDelimiter; - - private Character bulletListMarker; + private final List> nodeRendererFactories; private TextContentRenderer(Builder builder) { this.stripNewlines = builder.stripNewlines; + + this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); + this.nodeRendererFactories.addAll(builder.nodeRendererFactories); + // Add as last. This means clients can override the rendering of core nodes if they want. + this.nodeRendererFactories.add(new TextContentNodeRendererFactory() { + @Override + public NodeRenderer create(TextContentNodeRendererContext context) { + return new TextContentNodeRenderer(context); + } + }); } /** @@ -23,21 +40,9 @@ public static Builder builder() { return new Builder(); } - public void render(Node node, Appendable output) { - RendererVisitor rendererVisitor = new RendererVisitor(new TextContentWriter(output)); - node.accept(rendererVisitor); - } - - /** - * Render the tree of nodes to text content. - * - * @param node the root node - * @return the rendered text content - */ - public String render(Node node) { - StringBuilder sb = new StringBuilder(); - render(node, sb); - return sb.toString(); + @Override + protected NodeRendererContext createContext(Appendable out) { + return new RendererContext(new TextContentWriter(out)); } /** @@ -46,6 +51,7 @@ public String render(Node node) { public static class Builder { private boolean stripNewlines = false; + private List> nodeRendererFactories = new ArrayList<>(); /** * @return the configured {@link TextContentRenderer} @@ -65,196 +71,66 @@ public Builder stripNewlines(boolean stripNewlines) { this.stripNewlines = stripNewlines; return this; } - } - - private class RendererVisitor extends AbstractVisitor { - private final TextContentWriter textContent; - - public RendererVisitor(TextContentWriter textContentWriter) { - textContent = textContentWriter; - } - - @Override - public void visit(BlockQuote blockQuote) { - textContent.write('«'); - visitChildren(blockQuote); - textContent.write('»'); - - writeEndOfLine(blockQuote, null); - } - - @Override - public void visit(BulletList bulletList) { - bulletListMarker = bulletList.getBulletMarker(); - visitChildren(bulletList); - writeEndOfLine(bulletList, null); - bulletListMarker = null; - } - - @Override - public void visit(Code code) { - textContent.write('\"'); - textContent.write(code.getLiteral()); - textContent.write('\"'); - } - @Override - public void visit(FencedCodeBlock fencedCodeBlock) { - if (stripNewlines) { - textContent.writeStripped(fencedCodeBlock.getLiteral()); - writeEndOfLine(fencedCodeBlock, null); - } else { - textContent.write(fencedCodeBlock.getLiteral()); - } - } - - @Override - public void visit(HardLineBreak hardLineBreak) { - writeEndOfLine(hardLineBreak, null); - } - - @Override - public void visit(Heading heading) { - visitChildren(heading); - writeEndOfLine(heading, ':'); - } - - @Override - public void visit(ThematicBreak thematicBreak) { - if (!stripNewlines) { - textContent.write("***"); - } - writeEndOfLine(thematicBreak, null); - } - - @Override - public void visit(HtmlInline htmlInline) { - writeText(htmlInline.getLiteral()); - } - - @Override - public void visit(HtmlBlock htmlBlock) { - writeText(htmlBlock.getLiteral()); - } - - @Override - public void visit(Image image) { - writeLink(image, image.getTitle(), image.getDestination()); + /** + * 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(TextContentNodeRendererFactory nodeRendererFactory) { + this.nodeRendererFactories.add(nodeRendererFactory); + return this; } - @Override - public void visit(IndentedCodeBlock indentedCodeBlock) { - if (stripNewlines) { - textContent.writeStripped(indentedCodeBlock.getLiteral()); - writeEndOfLine(indentedCodeBlock, null); - } else { - textContent.write(indentedCodeBlock.getLiteral()); + /** + * @param extensions extensions to use on this text content renderer + * @return {@code this} + */ + public Builder extensions(Iterable extensions) { + for (Extension extension : extensions) { + if (extension instanceof TextContentRenderer.TextContentRendererExtension) { + TextContentRenderer.TextContentRendererExtension htmlRendererExtension = + (TextContentRenderer.TextContentRendererExtension) extension; + htmlRendererExtension.extend(this); + } } + return this; } + } - @Override - public void visit(Link link) { - writeLink(link, link.getTitle(), link.getDestination()); - } + /** + * Extension for {@link TextContentRenderer}. + */ + public interface TextContentRendererExtension extends Extension { + void extend(TextContentRenderer.Builder rendererBuilder); + } - @Override - public void visit(ListItem listItem) { - if (orderedListCounter != null) { - textContent.write(String.valueOf(orderedListCounter) + orderedListDelimiter + " "); - visitChildren(listItem); - writeEndOfLine(listItem, null); - orderedListCounter++; - } else if (bulletListMarker != null) { - if (!stripNewlines) { - textContent.write(bulletListMarker + " "); - } - visitChildren(listItem); - writeEndOfLine(listItem, null); - } - } + private class RendererContext extends TextContentNodeRendererContext { + private final TextContentWriter textContentWriter; - @Override - public void visit(OrderedList orderedList) { - orderedListCounter = orderedList.getStartNumber(); - orderedListDelimiter = orderedList.getDelimiter(); - visitChildren(orderedList); - writeEndOfLine(orderedList, null); - orderedListCounter = null; - orderedListDelimiter = null; - } + private RendererContext(TextContentWriter textContentWriter) { + this.textContentWriter = textContentWriter; - @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) { - writeEndOfLine(paragraph, null); + List renderers = new ArrayList<>(nodeRendererFactories.size()); + for (NodeRendererFactory nodeRendererFactory : nodeRendererFactories) { + renderers.add(nodeRendererFactory.create(this)); } + addNodeRenderers(renderers); } @Override - public void visit(SoftLineBreak softLineBreak) { - writeEndOfLine(softLineBreak, null); + public boolean stripNewlines() { + return stripNewlines; } @Override - public void visit(Text text) { - writeText(text.getLiteral()); - } - - private void writeText(String text) { - if (stripNewlines) { - textContent.writeStripped(text); - } else { - textContent.write(text); - } - } - - private void writeLink(Node node, String title, String destination) { - boolean hasChild = node.getFirstChild() != null; - boolean hasTitle = title != null; - boolean hasDestination = destination != null && !destination.equals(""); - - if (hasChild) { - textContent.write('"'); - visitChildren(node); - textContent.write('"'); - if (hasTitle || hasDestination) { - textContent.whitespace(); - textContent.write('('); - } - } - - if (hasTitle) { - textContent.write(title); - if (hasDestination) { - textContent.colon(); - textContent.whitespace(); - } - } - - if (hasDestination) { - textContent.write(destination); - } - - if (hasChild && (hasTitle || hasDestination)) { - textContent.write(')'); - } - } - - private void writeEndOfLine(Node node, Character c) { - if (stripNewlines) { - if (c != null) { - textContent.write(c); - } - if (node.getNext() != null) { - textContent.whitespace(); - } - } else { - if (node.getNext() != null) { - textContent.line(); - } - } + public TextContentWriter getWriter() { + return textContentWriter; } } } diff --git a/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java b/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java index 90c6cdc07..852d759db 100644 --- a/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java @@ -1,8 +1,10 @@ package org.commonmark.content; +import org.commonmark.renderer.Writer; + import java.io.IOException; -public class TextContentWriter { +public class TextContentWriter implements Writer { private final Appendable buffer; diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRenderer.java new file mode 100644 index 000000000..3323cfbfe --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRenderer.java @@ -0,0 +1,258 @@ +package org.commonmark.content.renderer; + +import org.commonmark.content.TextContentWriter; +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 TextContentNodeRenderer extends AbstractVisitor implements NodeRenderer { + + protected final TextContentNodeRendererContext context; + private final TextContentWriter textContent; + + private Integer orderedListCounter; + private Character orderedListDelimiter; + + private Character bulletListMarker; + + public TextContentNodeRenderer(TextContentNodeRendererContext context) { + this.context = context; + this.textContent = 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) { + textContent.write('«'); + visitChildren(blockQuote); + textContent.write('»'); + + writeEndOfLine(blockQuote, null); + } + + @Override + public void visit(BulletList bulletList) { + bulletListMarker = bulletList.getBulletMarker(); + visitChildren(bulletList); + writeEndOfLine(bulletList, null); + bulletListMarker = null; + } + + @Override + public void visit(Code code) { + textContent.write('\"'); + textContent.write(code.getLiteral()); + textContent.write('\"'); + } + + @Override + public void visit(FencedCodeBlock fencedCodeBlock) { + if (context.stripNewlines()) { + textContent.writeStripped(fencedCodeBlock.getLiteral()); + writeEndOfLine(fencedCodeBlock, null); + } else { + textContent.write(fencedCodeBlock.getLiteral()); + } + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + writeEndOfLine(hardLineBreak, null); + } + + @Override + public void visit(Heading heading) { + visitChildren(heading); + writeEndOfLine(heading, ':'); + } + + @Override + public void visit(ThematicBreak thematicBreak) { + if (!context.stripNewlines()) { + textContent.write("***"); + } + writeEndOfLine(thematicBreak, null); + } + + @Override + public void visit(HtmlInline htmlInline) { + writeText(htmlInline.getLiteral()); + } + + @Override + public void visit(HtmlBlock htmlBlock) { + writeText(htmlBlock.getLiteral()); + } + + @Override + public void visit(Image image) { + writeLink(image, image.getTitle(), image.getDestination()); + } + + @Override + public void visit(IndentedCodeBlock indentedCodeBlock) { + if (context.stripNewlines()) { + textContent.writeStripped(indentedCodeBlock.getLiteral()); + writeEndOfLine(indentedCodeBlock, null); + } else { + textContent.write(indentedCodeBlock.getLiteral()); + } + } + + @Override + public void visit(Link link) { + writeLink(link, link.getTitle(), link.getDestination()); + } + + @Override + public void visit(ListItem listItem) { + if (orderedListCounter != null) { + textContent.write(String.valueOf(orderedListCounter) + orderedListDelimiter + " "); + visitChildren(listItem); + writeEndOfLine(listItem, null); + orderedListCounter++; + } else if (bulletListMarker != null) { + if (!context.stripNewlines()) { + textContent.write(bulletListMarker + " "); + } + visitChildren(listItem); + writeEndOfLine(listItem, null); + } + } + + @Override + public void visit(OrderedList orderedList) { + orderedListCounter = orderedList.getStartNumber(); + orderedListDelimiter = orderedList.getDelimiter(); + visitChildren(orderedList); + writeEndOfLine(orderedList, null); + orderedListCounter = null; + orderedListDelimiter = null; + } + + @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) { + writeEndOfLine(paragraph, null); + } + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + writeEndOfLine(softLineBreak, null); + } + + @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) { + if (context.stripNewlines()) { + textContent.writeStripped(text); + } else { + textContent.write(text); + } + } + + private void writeLink(Node node, String title, String destination) { + boolean hasChild = node.getFirstChild() != null; + boolean hasTitle = title != null; + boolean hasDestination = destination != null && !destination.equals(""); + + if (hasChild) { + textContent.write('"'); + visitChildren(node); + textContent.write('"'); + if (hasTitle || hasDestination) { + textContent.whitespace(); + textContent.write('('); + } + } + + if (hasTitle) { + textContent.write(title); + if (hasDestination) { + textContent.colon(); + textContent.whitespace(); + } + } + + if (hasDestination) { + textContent.write(destination); + } + + if (hasChild && (hasTitle || hasDestination)) { + textContent.write(')'); + } + } + + private void writeEndOfLine(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(); + } + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java new file mode 100644 index 000000000..fc2dffb70 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java @@ -0,0 +1,13 @@ +package org.commonmark.content.renderer; + +import org.commonmark.content.TextContentWriter; +import org.commonmark.renderer.BaseNodeRendererContext; + +public abstract class TextContentNodeRendererContext extends BaseNodeRendererContext { + + /** + * @return true for stripping new lines and render text as "single line", + * false for keeping all line breaks. + */ + public abstract boolean stripNewlines(); +} diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java new file mode 100644 index 000000000..5a633b565 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java @@ -0,0 +1,6 @@ +package org.commonmark.content.renderer; + +import org.commonmark.renderer.NodeRendererFactory; + +public interface TextContentNodeRendererFactory extends NodeRendererFactory { +} diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index afebc8ab8..7a6b6dca8 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -9,6 +9,17 @@ public class TextContentRendererTest { + @Test + public void textContentEmphasis() { + String rendered; + + rendered = defaultRenderer().render(parse("foo\n***foo***\nbar\n\n***bar***")); + assertEquals("foo\nfoo\nbar\nbar", rendered); + + rendered = strippedRenderer().render(parse("foo\n***foo\nbar***\n\n***bar***")); + assertEquals("foo foo bar bar", rendered); + } + @Test public void textContentQuotes() { String rendered; From bd1ad06eae83e23c270e8599ef06601a521cd0f6 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 14 Sep 2016 16:48:30 +0300 Subject: [PATCH 168/815] Support TextContentRenderer in StrikethroughExtension --- .../strikethrough/StrikethroughExtension.java | 33 ++++++++++++++----- .../StrikethroughHtmlNodeRenderer.java | 28 ++++++++++++++++ .../internal/StrikethroughNodeRenderer.java | 20 +++-------- .../StrikethroughTextContentNodeRenderer.java | 22 +++++++++++++ .../gfm/strikethrough/StrikethroughTest.java | 13 ++++++-- 5 files changed, 89 insertions(+), 27 deletions(-) create mode 100644 commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java create mode 100644 commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java 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 7827cd472..86fde5e69 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,13 +1,17 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.Extension; +import org.commonmark.content.TextContentRenderer; +import org.commonmark.content.renderer.TextContentNodeRendererContext; +import org.commonmark.content.renderer.TextContentNodeRendererFactory; import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughDelimiterProcessor; -import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughNodeRenderer; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; -import org.commonmark.html.renderer.NodeRendererFactory; -import org.commonmark.parser.Parser; +import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughHtmlNodeRenderer; +import org.commonmark.ext.gfm.strikethrough.internal.StrikethroughTextContentNodeRenderer; import org.commonmark.html.HtmlRenderer; +import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.html.renderer.HtmlNodeRendererFactory; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; /** * Extension for GFM strikethrough using ~~ (GitHub Flavored Markdown). @@ -20,7 +24,8 @@ * The parsed strikethrough text regions are turned into {@link Strikethrough} nodes. *

    */ -public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, + TextContentRenderer.TextContentRendererExtension { private StrikethroughExtension() { } @@ -36,10 +41,20 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new NodeRendererFactory() { + rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { + @Override + public NodeRenderer create(HtmlNodeRendererContext context) { + return new StrikethroughHtmlNodeRenderer(context); + } + }); + } + + @Override + public void extend(TextContentRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { @Override - public NodeRenderer create(NodeRendererContext context) { - return new StrikethroughNodeRenderer(context); + public NodeRenderer create(TextContentNodeRendererContext context) { + return new StrikethroughTextContentNodeRenderer(context); } }); } 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 new file mode 100644 index 000000000..c11c2053f --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughHtmlNodeRenderer.java @@ -0,0 +1,28 @@ +package org.commonmark.ext.gfm.strikethrough.internal; + +import org.commonmark.html.HtmlWriter; +import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.node.Node; + +import java.util.Collections; +import java.util.Map; + +public class StrikethroughHtmlNodeRenderer extends StrikethroughNodeRenderer { + + private final HtmlNodeRendererContext context; + private final HtmlWriter html; + + public StrikethroughHtmlNodeRenderer(HtmlNodeRendererContext context) { + super(context); + this.context = context; + this.html = context.getWriter(); + } + + @Override + public void render(Node node) { + Map attributes = context.extendAttributes(node, Collections.emptyMap()); + 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 21aa115b6..9fa6f611a 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughNodeRenderer.java @@ -1,23 +1,19 @@ package org.commonmark.ext.gfm.strikethrough.internal; import org.commonmark.ext.gfm.strikethrough.Strikethrough; -import org.commonmark.html.HtmlWriter; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.NodeRendererContext; import java.util.Collections; -import java.util.Map; import java.util.Set; -public class StrikethroughNodeRenderer implements NodeRenderer { +abstract class StrikethroughNodeRenderer implements NodeRenderer { private final NodeRendererContext context; - private final HtmlWriter html; public StrikethroughNodeRenderer(NodeRendererContext context) { this.context = context; - this.html = context.getHtmlWriter(); } @Override @@ -25,15 +21,7 @@ public Set> getNodeTypes() { return Collections.>singleton(Strikethrough.class); } - @Override - public void render(Node node) { - Map attributes = context.extendAttributes(node, Collections.emptyMap()); - html.tag("del", attributes); - renderChildren(node); - html.tag("/del"); - } - - private void renderChildren(Node parent) { + protected void renderChildren(Node parent) { Node node = parent.getFirstChild(); while (node != null) { Node next = node.getNext(); diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java new file mode 100644 index 000000000..fd79e765d --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java @@ -0,0 +1,22 @@ +package org.commonmark.ext.gfm.strikethrough.internal; + +import org.commonmark.content.TextContentWriter; +import org.commonmark.content.renderer.TextContentNodeRendererContext; +import org.commonmark.node.Node; + +public class StrikethroughTextContentNodeRenderer extends StrikethroughNodeRenderer { + + private final TextContentWriter textContent; + + public StrikethroughTextContentNodeRenderer(TextContentNodeRendererContext context) { + super(context); + this.textContent = context.getWriter(); + } + + @Override + public void render(Node node) { + textContent.write('/'); + renderChildren(node); + textContent.write('/'); + } +} 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 70b7e4a33..54658a95d 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java @@ -1,6 +1,7 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.Extension; +import org.commonmark.content.TextContentRenderer; import org.commonmark.html.HtmlRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; @@ -16,7 +17,9 @@ public class StrikethroughTest extends RenderingTestCase { private static final Set EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer HTML_RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer CONTENT_RENDERER = TextContentRenderer.builder() + .extensions(EXTENSIONS).build(); @Test public void oneTildeIsNotEnough() { @@ -80,8 +83,14 @@ public void delimited() { assertEquals("~~", strikethrough.getClosingDelimiter()); } + @Test + public void textContentRenderer() { + Node document = PARSER.parse("~~foo~~"); + assertEquals("/foo/", CONTENT_RENDERER.render(document)); + } + @Override protected String render(String source) { - return RENDERER.render(PARSER.parse(source)); + return HTML_RENDERER.render(PARSER.parse(source)); } } From 93b0d32bce5bb5dba659119abec071491f4bcc24 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 14 Sep 2016 16:49:00 +0300 Subject: [PATCH 169/815] Update TablesExtension --- .../commonmark/ext/gfm/tables/TablesExtension.java | 12 ++++++------ .../ext/gfm/tables/internal/TableNodeRenderer.java | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) 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 064441167..d8f878b7d 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,11 +3,11 @@ import org.commonmark.Extension; import org.commonmark.ext.gfm.tables.internal.TableBlockParser; import org.commonmark.ext.gfm.tables.internal.TableNodeRenderer; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; -import org.commonmark.html.renderer.NodeRendererFactory; -import org.commonmark.parser.Parser; import org.commonmark.html.HtmlRenderer; +import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.html.renderer.HtmlNodeRendererFactory; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; /** * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). @@ -36,9 +36,9 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new NodeRendererFactory() { + rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { @Override - public NodeRenderer create(NodeRendererContext context) { + public NodeRenderer create(HtmlNodeRendererContext context) { return new TableNodeRenderer(context); } }); 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 429798360..9b9ee548e 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 @@ -2,19 +2,19 @@ import org.commonmark.ext.gfm.tables.*; import org.commonmark.html.HtmlWriter; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.html.renderer.HtmlNodeRendererContext; import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; import java.util.*; public class TableNodeRenderer implements NodeRenderer { private final HtmlWriter htmlWriter; - private final NodeRendererContext context; + private final HtmlNodeRendererContext context; - public TableNodeRenderer(NodeRendererContext context) { - this.htmlWriter = context.getHtmlWriter(); + public TableNodeRenderer(HtmlNodeRendererContext context) { + this.htmlWriter = context.getWriter(); this.context = context; } From bb48e5195dc2c22bcaa103b12aa2de57aa28d1e4 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 14 Sep 2016 17:11:57 +0300 Subject: [PATCH 170/815] Update InsExtension --- .../main/java/org/commonmark/ext/ins/InsExtension.java | 10 +++++----- .../commonmark/ext/ins/internal/InsNodeRenderer.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) 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 edcfb0373..4fd70fda2 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,11 +3,11 @@ import org.commonmark.Extension; import org.commonmark.ext.ins.internal.InsDelimiterProcessor; import org.commonmark.ext.ins.internal.InsNodeRenderer; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; -import org.commonmark.html.renderer.NodeRendererFactory; +import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.html.renderer.HtmlNodeRendererFactory; import org.commonmark.parser.Parser; import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.NodeRenderer; /** * Extension for ins using ++ @@ -36,9 +36,9 @@ public void extend(Parser.Builder parserBuilder) { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new NodeRendererFactory() { + rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { @Override - public NodeRenderer create(NodeRendererContext context) { + public NodeRenderer create(HtmlNodeRendererContext context) { return new InsNodeRenderer(context); } }); diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java index 1830988dd..0b716637c 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 @@ -2,9 +2,9 @@ import org.commonmark.ext.ins.Ins; import org.commonmark.html.HtmlWriter; -import org.commonmark.html.renderer.NodeRenderer; -import org.commonmark.html.renderer.NodeRendererContext; +import org.commonmark.html.renderer.HtmlNodeRendererContext; import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; import java.util.Collections; import java.util.Map; @@ -12,12 +12,12 @@ public class InsNodeRenderer implements NodeRenderer { - private final NodeRendererContext context; + private final HtmlNodeRendererContext context; private final HtmlWriter html; - public InsNodeRenderer(NodeRendererContext context) { + public InsNodeRenderer(HtmlNodeRendererContext context) { this.context = context; - this.html = context.getHtmlWriter(); + this.html = context.getWriter(); } @Override From e46a57b851d6b748a8bf83110af383bc97c95705 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 15 Sep 2016 15:30:32 +1000 Subject: [PATCH 171/815] Add Javadoc for Link nodes To a newcomer, it's not obvious that the link text is in child nodes. --- .../main/java/org/commonmark/node/Link.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/node/Link.java b/commonmark/src/main/java/org/commonmark/node/Link.java index b4794253e..b2ed8c2a1 100644 --- a/commonmark/src/main/java/org/commonmark/node/Link.java +++ b/commonmark/src/main/java/org/commonmark/node/Link.java @@ -1,5 +1,25 @@ package org.commonmark.node; +/** + * A link with a destination and an optional title; the link text is in child nodes. + *

    + * Example for an inline link in a CommonMark document: + *

    
    + * [link](/uri "title")
    + * 
    + *

    + * The corresponding Link node would look like this: + *

      + *
    • {@link #getDestination()} returns {@code "/uri"} + *
    • {@link #getTitle()} returns {@code "title"} + *
    • A {@link Text} child node with {@link Text#getLiteral() getLiteral} that returns {@code "link"}
    • + *
    + *

    + * 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 + */ public class Link extends Node { private String destination; From 87176a2af045145e12435a0b26c1fc6ac49ba0b7 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Thu, 15 Sep 2016 16:31:43 +1000 Subject: [PATCH 172/815] Set version to 0.6.1 --- commonmark-ext-heading-anchor/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 792a9e30e..5c4b109fc 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.4.2-SNAPSHOT + 0.6.1-SNAPSHOT commonmark-ext-heading-anchor From d70584fdc18d9eadcbc142c12f339e7d24ace996 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Thu, 15 Sep 2016 16:37:00 +1000 Subject: [PATCH 173/815] Remove Java 8 string join method --- .../anchor/internal/HeadingIdAttributeProvider.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index 9e3a2d353..617e3ae80 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -44,7 +44,13 @@ public void visit(Code code) { } }); - attributes.put("id", idProvider.getUniqueIdentifier(String.join("", wordList)).toLowerCase()); + String finalString = ""; + for (String word : wordList) { + finalString += word + " "; + } + finalString = finalString.trim().toLowerCase(); + + attributes.put("id", idProvider.getUniqueIdentifier(finalString)); } } From d1bb1bdd5461227bbd20da736ae6b2b998a8675e Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Thu, 15 Sep 2016 16:44:01 +1000 Subject: [PATCH 174/815] Fixing word concatenation to replicate old java 8 implementation --- .../ext/heading/anchor/internal/HeadingIdAttributeProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index 617e3ae80..f16b358fc 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -46,7 +46,7 @@ public void visit(Code code) { String finalString = ""; for (String word : wordList) { - finalString += word + " "; + finalString += word; } finalString = finalString.trim().toLowerCase(); From a3ea6b503d85b02d45a2572a45c00d7213bb9b40 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 20 Sep 2016 09:39:06 +1000 Subject: [PATCH 175/815] Change AttributeProvider to a factory in HtmlRenderer (api break) Each time we render we want a new AttributeProvider to be used, just like with a NodeRenderer. This allows the AttributeProvider to have state and not worry about resetting it. While we're breaking API, move interfaces into a new package. For extensibility, add a AttributeProviderContext similar to NodeRenderContext. --- .../commonmark/ext/gfm/tables/TablesTest.java | 37 ++++++---- .../anchor/HeadingAnchorExtension.java | 13 +++- .../internal/HeadingIdAttributeProvider.java | 2 +- ...aderIdTest.java => HeadingAnchorTest.java} | 19 ++--- .../org/commonmark/html/HtmlRenderer.java | 28 +++++--- .../{ => attribute}/AttributeProvider.java | 2 +- .../attribute/AttributeProviderContext.java | 9 +++ .../attribute/AttributeProviderFactory.java | 15 ++++ .../html/renderer/NodeRendererContext.java | 1 + .../org/commonmark/test/HtmlRendererTest.java | 71 ++++++++++++++----- 10 files changed, 137 insertions(+), 60 deletions(-) rename commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/{HeaderIdTest.java => HeadingAnchorTest.java} (83%) rename commonmark/src/main/java/org/commonmark/html/{ => attribute}/AttributeProvider.java (95%) create mode 100644 commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderContext.java create mode 100644 commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderFactory.java 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 85d84d64d..761cc65d5 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,10 @@ package org.commonmark.ext.gfm.tables; import org.commonmark.Extension; -import org.commonmark.html.AttributeProvider; import org.commonmark.html.HtmlRenderer; +import org.commonmark.html.attribute.AttributeProvider; +import org.commonmark.html.attribute.AttributeProviderContext; +import org.commonmark.html.attribute.AttributeProviderFactory; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; @@ -308,24 +310,29 @@ public void tableEndWithoutEmptyLine() { @Test public void attributeProviderIsApplied() { - AttributeProvider attributeProvider = new AttributeProvider() { + AttributeProviderFactory factory = new AttributeProviderFactory() { @Override - public void setAttributes(Node node, Map attributes) { - if (node instanceof TableBlock) { - attributes.put("test", "block"); - } else if (node instanceof TableHead) { - attributes.put("test", "head"); - } else if (node instanceof TableBody) { - attributes.put("test", "body"); - } else if (node instanceof TableRow) { - attributes.put("test", "row"); - } else if (node instanceof TableCell) { - attributes.put("test", "cell"); - } + public AttributeProvider create(AttributeProviderContext context) { + return new AttributeProvider() { + @Override + public void setAttributes(Node node, Map attributes) { + if (node instanceof TableBlock) { + attributes.put("test", "block"); + } else if (node instanceof TableHead) { + attributes.put("test", "head"); + } else if (node instanceof TableBody) { + attributes.put("test", "body"); + } else if (node instanceof TableRow) { + attributes.put("test", "row"); + } else if (node instanceof TableCell) { + attributes.put("test", "cell"); + } + } + }; } }; HtmlRenderer renderer = HtmlRenderer.builder() - .attributeProvider(attributeProvider) + .attributeProviderFactory(factory) .extensions(EXTENSIONS) .build(); String rendered = renderer.render(PARSER.parse("Abc|Def\n---|---\n1|2")); diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index 86411ca18..6f635a4e1 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -1,9 +1,11 @@ package org.commonmark.ext.heading.anchor; import org.commonmark.Extension; -import org.commonmark.html.HtmlRenderer; - import org.commonmark.ext.heading.anchor.internal.HeadingIdAttributeProvider; +import org.commonmark.html.HtmlRenderer; +import org.commonmark.html.attribute.AttributeProvider; +import org.commonmark.html.attribute.AttributeProviderContext; +import org.commonmark.html.attribute.AttributeProviderFactory; /** * Extension for adding auto generated ids to headings @@ -40,6 +42,11 @@ public static Extension create() { @Override public void extend(HtmlRenderer.Builder rendererBuilder) { - rendererBuilder.attributeProvider(HeadingIdAttributeProvider.create()); + rendererBuilder.attributeProviderFactory(new AttributeProviderFactory() { + @Override + public AttributeProvider create(AttributeProviderContext context) { + return HeadingIdAttributeProvider.create(); + } + }); } } diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index f16b358fc..98eaa4796 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Map; -import org.commonmark.html.AttributeProvider; +import org.commonmark.html.attribute.AttributeProvider; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Code; import org.commonmark.node.Heading; diff --git a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java similarity index 83% rename from commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java rename to commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java index 97c28cc95..671462085 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeaderIdTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java @@ -1,24 +1,19 @@ package org.commonmark.ext.heading.anchor; -import org.commonmark.ext.heading.anchor.internal.HeadingIdAttributeProvider; +import org.commonmark.Extension; import org.commonmark.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; -import org.junit.Before; import org.junit.Test; -public class HeaderIdTest extends RenderingTestCase { +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 Parser PARSER = Parser.builder().build(); - private static final HeadingIdAttributeProvider attributeProvider = HeadingIdAttributeProvider.create(); - private static HtmlRenderer RENDERER = HtmlRenderer.builder().attributeProvider(attributeProvider).build(); - - @Before - public void resetHeader() { - RENDERER = HtmlRenderer.builder() - .attributeProvider(HeadingIdAttributeProvider.create()) - .build(); - } + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); @Test public void baseCaseSingleHeader() { diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index d36742680..4ad8b1386 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -1,6 +1,9 @@ package org.commonmark.html; import org.commonmark.Extension; +import org.commonmark.html.attribute.AttributeProvider; +import org.commonmark.html.attribute.AttributeProviderContext; +import org.commonmark.html.attribute.AttributeProviderFactory; import org.commonmark.html.renderer.CoreNodeRenderer; import org.commonmark.html.renderer.NodeRenderer; import org.commonmark.html.renderer.NodeRendererContext; @@ -26,20 +29,19 @@ public class HtmlRenderer { private final String softbreak; private final boolean escapeHtml; private final boolean percentEncodeUrls; - private final List attributeProviders; + private final List attributeProviderFactories; private final List nodeRendererFactories; private HtmlRenderer(Builder builder) { this.softbreak = builder.softbreak; this.escapeHtml = builder.escapeHtml; this.percentEncodeUrls = builder.percentEncodeUrls; - this.attributeProviders = builder.attributeProviders; + this.attributeProviderFactories = new ArrayList<>(builder.attributeProviderFactories); 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 NodeRendererFactory() { - @Override public NodeRenderer create(NodeRendererContext context) { return new CoreNodeRenderer(context); } @@ -80,7 +82,7 @@ public static class Builder { private String softbreak = "\n"; private boolean escapeHtml = false; private boolean percentEncodeUrls = false; - private List attributeProviders = new ArrayList<>(); + private List attributeProviderFactories = new ArrayList<>(); private List nodeRendererFactories = new ArrayList<>(); /** @@ -140,13 +142,13 @@ public Builder percentEncodeUrls(boolean percentEncodeUrls) { } /** - * Add an attribute provider for adding/changing HTML attributes to the rendered tags. + * Add a factory for an attribute provider for adding/changing HTML attributes to the rendered tags. * - * @param attributeProvider the attribute provider to add + * @param attributeProviderFactory the attribute provider factory to add * @return {@code this} */ - public Builder attributeProvider(AttributeProvider attributeProvider) { - this.attributeProviders.add(attributeProvider); + public Builder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) { + this.attributeProviderFactories.add(attributeProviderFactory); return this; } @@ -187,15 +189,21 @@ public interface HtmlRendererExtension extends Extension { void extend(Builder rendererBuilder); } - private class MainNodeRenderer implements NodeRendererContext { + private class MainNodeRenderer implements NodeRendererContext, AttributeProviderContext { private final HtmlWriter htmlWriter; + private final List attributeProviders; private final Map, NodeRenderer> renderers; private MainNodeRenderer(HtmlWriter htmlWriter) { this.htmlWriter = htmlWriter; - this.renderers = new HashMap<>(32); + attributeProviders = new ArrayList<>(attributeProviderFactories.size()); + for (AttributeProviderFactory attributeProviderFactory : attributeProviderFactories) { + attributeProviders.add(attributeProviderFactory.create(this)); + } + + renderers = new HashMap<>(32); // The first node renderer for a node type "wins". for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { NodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); diff --git a/commonmark/src/main/java/org/commonmark/html/AttributeProvider.java b/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProvider.java similarity index 95% rename from commonmark/src/main/java/org/commonmark/html/AttributeProvider.java rename to commonmark/src/main/java/org/commonmark/html/attribute/AttributeProvider.java index e5f62365d..a76641722 100644 --- a/commonmark/src/main/java/org/commonmark/html/AttributeProvider.java +++ b/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProvider.java @@ -1,4 +1,4 @@ -package org.commonmark.html; +package org.commonmark.html.attribute; import org.commonmark.node.Node; diff --git a/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderContext.java b/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderContext.java new file mode 100644 index 000000000..f60043d06 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderContext.java @@ -0,0 +1,9 @@ +package org.commonmark.html.attribute; + +/** + * The context for attribute providers. + *

    Note: There are currently no methods here, this is for future extensibility.

    + *

    This interface is not intended to be implemented by clients.

    + */ +public interface AttributeProviderContext { +} diff --git a/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderFactory.java b/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderFactory.java new file mode 100644 index 000000000..b60031502 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderFactory.java @@ -0,0 +1,15 @@ +package org.commonmark.html.attribute; + +/** + * Factory for instantiating new attribute providers when rendering is done. + */ +public interface AttributeProviderFactory { + + /** + * Create a new attribute provider. + * + * @param context for this attribute provider + * @return an AttributeProvider + */ + AttributeProvider create(AttributeProviderContext context); +} diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java index 9b24c1240..0bac7bd80 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/html/renderer/NodeRendererContext.java @@ -7,6 +7,7 @@ /** * The context for node rendering, including configuration and functionality for the node renderer to use. + *

    This interface is not intended to be implemented by clients.

    */ public interface NodeRendererContext { diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 6a77f8a0a..9df773b2c 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -1,7 +1,9 @@ package org.commonmark.test; -import org.commonmark.html.AttributeProvider; import org.commonmark.html.HtmlRenderer; +import org.commonmark.html.attribute.AttributeProvider; +import org.commonmark.html.attribute.AttributeProviderContext; +import org.commonmark.html.attribute.AttributeProviderFactory; import org.commonmark.html.renderer.NodeRenderer; import org.commonmark.html.renderer.NodeRendererContext; import org.commonmark.html.renderer.NodeRendererFactory; @@ -85,20 +87,25 @@ public void percentEncodeUrl() { @Test public void attributeProviderForCodeBlock() { - AttributeProvider custom = new AttributeProvider() { + AttributeProviderFactory custom = new AttributeProviderFactory() { @Override - public void setAttributes(Node node, Map attributes) { - if (node instanceof FencedCodeBlock) { - FencedCodeBlock fencedCodeBlock = (FencedCodeBlock) node; - // Remove the default attribute for info - attributes.remove("class"); - // Put info in custom attribute instead - attributes.put("data-custom", fencedCodeBlock.getInfo()); - } + public AttributeProvider create(AttributeProviderContext context) { + return new AttributeProvider() { + @Override + public void setAttributes(Node node, Map attributes) { + if (node instanceof FencedCodeBlock) { + FencedCodeBlock fencedCodeBlock = (FencedCodeBlock) node; + // Remove the default attribute for info + attributes.remove("class"); + // Put info in custom attribute instead + attributes.put("data-custom", fencedCodeBlock.getInfo()); + } + } + }; } }; - HtmlRenderer renderer = HtmlRenderer.builder().attributeProvider(custom).build(); + HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(custom).build(); String rendered = renderer.render(parse("```info\ncontent\n```")); assertEquals("
    content\n
    \n", rendered); @@ -108,21 +115,49 @@ public void setAttributes(Node node, Map attributes) { @Test public void attributeProviderForImage() { - AttributeProvider custom = new AttributeProvider() { + AttributeProviderFactory custom = new AttributeProviderFactory() { @Override - public void setAttributes(Node node, Map attributes) { - if (node instanceof Image) { - attributes.remove("alt"); - attributes.put("test", "hey"); - } + public AttributeProvider create(AttributeProviderContext context) { + return new AttributeProvider() { + @Override + public void setAttributes(Node node, Map attributes) { + if (node instanceof Image) { + attributes.remove("alt"); + attributes.put("test", "hey"); + } + } + }; } }; - HtmlRenderer renderer = HtmlRenderer.builder().attributeProvider(custom).build(); + HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(custom).build(); String rendered = renderer.render(parse("![foo](/url)\n")); assertEquals("

    \n", rendered); } + @Test + public void attributeProviderFactoryNewInstanceForEachRender() { + AttributeProviderFactory factory = new AttributeProviderFactory() { + @Override + public AttributeProvider create(AttributeProviderContext context) { + return new AttributeProvider() { + int i = 0; + + @Override + public void setAttributes(Node node, Map attributes) { + attributes.put("key", "" + i); + i++; + } + }; + } + }; + + HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(factory).build(); + String rendered = renderer.render(parse("text node")); + String secondPass = renderer.render(parse("text node")); + assertEquals(rendered, secondPass); + } + @Test public void overrideNodeRender() { NodeRendererFactory nodeRendererFactory = new NodeRendererFactory() { From 35e569db05a1b424390ff2f06b2d0cd8b7fd3d51 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 20 Sep 2016 10:05:59 +1000 Subject: [PATCH 176/815] Rename class, add a builder and extend documentation a bit --- .../anchor/HeadingAnchorExtension.java | 4 +- .../ext/heading/anchor/IdGenerator.java | 116 ++++++++++++++++++ .../anchor/UniqueIdentifierProvider.java | 115 ----------------- .../internal/HeadingIdAttributeProvider.java | 20 ++- .../src/main/javadoc/overview.html | 5 +- 5 files changed, 129 insertions(+), 131 deletions(-) create mode 100644 commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java delete mode 100644 commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/UniqueIdentifierProvider.java diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index 6f635a4e1..08f6ac200 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -8,7 +8,7 @@ import org.commonmark.html.attribute.AttributeProviderFactory; /** - * Extension for adding auto generated ids to headings + * Extension for adding auto generated IDs to headings. *

    * Create it with {@link #create()} and then configure it on the builder * {@link org.commonmark.html.HtmlRenderer.Builder#extensions(Iterable)}). @@ -30,6 +30,8 @@ * <h1 id="heading-1">Heading</h1> * * + * + * @see IdGenerator the IdGenerator class if just the ID generation part is needed */ public class HeadingAnchorExtension implements HtmlRenderer.HtmlRendererExtension { diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java new file mode 100644 index 000000000..eb8df6336 --- /dev/null +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java @@ -0,0 +1,116 @@ +package org.commonmark.ext.heading.anchor; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Generates strings to be used as identifiers. + *

    + * Use {@link #builder()} to create an instance. + */ +public class IdGenerator { + private final Pattern allowedCharacters = Pattern.compile("[\\w\\-_]+", Pattern.UNICODE_CHARACTER_CLASS); + private final Map identityMap; + private String defaultIdentifier; + + private IdGenerator(Builder builder) { + this.defaultIdentifier = builder.defaultIdentifier; + this.identityMap = new HashMap<>(); + } + + /** + * @return a new builder with default arguments + */ + public static Builder builder() { + return new Builder(); + } + + /** + *

    + * Generate an ID based on the provided text and previously generated IDs. + *

    + *

    + * This method is not thread safe, concurrent calls can end up + * with non-unique identifiers. + *

    + *

    + * Note that collision can occur in the case that + *

      + *
    • Method called with 'X'
    • + *
    • Method called with 'X' again
    • + *
    • Method called with 'X-1'
    • + *
    + *

    + *

    + * In that case, the three generated IDs will be: + *

      + *
    • X
    • + *
    • X-1
    • + *
    • X-1
    • + *
    + *

    + *

    + * Therefore if collisions are unacceptable you should ensure that + * numbers are stripped from end of {@code text}. + *

    + * + * @param text Text that the identifier should be based on. Will be normalised, then used to generate the + * identifier. + * @return {@code text} if this is the first instance that the {@code text} has been passed + * to the method. Otherwise, {@code text + "-" + X} will be returned, where X is the number of times + * that {@code text} has previously been passed in. If {@code text} is empty, the default + * identifier given in the constructor will be used. + */ + public String generateId(String text) { + String normalizedIdentity = text != null ? normalizeText(text) : defaultIdentifier; + + if (normalizedIdentity.length() == 0) { + normalizedIdentity = defaultIdentifier; + } + + if (!identityMap.containsKey(normalizedIdentity)) { + identityMap.put(normalizedIdentity, 1); + return normalizedIdentity; + } else { + int currentCount = identityMap.get(normalizedIdentity); + identityMap.put(normalizedIdentity, currentCount + 1); + return normalizedIdentity + "-" + currentCount; + } + } + + /** + * Assume we've been given a space separated text. + * + * @param text Text to normalize to an ID + */ + private String normalizeText(String text) { + String firstPassNormalising = text.toLowerCase().replace(" ", "-"); + + StringBuilder sb = new StringBuilder(); + Matcher matcher = allowedCharacters.matcher(firstPassNormalising); + + while (matcher.find()) { + sb.append(matcher.group()); + } + + return sb.toString(); + } + + public static class Builder { + private String defaultIdentifier = "id"; + + public IdGenerator build() { + return new IdGenerator(this); + } + + /** + * @param defaultId the default identifier to use in case the provided text is empty or only contains unusable characters + * @return {@code this} + */ + public Builder defaultId(String defaultId) { + return this; + } + } +} diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/UniqueIdentifierProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/UniqueIdentifierProvider.java deleted file mode 100644 index 09247674e..000000000 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/UniqueIdentifierProvider.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.commonmark.ext.heading.anchor; - -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Provider of unique strings to be used as an identifier - */ -public class UniqueIdentifierProvider { - private final Pattern allowedCharacters = Pattern.compile("[\\w\\-_]+", Pattern.UNICODE_CHARACTER_CLASS); - private final Map identityMap; - private String defaultIdentifier; - - /** - * Create a provider with the default identifier of "id" - */ - public UniqueIdentifierProvider() { - this("id"); - } - - /** - * @param defaultIdentifier default identifier to use if given a null, or empty {@code providedIdentity} - * in {@link #getUniqueIdentifier(String)} - */ - public UniqueIdentifierProvider(String defaultIdentifier) { - this.defaultIdentifier = defaultIdentifier; - this.identityMap = new HashMap<>(); - } - - public String getDefaultIdentifier() { - return defaultIdentifier; - } - - public void setDefaultIdentifier(String defaultIdentifier) { - this.defaultIdentifier = defaultIdentifier; - } - - /** - *

    - * Provides unique strings over the lifetime of the instance - *

    - *

    - * This method is not thread safe, concurrent calls can end up - * with non-unique identifiers - *

    - *

    - * Note that collision can occur in the case that - *

      - *
    • String 'X' provided to getUniqueIdentifier
    • - *
    • String 'X' provided again to getUniqueIdentifier
    • - *
    • String 'X-1' provided to getUniqueIdentifier
    • - *
    - *

    - *

    - * In that case, the three ids provided will be - *

      - *
    • X
    • - *
    • X-1
    • - *
    • X-1
    • - *
    - *

    - *

    - * Therefore if collisions are unnacceptable you should ensure that - * numbers are stripped from end of {@code providedIdentity} - *

    - * - * @param providedIdentity tentative identifier to be used. Will be normalised, then used to generate the - * identifier. - * Becomes the basis for the returned identifier. - * @return {@code providedIdentity} if this is the first instance that the {@code providedIdentity} has been passed - * to the method. Otherwise, {@code providedIdentity + "-" + X} will be returned, where X is the number of times - * that {@code providedIdentity} has previously been passed in. If {@code providedIdentity} is empty, the default - * identifier given in the constructor (or "id" if none given) will be used. - */ - public String getUniqueIdentifier(String providedIdentity) { - String normalizedIdentity = - providedIdentity != null ? - normalizeProvidedIdentity(providedIdentity) : defaultIdentifier; - - if (normalizedIdentity.trim().length() == 0) { - normalizedIdentity = defaultIdentifier; - } - - if (!identityMap.containsKey(normalizedIdentity)) { - identityMap.put(normalizedIdentity, 1); - return normalizedIdentity; - } - else { - int currentCount = identityMap.get(normalizedIdentity); - identityMap.put(normalizedIdentity, currentCount + 1); - return normalizedIdentity + "-" + currentCount; - } - } - - /** - * Assume we've been given a space separated - * - * @param providedIdentity - */ - private String normalizeProvidedIdentity(String providedIdentity) { - String firstPassNormalising = providedIdentity.toLowerCase() - .replace(" ", "-"); - - StringBuilder sb = new StringBuilder(); - Matcher matcher = allowedCharacters.matcher(firstPassNormalising); - - while (matcher.find()) { - sb.append(matcher.group()); - } - - return sb.toString(); - } -} diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index 98eaa4796..bce777a4d 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -1,24 +1,19 @@ package org.commonmark.ext.heading.anchor.internal; +import org.commonmark.ext.heading.anchor.IdGenerator; +import org.commonmark.html.attribute.AttributeProvider; +import org.commonmark.node.*; + import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.commonmark.html.attribute.AttributeProvider; -import org.commonmark.node.AbstractVisitor; -import org.commonmark.node.Code; -import org.commonmark.node.Heading; -import org.commonmark.node.Node; -import org.commonmark.node.Text; - -import org.commonmark.ext.heading.anchor.UniqueIdentifierProvider; - public class HeadingIdAttributeProvider implements AttributeProvider { - private final UniqueIdentifierProvider idProvider; + private final IdGenerator idGenerator; private HeadingIdAttributeProvider() { - idProvider = new UniqueIdentifierProvider("heading"); + idGenerator = IdGenerator.builder().defaultId("heading").build(); } public static HeadingIdAttributeProvider create() { @@ -50,8 +45,7 @@ public void visit(Code code) { } finalString = finalString.trim().toLowerCase(); - attributes.put("id", idProvider.getUniqueIdentifier(finalString)); + attributes.put("id", idGenerator.generateId(finalString)); } } - } diff --git a/commonmark-ext-heading-anchor/src/main/javadoc/overview.html b/commonmark-ext-heading-anchor/src/main/javadoc/overview.html index cae7fdd09..4a64323eb 100644 --- a/commonmark-ext-heading-anchor/src/main/javadoc/overview.html +++ b/commonmark-ext-heading-anchor/src/main/javadoc/overview.html @@ -1,6 +1,7 @@ -Extension for automatically adding Id attributes to all headers -

    See {@link org.commonmark.ext.heading.anchor.HeadingAnchorExtension

    +Extension for automatically adding {@code id} attributes to all headers +

    See {@link org.commonmark.ext.heading.anchor.HeadingAnchorExtension} or use + {@link org.commonmark.ext.heading.anchor.IdGenerator} directly.

    From 8d9a48f32db66f20322b9feb92fe20da88eef55f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 20 Sep 2016 10:13:52 +1000 Subject: [PATCH 177/815] Add link to CommonMark Dingus --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b76ee18f..970bc7fa4 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,10 @@ 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. -See the [spec.txt](commonmark-test-util/src/main/resources/spec.txt) file if -you're wondering which version of the spec is currently implemented. +See the [spec.txt](commonmark-test-util/src/main/resources/spec.txt) +file if you're wondering which version of the spec is currently +implemented. Also check out the [CommonMark dingus] for getting familiar +with the syntax or trying out edge cases. [![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) [![Coverage status](https://coveralls.io/repos/github/atlassian/commonmark-java/badge.svg?branch=master)](https://coveralls.io/github/atlassian/commonmark-java?branch=master) @@ -214,6 +216,7 @@ BSD (2-clause) licensed, see LICENSE.txt file. [CommonMark]: http://commonmark.org/ [Markdown]: https://daringfireball.net/projects/markdown/ [commonmark.js]: https://github.com/jgm/commonmark.js +[CommonMark Dingus]: http://spec.commonmark.org/dingus/ [Maven Central]: https://search.maven.org/#search|ga|1|g%3A%22com.atlassian.commonmark%22 [Semantic Versioning]: http://semver.org/ [autolink-java]: https://github.com/robinst/autolink-java From 43c7e28a7cd32d5fb5a4fb367f4bf7dd7371d627 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 20 Sep 2016 16:56:23 +1000 Subject: [PATCH 178/815] Share fewer interfaces/base classes between HTML and text rendering Even though the two are similar for now, I don't want to expose too much in the API, especially the base classes and the generics. So for now, separate the two a bit more and in turn force a tiny bit of duplication on extensions. --- .../StrikethroughHtmlNodeRenderer.java | 10 +++- .../internal/StrikethroughNodeRenderer.java | 16 ------- .../StrikethroughTextContentNodeRenderer.java | 12 ++++- .../content/TextContentRenderer.java | 46 +++++++++++++------ .../commonmark/content/TextContentWriter.java | 4 +- ....java => CoreTextContentNodeRenderer.java} | 6 +-- .../TextContentNodeRendererContext.java | 19 ++++++-- .../TextContentNodeRendererFactory.java | 15 +++++- .../org/commonmark/html/HtmlRenderer.java | 44 ++++++++++++------ .../java/org/commonmark/html/HtmlWriter.java | 3 +- ...enderer.java => CoreHtmlNodeRenderer.java} | 4 +- .../renderer/HtmlNodeRendererContext.java | 26 ++++++++--- .../renderer/HtmlNodeRendererFactory.java | 15 +++++- .../internal/renderer/NodeRendererMap.java | 26 +++++++++++ .../renderer/BaseNodeRendererContext.java | 31 ------------- .../org/commonmark/renderer/BaseRenderer.java | 28 ----------- .../renderer/NodeRendererContext.java | 23 ---------- .../renderer/NodeRendererFactory.java | 15 ------ .../org/commonmark/renderer/Renderer.java | 2 +- .../java/org/commonmark/renderer/Writer.java | 4 -- .../test/DelimiterProcessorTest.java | 5 +- 21 files changed, 177 insertions(+), 177 deletions(-) rename commonmark/src/main/java/org/commonmark/content/renderer/{TextContentNodeRenderer.java => CoreTextContentNodeRenderer.java} (97%) rename commonmark/src/main/java/org/commonmark/html/renderer/{HtmlNodeRenderer.java => CoreHtmlNodeRenderer.java} (98%) create mode 100644 commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java delete mode 100644 commonmark/src/main/java/org/commonmark/renderer/BaseNodeRendererContext.java delete mode 100644 commonmark/src/main/java/org/commonmark/renderer/BaseRenderer.java delete mode 100644 commonmark/src/main/java/org/commonmark/renderer/NodeRendererContext.java delete mode 100644 commonmark/src/main/java/org/commonmark/renderer/NodeRendererFactory.java delete mode 100644 commonmark/src/main/java/org/commonmark/renderer/Writer.java 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 c11c2053f..07e92271b 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 @@ -13,7 +13,6 @@ public class StrikethroughHtmlNodeRenderer extends StrikethroughNodeRenderer { private final HtmlWriter html; public StrikethroughHtmlNodeRenderer(HtmlNodeRendererContext context) { - super(context); this.context = context; this.html = context.getWriter(); } @@ -25,4 +24,13 @@ public void render(Node node) { renderChildren(node); html.tag("/del"); } + + 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/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 9fa6f611a..4f3a12618 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 @@ -3,30 +3,14 @@ import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.NodeRendererContext; import java.util.Collections; import java.util.Set; abstract class StrikethroughNodeRenderer implements NodeRenderer { - private final NodeRendererContext context; - - public StrikethroughNodeRenderer(NodeRendererContext context) { - this.context = context; - } - @Override public Set> getNodeTypes() { return Collections.>singleton(Strikethrough.class); } - - protected 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/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java index fd79e765d..5a1f9d332 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java @@ -6,10 +6,11 @@ public class StrikethroughTextContentNodeRenderer extends StrikethroughNodeRenderer { + private final TextContentNodeRendererContext context; private final TextContentWriter textContent; public StrikethroughTextContentNodeRenderer(TextContentNodeRendererContext context) { - super(context); + this.context = context; this.textContent = context.getWriter(); } @@ -19,4 +20,13 @@ public void render(Node node) { renderChildren(node); textContent.write('/'); } + + 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/src/main/java/org/commonmark/content/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java index 989406446..4ff3a82b5 100644 --- a/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java @@ -1,21 +1,22 @@ package org.commonmark.content; import org.commonmark.Extension; -import org.commonmark.content.renderer.TextContentNodeRenderer; +import org.commonmark.content.renderer.CoreTextContentNodeRenderer; import org.commonmark.content.renderer.TextContentNodeRendererContext; import org.commonmark.content.renderer.TextContentNodeRendererFactory; -import org.commonmark.renderer.BaseRenderer; +import org.commonmark.internal.renderer.NodeRendererMap; +import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.NodeRendererContext; -import org.commonmark.renderer.NodeRendererFactory; +import org.commonmark.renderer.Renderer; import java.util.ArrayList; import java.util.List; -public class TextContentRenderer extends BaseRenderer { +public class TextContentRenderer implements Renderer { + private final boolean stripNewlines; - private final List> nodeRendererFactories; + private final List nodeRendererFactories; private TextContentRenderer(Builder builder) { this.stripNewlines = builder.stripNewlines; @@ -26,7 +27,7 @@ private TextContentRenderer(Builder builder) { this.nodeRendererFactories.add(new TextContentNodeRendererFactory() { @Override public NodeRenderer create(TextContentNodeRendererContext context) { - return new TextContentNodeRenderer(context); + return new CoreTextContentNodeRenderer(context); } }); } @@ -41,8 +42,16 @@ public static Builder builder() { } @Override - protected NodeRendererContext createContext(Appendable out) { - return new RendererContext(new TextContentWriter(out)); + public void render(Node node, Appendable output) { + RendererContext context = new RendererContext(new TextContentWriter(output)); + context.render(node); + } + + @Override + public String render(Node node) { + StringBuilder sb = new StringBuilder(); + render(node, sb); + return sb.toString(); } /** @@ -51,7 +60,7 @@ protected NodeRendererContext createContext(Appendable out) { public static class Builder { private boolean stripNewlines = false; - private List> nodeRendererFactories = new ArrayList<>(); + private List nodeRendererFactories = new ArrayList<>(); /** * @return the configured {@link TextContentRenderer} @@ -110,17 +119,19 @@ public interface TextContentRendererExtension extends Extension { void extend(TextContentRenderer.Builder rendererBuilder); } - private class RendererContext extends TextContentNodeRendererContext { + private class RendererContext implements TextContentNodeRendererContext { private final TextContentWriter textContentWriter; + private final NodeRendererMap nodeRendererMap = new NodeRendererMap(); private RendererContext(TextContentWriter textContentWriter) { this.textContentWriter = textContentWriter; - List renderers = new ArrayList<>(nodeRendererFactories.size()); - for (NodeRendererFactory nodeRendererFactory : nodeRendererFactories) { - renderers.add(nodeRendererFactory.create(this)); + // 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); } - addNodeRenderers(renderers); } @Override @@ -132,5 +143,10 @@ public boolean stripNewlines() { public TextContentWriter getWriter() { return textContentWriter; } + + @Override + public void render(Node node) { + nodeRendererMap.render(node); + } } } diff --git a/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java b/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java index 852d759db..90c6cdc07 100644 --- a/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java @@ -1,10 +1,8 @@ package org.commonmark.content; -import org.commonmark.renderer.Writer; - import java.io.IOException; -public class TextContentWriter implements Writer { +public class TextContentWriter { private final Appendable buffer; diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/content/renderer/CoreTextContentNodeRenderer.java similarity index 97% rename from commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRenderer.java rename to commonmark/src/main/java/org/commonmark/content/renderer/CoreTextContentNodeRenderer.java index 3323cfbfe..79072f45e 100644 --- a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/content/renderer/CoreTextContentNodeRenderer.java @@ -1,8 +1,8 @@ package org.commonmark.content.renderer; -import org.commonmark.content.TextContentWriter; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; +import org.commonmark.content.TextContentWriter; import java.util.Arrays; import java.util.HashSet; @@ -11,7 +11,7 @@ /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). */ -public class TextContentNodeRenderer extends AbstractVisitor implements NodeRenderer { +public class CoreTextContentNodeRenderer extends AbstractVisitor implements NodeRenderer { protected final TextContentNodeRendererContext context; private final TextContentWriter textContent; @@ -21,7 +21,7 @@ public class TextContentNodeRenderer extends AbstractVisitor implements NodeRend private Character bulletListMarker; - public TextContentNodeRenderer(TextContentNodeRendererContext context) { + public CoreTextContentNodeRenderer(TextContentNodeRendererContext context) { this.context = context; this.textContent = context.getWriter(); } diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java index fc2dffb70..0aea1b6cb 100644 --- a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java @@ -1,13 +1,26 @@ package org.commonmark.content.renderer; +import org.commonmark.node.Node; import org.commonmark.content.TextContentWriter; -import org.commonmark.renderer.BaseNodeRendererContext; -public abstract class TextContentNodeRendererContext extends BaseNodeRendererContext { +public interface TextContentNodeRendererContext { /** * @return true for stripping new lines and render text as "single line", * false for keeping all line breaks. */ - public abstract boolean stripNewlines(); + boolean stripNewlines(); + + /** + * @return the writer to use + */ + TextContentWriter getWriter(); + + /** + * Render the specified node and its children using the configured renderers. This should be used to render child + * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * + * @param node the node to render + */ + void render(Node node); } diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java index 5a633b565..c3fbb6611 100644 --- a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java @@ -1,6 +1,17 @@ package org.commonmark.content.renderer; -import org.commonmark.renderer.NodeRendererFactory; +import org.commonmark.renderer.NodeRenderer; -public interface TextContentNodeRendererFactory extends NodeRendererFactory { +/** + * Factory for instantiating new node renderers when rendering is done. + */ +public interface TextContentNodeRendererFactory { + + /** + * 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(TextContentNodeRendererContext context); } diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java index f84fa20b9..ee62d6f21 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java @@ -4,17 +4,16 @@ import org.commonmark.html.attribute.AttributeProvider; import org.commonmark.html.attribute.AttributeProviderContext; import org.commonmark.html.attribute.AttributeProviderFactory; -import org.commonmark.html.renderer.HtmlNodeRenderer; +import org.commonmark.html.renderer.CoreHtmlNodeRenderer; import org.commonmark.html.renderer.HtmlNodeRendererContext; import org.commonmark.html.renderer.HtmlNodeRendererFactory; +import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.internal.util.Escaping; import org.commonmark.node.HtmlBlock; import org.commonmark.node.HtmlInline; import org.commonmark.node.Node; -import org.commonmark.renderer.BaseRenderer; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.NodeRendererContext; -import org.commonmark.renderer.NodeRendererFactory; +import org.commonmark.renderer.Renderer; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -30,13 +29,13 @@ * renderer.render(node); * */ -public class HtmlRenderer extends BaseRenderer { +public class HtmlRenderer implements Renderer { private final String softbreak; private final boolean escapeHtml; private final boolean percentEncodeUrls; private final List attributeProviderFactories; - private final List> nodeRendererFactories; + private final List nodeRendererFactories; private HtmlRenderer(Builder builder) { this.softbreak = builder.softbreak; @@ -50,7 +49,7 @@ private HtmlRenderer(Builder builder) { this.nodeRendererFactories.add(new HtmlNodeRendererFactory() { @Override public NodeRenderer create(HtmlNodeRendererContext context) { - return new HtmlNodeRenderer(context); + return new CoreHtmlNodeRenderer(context); } }); } @@ -65,8 +64,16 @@ public static Builder builder() { } @Override - public NodeRendererContext createContext(Appendable out) { - return new RendererContext(new HtmlWriter(out)); + public void render(Node node, Appendable output) { + RendererContext context = new RendererContext(new HtmlWriter(output)); + context.render(node); + } + + @Override + public String render(Node node) { + StringBuilder sb = new StringBuilder(); + render(node, sb); + return sb.toString(); } /** @@ -78,7 +85,7 @@ public static class Builder { private boolean escapeHtml = false; private boolean percentEncodeUrls = false; private List attributeProviderFactories = new ArrayList<>(); - private List> nodeRendererFactories = new ArrayList<>(); + private List nodeRendererFactories = new ArrayList<>(); /** * @return the configured {@link HtmlRenderer} @@ -184,10 +191,11 @@ public interface HtmlRendererExtension extends Extension { void extend(Builder rendererBuilder); } - private class RendererContext extends HtmlNodeRendererContext implements AttributeProviderContext { + private class RendererContext implements HtmlNodeRendererContext, AttributeProviderContext { private final HtmlWriter htmlWriter; private final List attributeProviders; + private final NodeRendererMap nodeRendererMap = new NodeRendererMap(); private RendererContext(HtmlWriter htmlWriter) { this.htmlWriter = htmlWriter; @@ -197,11 +205,12 @@ private RendererContext(HtmlWriter htmlWriter) { attributeProviders.add(attributeProviderFactory.create(this)); } - List renderers = new ArrayList<>(nodeRendererFactories.size()); - for (NodeRendererFactory nodeRendererFactory : nodeRendererFactories) { - renderers.add(nodeRendererFactory.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); } - addNodeRenderers(renderers); } @Override @@ -235,6 +244,11 @@ public String getSoftbreak() { return softbreak; } + @Override + public void render(Node node) { + nodeRendererMap.render(node); + } + private void setCustomAttributes(Node node, Map attrs) { for (AttributeProvider attributeProvider : attributeProviders) { attributeProvider.setAttributes(node, attrs); diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java index 0677f740a..e69881601 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java @@ -1,13 +1,12 @@ package org.commonmark.html; import org.commonmark.internal.util.Escaping; -import org.commonmark.renderer.Writer; import java.io.IOException; import java.util.Collections; import java.util.Map; -public class HtmlWriter implements Writer { +public class HtmlWriter { private static final Map NO_ATTRIBUTES = Collections.emptyMap(); diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRenderer.java b/commonmark/src/main/java/org/commonmark/html/renderer/CoreHtmlNodeRenderer.java similarity index 98% rename from commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRenderer.java rename to commonmark/src/main/java/org/commonmark/html/renderer/CoreHtmlNodeRenderer.java index cdb91befe..bc71d468a 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/html/renderer/CoreHtmlNodeRenderer.java @@ -9,12 +9,12 @@ /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). */ -public class HtmlNodeRenderer extends AbstractVisitor implements NodeRenderer { +public class CoreHtmlNodeRenderer extends AbstractVisitor implements NodeRenderer { protected final HtmlNodeRendererContext context; private final HtmlWriter html; - public HtmlNodeRenderer(HtmlNodeRendererContext context) { + public CoreHtmlNodeRenderer(HtmlNodeRendererContext context) { this.context = context; this.html = context.getWriter(); } diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java index 9b633c36d..acf086c08 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java @@ -2,34 +2,46 @@ import org.commonmark.html.HtmlWriter; import org.commonmark.node.Node; -import org.commonmark.renderer.BaseNodeRendererContext; import java.util.Map; -public abstract class HtmlNodeRendererContext extends BaseNodeRendererContext { +public interface HtmlNodeRendererContext { /** * @param url to be encoded * @return an encoded URL (depending on the configuration) */ - public abstract String encodeUrl(String url); + String encodeUrl(String url); /** * Extend the attributes by extensions. * - * @param node the node for which the attributes are applied + * @param node the node for which the attributes are applied * @param attributes the attributes that were calculated by the renderer * @return the extended attributes with added/updated/removed entries */ - public abstract Map extendAttributes(Node node, Map attributes); + Map extendAttributes(Node node, Map attributes); + + /** + * @return the HTML writer to use + */ + HtmlWriter getWriter(); /** * @return HTML that should be rendered for a soft line break */ - public abstract String getSoftbreak(); + String getSoftbreak(); + + /** + * Render the specified node and its children using the configured renderers. This should be used to render child + * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. + * + * @param node the node to render + */ + void render(Node node); /** * @return whether HTML blocks and tags should be escaped or not */ - public abstract boolean shouldEscapeHtml(); + boolean shouldEscapeHtml(); } diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java index 8467b3722..66aa97860 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java @@ -1,6 +1,17 @@ package org.commonmark.html.renderer; -import org.commonmark.renderer.NodeRendererFactory; +import org.commonmark.renderer.NodeRenderer; -public interface HtmlNodeRendererFactory extends NodeRendererFactory { +/** + * Factory for instantiating new node renderers when rendering is done. + */ +public interface HtmlNodeRendererFactory { + + /** + * 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(HtmlNodeRendererContext context); } diff --git a/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java b/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java new file mode 100644 index 000000000..e3adaa11f --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/NodeRendererMap.java @@ -0,0 +1,26 @@ +package org.commonmark.internal.renderer; + +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; + +import java.util.HashMap; +import java.util.Map; + +public class NodeRendererMap { + + 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); + } + } + + public void render(Node node) { + NodeRenderer nodeRenderer = renderers.get(node.getClass()); + if (nodeRenderer != null) { + nodeRenderer.render(node); + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/BaseNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/BaseNodeRendererContext.java deleted file mode 100644 index 69f98be00..000000000 --- a/commonmark/src/main/java/org/commonmark/renderer/BaseNodeRendererContext.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.commonmark.renderer; - -import org.commonmark.node.Node; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public abstract class BaseNodeRendererContext implements NodeRendererContext { - - private final Map, NodeRenderer> renderers = new HashMap<>(32); - - @Override - public void render(Node node) { - NodeRenderer nodeRenderer = renderers.get(node.getClass()); - if (nodeRenderer != null) { - nodeRenderer.render(node); - } - } - - protected void addNodeRenderers(List renderers) { - // The first node renderer for a node type "wins". - for (int i = renderers.size() - 1; i >= 0; i--) { - NodeRenderer nodeRenderer = renderers.get(i); - for (Class nodeType : nodeRenderer.getNodeTypes()) { - // Overwrite existing renderer - this.renderers.put(nodeType, nodeRenderer); - } - } - } -} diff --git a/commonmark/src/main/java/org/commonmark/renderer/BaseRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/BaseRenderer.java deleted file mode 100644 index d78a741b3..000000000 --- a/commonmark/src/main/java/org/commonmark/renderer/BaseRenderer.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.commonmark.renderer; - -import org.commonmark.node.Node; - -public abstract class BaseRenderer implements Renderer { - - @Override - public void render(Node node, Appendable output) { - NodeRendererContext context = createContext(output); - context.render(node); - } - - - @Override - public String render(Node node) { - StringBuilder sb = new StringBuilder(); - render(node, sb); - return sb.toString(); - } - - /** - * Create context for renderer. - * - * @param out the output for rendering - * @return context for renderer - */ - protected abstract NodeRendererContext createContext(Appendable out); -} diff --git a/commonmark/src/main/java/org/commonmark/renderer/NodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRendererContext.java deleted file mode 100644 index 97fda6682..000000000 --- a/commonmark/src/main/java/org/commonmark/renderer/NodeRendererContext.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.commonmark.renderer; - -import org.commonmark.node.Node; - -/** - * The context for node rendering, including configuration and functionality for the node renderer to use. - *

    This interface is not intended to be implemented by clients.

    - */ -public interface NodeRendererContext { - - /** - * Render the specified node and its children using the configured renderers. This should be used to render child - * nodes; be careful not to pass the node that is being rendered, that would result in an endless loop. - * - * @param node the node to render - */ - void render(Node node); - - /** - * @return the writer to use - */ - W getWriter(); -} diff --git a/commonmark/src/main/java/org/commonmark/renderer/NodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/NodeRendererFactory.java deleted file mode 100644 index f4f5e7a44..000000000 --- a/commonmark/src/main/java/org/commonmark/renderer/NodeRendererFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.commonmark.renderer; - -/** - * Factory for instantiating new node renderers when rendering is done. - */ -public interface NodeRendererFactory { - - /** - * 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(C context); -} diff --git a/commonmark/src/main/java/org/commonmark/renderer/Renderer.java b/commonmark/src/main/java/org/commonmark/renderer/Renderer.java index e954439a6..42740d91a 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/Renderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/Renderer.java @@ -7,7 +7,7 @@ public interface Renderer { /** * Render the tree of nodes to output. * - * @param node the root node + * @param node the root node * @param output output for rendering */ void render(Node node, Appendable output); diff --git a/commonmark/src/main/java/org/commonmark/renderer/Writer.java b/commonmark/src/main/java/org/commonmark/renderer/Writer.java deleted file mode 100644 index 8eba55305..000000000 --- a/commonmark/src/main/java/org/commonmark/renderer/Writer.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.commonmark.renderer; - -public interface Writer { -} diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 631192fb4..285e60e98 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -10,7 +10,6 @@ import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.NodeRendererContext; import org.junit.Test; import java.util.Collections; @@ -137,9 +136,9 @@ public NodeRenderer create(HtmlNodeRendererContext context) { private static class UpperCaseNodeRenderer implements NodeRenderer { - private final NodeRendererContext context; + private final HtmlNodeRendererContext context; - private UpperCaseNodeRenderer(NodeRendererContext context) { + private UpperCaseNodeRenderer(HtmlNodeRendererContext context) { this.context = context; } From 91827e2364f9f3bb162df22cf012d04f6c0980fb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 20 Sep 2016 16:59:07 +1000 Subject: [PATCH 179/815] Move renderers into .renderer package (api break) * HtmlRenderer and related classes now in org.commonmark.renderer.html * TextContentRenderer and co now in org.commonmark.renderer.text --- README.md | 2 +- .../android/test/AndroidSupportTest.java | 2 +- .../commonmark/ext/autolink/AutolinkExtension.java | 3 ++- .../org/commonmark/ext/autolink/AutolinkTest.java | 2 +- .../gfm/strikethrough/StrikethroughExtension.java | 14 +++++++------- .../internal/StrikethroughHtmlNodeRenderer.java | 4 ++-- .../StrikethroughTextContentNodeRenderer.java | 4 ++-- .../ext/gfm/strikethrough/StrikethroughTest.java | 4 ++-- .../commonmark/ext/gfm/tables/TablesExtension.java | 8 ++++---- .../ext/gfm/tables/internal/TableNodeRenderer.java | 4 ++-- .../org/commonmark/ext/gfm/tables/TablesTest.java | 8 ++++---- .../ext/heading/anchor/HeadingAnchorExtension.java | 10 +++++----- .../internal/HeadingIdAttributeProvider.java | 2 +- .../ext/heading/anchor/HeadingAnchorTest.java | 2 +- .../java/org/commonmark/ext/ins/InsExtension.java | 8 ++++---- .../ext/ins/internal/InsNodeRenderer.java | 4 ++-- .../test/java/org/commonmark/ext/ins/InsTest.java | 2 +- .../ext/front/matter/YamlFrontMatterExtension.java | 3 ++- .../ext/front/matter/YamlFrontMatterTest.java | 2 +- .../integration/SpecIntegrationTest.java | 2 +- .../java/org/commonmark/content/package-info.java | 4 ---- .../java/org/commonmark/html/package-info.java | 4 ---- .../src/main/java/org/commonmark/package-info.java | 2 +- .../html}/AttributeProvider.java | 2 +- .../html}/AttributeProviderContext.java | 2 +- .../html}/AttributeProviderFactory.java | 2 +- .../html}/CoreHtmlNodeRenderer.java | 3 +-- .../html}/HtmlNodeRendererContext.java | 3 +-- .../html}/HtmlNodeRendererFactory.java | 2 +- .../{ => renderer}/html/HtmlRenderer.java | 8 +------- .../commonmark/{ => renderer}/html/HtmlWriter.java | 2 +- .../org/commonmark/renderer/html/package-info.java | 4 ++++ .../text}/CoreTextContentNodeRenderer.java | 3 +-- .../text}/TextContentNodeRendererContext.java | 3 +-- .../text}/TextContentNodeRendererFactory.java | 2 +- .../text}/TextContentRenderer.java | 5 +---- .../text}/TextContentWriter.java | 2 +- .../org/commonmark/renderer/text/package-info.java | 4 ++++ commonmark/src/main/javadoc/overview.html | 6 +++--- .../org/commonmark/test/CoreRenderingTestCase.java | 2 +- .../commonmark/test/DelimiterProcessorTest.java | 6 +++--- .../java/org/commonmark/test/HtmlRendererTest.java | 12 ++++++------ .../test/java/org/commonmark/test/ParserTest.java | 2 +- .../java/org/commonmark/test/SpecBenchmark.java | 3 +-- .../java/org/commonmark/test/SpecCoreTest.java | 2 +- .../commonmark/test/TextContentRendererTest.java | 2 +- .../org/commonmark/test/TextContentWriterTest.java | 2 +- .../java/org/commonmark/test/UsageExampleTest.java | 2 +- 48 files changed, 87 insertions(+), 99 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/content/package-info.java delete mode 100644 commonmark/src/main/java/org/commonmark/html/package-info.java rename commonmark/src/main/java/org/commonmark/{html/attribute => renderer/html}/AttributeProvider.java (95%) rename commonmark/src/main/java/org/commonmark/{html/attribute => renderer/html}/AttributeProviderContext.java (87%) rename commonmark/src/main/java/org/commonmark/{html/attribute => renderer/html}/AttributeProviderFactory.java (89%) rename commonmark/src/main/java/org/commonmark/{html/renderer => renderer/html}/CoreHtmlNodeRenderer.java (99%) rename commonmark/src/main/java/org/commonmark/{html/renderer => renderer/html}/HtmlNodeRendererContext.java (94%) rename commonmark/src/main/java/org/commonmark/{html/renderer => renderer/html}/HtmlNodeRendererFactory.java (92%) rename commonmark/src/main/java/org/commonmark/{ => renderer}/html/HtmlRenderer.java (95%) rename commonmark/src/main/java/org/commonmark/{ => renderer}/html/HtmlWriter.java (97%) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/html/package-info.java rename commonmark/src/main/java/org/commonmark/{content/renderer => renderer/text}/CoreTextContentNodeRenderer.java (98%) rename commonmark/src/main/java/org/commonmark/{content/renderer => renderer/text}/TextContentNodeRendererContext.java (87%) rename commonmark/src/main/java/org/commonmark/{content/renderer => renderer/text}/TextContentNodeRendererFactory.java (91%) rename commonmark/src/main/java/org/commonmark/{content => renderer/text}/TextContentRenderer.java (95%) rename commonmark/src/main/java/org/commonmark/{content => renderer/text}/TextContentWriter.java (97%) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/text/package-info.java diff --git a/README.md b/README.md index 970bc7fa4..f9db66333 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,9 @@ Usage #### Parse and render to HTML ```java -import org.commonmark.html.HtmlRenderer; import org.commonmark.node.*; import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; Parser parser = Parser.builder().build(); Node document = parser.parse("This is *Sparta*"); diff --git a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java index 0308cf549..d59745dad 100644 --- a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java +++ b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java @@ -4,9 +4,9 @@ import org.commonmark.ext.autolink.AutolinkExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; -import org.commonmark.html.HtmlRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.spec.SpecReader; import org.junit.Before; import org.junit.Test; 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 606bbb532..e5926c7bb 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 @@ -3,13 +3,14 @@ import org.commonmark.Extension; import org.commonmark.ext.autolink.internal.AutolinkPostProcessor; import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; /** * Extension for automatically turning plain URLs and email addresses into links. *

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

    *

    * The parsed links are turned into normal {@link org.commonmark.node.Link} nodes. 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 c78887d1e..43346c079 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -1,7 +1,7 @@ package org.commonmark.ext.autolink; import org.commonmark.Extension; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; import org.junit.Test; 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 86fde5e69..3d0839f11 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,15 +1,15 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.Extension; -import org.commonmark.content.TextContentRenderer; -import org.commonmark.content.renderer.TextContentNodeRendererContext; -import org.commonmark.content.renderer.TextContentNodeRendererFactory; +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.StrikethroughTextContentNodeRenderer; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.renderer.HtmlNodeRendererContext; -import org.commonmark.html.renderer.HtmlNodeRendererFactory; +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; @@ -18,7 +18,7 @@ *

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

    *

    * The parsed strikethrough text regions are turned into {@link Strikethrough} nodes. 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 07e92271b..b818a87fa 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,7 +1,7 @@ package org.commonmark.ext.gfm.strikethrough.internal; -import org.commonmark.html.HtmlWriter; -import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; +import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.node.Node; import java.util.Collections; diff --git a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java index 5a1f9d332..ebdcd9dbe 100644 --- a/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java +++ b/commonmark-ext-gfm-strikethrough/src/main/java/org/commonmark/ext/gfm/strikethrough/internal/StrikethroughTextContentNodeRenderer.java @@ -1,7 +1,7 @@ package org.commonmark.ext.gfm.strikethrough.internal; -import org.commonmark.content.TextContentWriter; -import org.commonmark.content.renderer.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentWriter; +import org.commonmark.renderer.text.TextContentNodeRendererContext; import org.commonmark.node.Node; public class StrikethroughTextContentNodeRenderer extends StrikethroughNodeRenderer { 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 54658a95d..3ad8ee915 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java @@ -1,8 +1,8 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.Extension; -import org.commonmark.content.TextContentRenderer; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.text.TextContentRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; 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 d8f878b7d..5f562fc63 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,9 +3,9 @@ import org.commonmark.Extension; import org.commonmark.ext.gfm.tables.internal.TableBlockParser; import org.commonmark.ext.gfm.tables.internal.TableNodeRenderer; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.renderer.HtmlNodeRendererContext; -import org.commonmark.html.renderer.HtmlNodeRendererFactory; +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; @@ -14,7 +14,7 @@ *

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

    *

    * The parsed tables are turned into {@link TableBlock} blocks. 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 9b9ee548e..b93fdeb5a 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,8 +1,8 @@ package org.commonmark.ext.gfm.tables.internal; import org.commonmark.ext.gfm.tables.*; -import org.commonmark.html.HtmlWriter; -import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; +import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; 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 761cc65d5..df777a2a9 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,10 +1,10 @@ package org.commonmark.ext.gfm.tables; import org.commonmark.Extension; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.attribute.AttributeProvider; -import org.commonmark.html.attribute.AttributeProviderContext; -import org.commonmark.html.attribute.AttributeProviderFactory; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.html.AttributeProvider; +import org.commonmark.renderer.html.AttributeProviderContext; +import org.commonmark.renderer.html.AttributeProviderFactory; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index 08f6ac200..ba50af176 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -2,16 +2,16 @@ import org.commonmark.Extension; import org.commonmark.ext.heading.anchor.internal.HeadingIdAttributeProvider; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.attribute.AttributeProvider; -import org.commonmark.html.attribute.AttributeProviderContext; -import org.commonmark.html.attribute.AttributeProviderFactory; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.html.AttributeProvider; +import org.commonmark.renderer.html.AttributeProviderContext; +import org.commonmark.renderer.html.AttributeProviderFactory; /** * Extension for adding auto generated IDs to headings. *

    * Create it with {@link #create()} and then configure it on the builder - * {@link org.commonmark.html.HtmlRenderer.Builder#extensions(Iterable)}). + * {@link HtmlRenderer.Builder#extensions(Iterable)}). *

    *

    * The heading text will be used to create the id. Multiple headings with the diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index bce777a4d..9d5d98872 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -1,7 +1,7 @@ package org.commonmark.ext.heading.anchor.internal; import org.commonmark.ext.heading.anchor.IdGenerator; -import org.commonmark.html.attribute.AttributeProvider; +import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.node.*; import java.util.ArrayList; 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 671462085..3b797a1ab 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java @@ -1,7 +1,7 @@ package org.commonmark.ext.heading.anchor; import org.commonmark.Extension; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; import org.junit.Test; 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 4fd70fda2..831cd75c8 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,10 +3,10 @@ import org.commonmark.Extension; import org.commonmark.ext.ins.internal.InsDelimiterProcessor; import org.commonmark.ext.ins.internal.InsNodeRenderer; -import org.commonmark.html.renderer.HtmlNodeRendererContext; -import org.commonmark.html.renderer.HtmlNodeRendererFactory; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.parser.Parser; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.NodeRenderer; /** @@ -14,7 +14,7 @@ *

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

    *

    * The parsed ins text regions are turned into {@link Ins} nodes. 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 0b716637c..66c9dcab9 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java @@ -1,8 +1,8 @@ package org.commonmark.ext.ins.internal; import org.commonmark.ext.ins.Ins; -import org.commonmark.html.HtmlWriter; -import org.commonmark.html.renderer.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; +import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; 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 474c23786..3719078fc 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java @@ -1,7 +1,7 @@ package org.commonmark.ext.ins; import org.commonmark.Extension; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java index 19b4984e4..7a2c9f9f5 100644 --- a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java +++ b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/YamlFrontMatterExtension.java @@ -3,13 +3,14 @@ import org.commonmark.Extension; import org.commonmark.ext.front.matter.internal.YamlFrontMatterBlockParser; import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; /** * Extension for YAML-like metadata. *

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

    *

    * The parsed metadata is turned into {@link YamlFrontMatterNode}. You can access the metadata using {@link YamlFrontMatterVisitor}. 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 619d44be8..84e20b774 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java @@ -1,7 +1,7 @@ package org.commonmark.ext.front.matter; import org.commonmark.Extension; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.test.RenderingTestCase; 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 6e728bc22..af4edf23d 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 @@ -6,7 +6,7 @@ import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; import org.commonmark.ext.front.matter.YamlFrontMatterExtension; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.spec.SpecExample; import org.commonmark.test.SpecTestCase; diff --git a/commonmark/src/main/java/org/commonmark/content/package-info.java b/commonmark/src/main/java/org/commonmark/content/package-info.java deleted file mode 100644 index 414b8f5b7..000000000 --- a/commonmark/src/main/java/org/commonmark/content/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Text content rendering (see {@link org.commonmark.content.TextContentRenderer}) - */ -package org.commonmark.content; diff --git a/commonmark/src/main/java/org/commonmark/html/package-info.java b/commonmark/src/main/java/org/commonmark/html/package-info.java deleted file mode 100644 index e18834e7c..000000000 --- a/commonmark/src/main/java/org/commonmark/html/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * HTML rendering (see {@link org.commonmark.html.HtmlRenderer}) - */ -package org.commonmark.html; diff --git a/commonmark/src/main/java/org/commonmark/package-info.java b/commonmark/src/main/java/org/commonmark/package-info.java index 7ac2dad76..e3f0e0572 100644 --- a/commonmark/src/main/java/org/commonmark/package-info.java +++ b/commonmark/src/main/java/org/commonmark/package-info.java @@ -4,7 +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.html} for HTML rendering
    • + *
    • {@link org.commonmark.renderer.html} for HTML rendering
    • *
    */ package org.commonmark; diff --git a/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProvider.java b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java similarity index 95% rename from commonmark/src/main/java/org/commonmark/html/attribute/AttributeProvider.java rename to commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java index a76641722..e56975a42 100644 --- a/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProvider.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java @@ -1,4 +1,4 @@ -package org.commonmark.html.attribute; +package org.commonmark.renderer.html; import org.commonmark.node.Node; diff --git a/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderContext.java b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderContext.java similarity index 87% rename from commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderContext.java rename to commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderContext.java index f60043d06..0959932bc 100644 --- a/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderContext.java @@ -1,4 +1,4 @@ -package org.commonmark.html.attribute; +package org.commonmark.renderer.html; /** * The context for attribute providers. diff --git a/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderFactory.java b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderFactory.java similarity index 89% rename from commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderFactory.java rename to commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderFactory.java index b60031502..d4c12ca06 100644 --- a/commonmark/src/main/java/org/commonmark/html/attribute/AttributeProviderFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProviderFactory.java @@ -1,4 +1,4 @@ -package org.commonmark.html.attribute; +package org.commonmark.renderer.html; /** * Factory for instantiating new attribute providers when rendering is done. diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/CoreHtmlNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java similarity index 99% rename from commonmark/src/main/java/org/commonmark/html/renderer/CoreHtmlNodeRenderer.java rename to commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java index bc71d468a..2a97e668a 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -1,6 +1,5 @@ -package org.commonmark.html.renderer; +package org.commonmark.renderer.html; -import org.commonmark.html.HtmlWriter; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java similarity index 94% rename from commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java rename to commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java index acf086c08..26b2d528d 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java @@ -1,6 +1,5 @@ -package org.commonmark.html.renderer; +package org.commonmark.renderer.html; -import org.commonmark.html.HtmlWriter; import org.commonmark.node.Node; import java.util.Map; diff --git a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererFactory.java similarity index 92% rename from commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java rename to commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererFactory.java index 66aa97860..8a343bf0f 100644 --- a/commonmark/src/main/java/org/commonmark/html/renderer/HtmlNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererFactory.java @@ -1,4 +1,4 @@ -package org.commonmark.html.renderer; +package org.commonmark.renderer.html; import org.commonmark.renderer.NodeRenderer; diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java similarity index 95% rename from commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java rename to commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java index ee62d6f21..59a3104b6 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -1,12 +1,6 @@ -package org.commonmark.html; +package org.commonmark.renderer.html; import org.commonmark.Extension; -import org.commonmark.html.attribute.AttributeProvider; -import org.commonmark.html.attribute.AttributeProviderContext; -import org.commonmark.html.attribute.AttributeProviderFactory; -import org.commonmark.html.renderer.CoreHtmlNodeRenderer; -import org.commonmark.html.renderer.HtmlNodeRendererContext; -import org.commonmark.html.renderer.HtmlNodeRendererFactory; import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.internal.util.Escaping; import org.commonmark.node.HtmlBlock; diff --git a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java similarity index 97% rename from commonmark/src/main/java/org/commonmark/html/HtmlWriter.java rename to commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java index e69881601..46b65a2a5 100644 --- a/commonmark/src/main/java/org/commonmark/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java @@ -1,4 +1,4 @@ -package org.commonmark.html; +package org.commonmark.renderer.html; import org.commonmark.internal.util.Escaping; diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/package-info.java b/commonmark/src/main/java/org/commonmark/renderer/html/package-info.java new file mode 100644 index 000000000..014a4c69c --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/html/package-info.java @@ -0,0 +1,4 @@ +/** + * HTML rendering (see {@link org.commonmark.renderer.html.HtmlRenderer}) + */ +package org.commonmark.renderer.html; diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java similarity index 98% rename from commonmark/src/main/java/org/commonmark/content/renderer/CoreTextContentNodeRenderer.java rename to commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index 79072f45e..a78a7aadb 100644 --- a/commonmark/src/main/java/org/commonmark/content/renderer/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -1,8 +1,7 @@ -package org.commonmark.content.renderer; +package org.commonmark.renderer.text; import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.content.TextContentWriter; import java.util.Arrays; import java.util.HashSet; diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java similarity index 87% rename from commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java rename to commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java index 0aea1b6cb..1b1cf327c 100644 --- a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererContext.java @@ -1,7 +1,6 @@ -package org.commonmark.content.renderer; +package org.commonmark.renderer.text; import org.commonmark.node.Node; -import org.commonmark.content.TextContentWriter; public interface TextContentNodeRendererContext { diff --git a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererFactory.java similarity index 91% rename from commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java rename to commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererFactory.java index c3fbb6611..bf193dff4 100644 --- a/commonmark/src/main/java/org/commonmark/content/renderer/TextContentNodeRendererFactory.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentNodeRendererFactory.java @@ -1,4 +1,4 @@ -package org.commonmark.content.renderer; +package org.commonmark.renderer.text; import org.commonmark.renderer.NodeRenderer; diff --git a/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java similarity index 95% rename from commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java rename to commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java index 4ff3a82b5..d38f99972 100644 --- a/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -1,9 +1,6 @@ -package org.commonmark.content; +package org.commonmark.renderer.text; import org.commonmark.Extension; -import org.commonmark.content.renderer.CoreTextContentNodeRenderer; -import org.commonmark.content.renderer.TextContentNodeRendererContext; -import org.commonmark.content.renderer.TextContentNodeRendererFactory; import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; diff --git a/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java similarity index 97% rename from commonmark/src/main/java/org/commonmark/content/TextContentWriter.java rename to commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java index 90c6cdc07..46c2f09de 100644 --- a/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java @@ -1,4 +1,4 @@ -package org.commonmark.content; +package org.commonmark.renderer.text; import java.io.IOException; 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 new file mode 100644 index 000000000..07a558091 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/text/package-info.java @@ -0,0 +1,4 @@ +/** + * Text content rendering (see {@link org.commonmark.renderer.text.TextContentRenderer}) + */ +package org.commonmark.renderer.text; diff --git a/commonmark/src/main/javadoc/overview.html b/commonmark/src/main/javadoc/overview.html index 62bcf4a08..f562778a3 100644 --- a/commonmark/src/main/javadoc/overview.html +++ b/commonmark/src/main/javadoc/overview.html @@ -2,9 +2,9 @@ Java implementation of CommonMark for parsing markdown and rendering to HTML (core library)

    Example:

    -
        import org.commonmark.html.HtmlRenderer;
    -    import org.commonmark.node.*;
    +
        import org.commonmark.node.*;
         import org.commonmark.parser.Parser;
    +    import org.commonmark.renderer.html.HtmlRenderer;
     
         Parser parser = Parser.builder().build();
         Node document = parser.parse("This is *Sparta*");
    @@ -15,7 +15,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.html} for HTML rendering
    • +
    • {@link org.commonmark.renderer.html} for HTML rendering
    diff --git a/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java b/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java index 7c89caa2b..078c0ba12 100644 --- a/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java +++ b/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; public class CoreRenderingTestCase extends RenderingTestCase { diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 285e60e98..899a70d29 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -1,8 +1,8 @@ package org.commonmark.test; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.renderer.HtmlNodeRendererContext; -import org.commonmark.html.renderer.HtmlNodeRendererFactory; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.node.CustomNode; import org.commonmark.node.Node; import org.commonmark.node.Text; diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index fe21bbf52..b314a1911 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -1,11 +1,11 @@ package org.commonmark.test; -import org.commonmark.html.HtmlRenderer; -import org.commonmark.html.attribute.AttributeProvider; -import org.commonmark.html.attribute.AttributeProviderContext; -import org.commonmark.html.attribute.AttributeProviderFactory; -import org.commonmark.html.renderer.HtmlNodeRendererContext; -import org.commonmark.html.renderer.HtmlNodeRendererFactory; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.html.AttributeProvider; +import org.commonmark.renderer.html.AttributeProviderContext; +import org.commonmark.renderer.html.AttributeProviderFactory; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.Image; import org.commonmark.node.Link; diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 6c6915919..832b76e85 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.commonmark.parser.block.*; diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index 0ea063c65..33f072ed1 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -1,9 +1,8 @@ package org.commonmark.test; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; import org.commonmark.spec.SpecReader; -import org.openjdk.jmh.Main; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index 358aaea4d..521d4e9b6 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; import org.commonmark.node.Text; diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 7a6b6dca8..af74c36b4 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.commonmark.content.TextContentRenderer; +import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.junit.Test; diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java index a0bf41b18..0ffaf0e1f 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.commonmark.content.TextContentWriter; +import org.commonmark.renderer.text.TextContentWriter; import org.junit.Test; import static org.junit.Assert.*; diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 0bc079ea1..8009b329d 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -1,6 +1,6 @@ package org.commonmark.test; -import org.commonmark.html.HtmlRenderer; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; import org.commonmark.node.Text; From 17cbc4c3f831d08721e7a1d0de07aded3c2e47f3 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 23 Sep 2016 14:36:01 +1000 Subject: [PATCH 180/815] Add usage of NodeRenderer (#62) --- README.md | 52 +++++++++++++++++++ .../org/commonmark/test/UsageExampleTest.java | 50 +++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f9db66333..94964dacc 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,10 @@ the responsibility of the caller. #### Use a visitor to process parsed nodes +After the source text has been parsed, the result is a tree of nodes. +That tree can be modified before rendering, or just inspected without +rendering: + ```java Node node = parser.parse("Example\n=======\n\nSome more text"); WordCountVisitor visitor = new WordCountVisitor(); @@ -92,6 +96,54 @@ class WordCountVisitor extends AbstractVisitor { } ``` +#### Customize HTML rendering + +Sometimes you might want to customize how HTML is rendered, or you added +custom node subclasses that you want to render to HTML. Both can be done +using node renderers. + +In this example, we're changing the rendering of indented code blocks: + +```java +Parser parser = Parser.builder().build(); +HtmlNodeRendererFactory factory = new HtmlNodeRendererFactory() { + @Override + public NodeRenderer create(HtmlNodeRendererContext context) { + return new IndentedCodeBlockNodeRenderer(context); + } +}; +HtmlRenderer renderer = HtmlRenderer.builder().nodeRendererFactory(factory).build(); + +Node document = parser.parse("Example:\n\n code"); +renderer.render(document); // "

    Example:

    \n
    code\n
    \n" + +class IndentedCodeBlockNodeRenderer implements NodeRenderer { + + private final HtmlWriter html; + + IndentedCodeBlockNodeRenderer(HtmlNodeRendererContext context) { + this.html = context.getWriter(); + } + + @Override + public Set> getNodeTypes() { + // Return the node types we want to use this renderer for. + return Collections.>singleton(IndentedCodeBlock.class); + } + + @Override + public void render(Node node) { + // We only handle one type as per getNodeTypes, so we can just cast it here. + IndentedCodeBlock codeBlock = (IndentedCodeBlock) node; + html.line(); + html.tag("pre"); + html.text(codeBlock.getLiteral()); + html.tag("/pre"); + html.line(); + } +} +``` + ### API documentation Javadocs are available online on diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 8009b329d..f45eaaf71 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -1,12 +1,20 @@ package org.commonmark.test; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.IndentedCodeBlock; import org.commonmark.node.Node; import org.commonmark.node.Text; 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.html.HtmlWriter; import org.junit.Test; +import java.util.Collections; +import java.util.Set; + import static org.junit.Assert.assertEquals; public class UsageExampleTest { @@ -28,6 +36,21 @@ public void visitor() { assertEquals(4, visitor.wordCount); } + @Test + public void customizeRendering() { + Parser parser = Parser.builder().build(); + HtmlNodeRendererFactory factory = new HtmlNodeRendererFactory() { + @Override + public NodeRenderer create(HtmlNodeRendererContext context) { + return new IndentedCodeBlockNodeRenderer(context); + } + }; + HtmlRenderer renderer = HtmlRenderer.builder().nodeRendererFactory(factory).build(); + + Node document = parser.parse("Example:\n\n code"); + assertEquals("

    Example:

    \n
    code\n
    \n", renderer.render(document)); + } + class WordCountVisitor extends AbstractVisitor { int wordCount = 0; @@ -44,4 +67,29 @@ public void visit(Text text) { } } + class IndentedCodeBlockNodeRenderer implements NodeRenderer { + + private final HtmlWriter html; + + IndentedCodeBlockNodeRenderer(HtmlNodeRendererContext context) { + this.html = context.getWriter(); + } + + @Override + public Set> getNodeTypes() { + // Return the node types we want to use this renderer for. + return Collections.>singleton(IndentedCodeBlock.class); + } + + @Override + public void render(Node node) { + // We only handle one type as per getNodeTypes, so we can just cast it here. + IndentedCodeBlock codeBlock = (IndentedCodeBlock) node; + html.line(); + html.tag("pre"); + html.text(codeBlock.getLiteral()); + html.tag("/pre"); + html.line(); + } + } } From 6508cead27d0db7a25048fb6d8be517e2fa29c8f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 23 Sep 2016 14:37:50 +1000 Subject: [PATCH 181/815] README: Mention TextContentRenderer --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 94964dacc..498f89c9e 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,9 @@ options, see methods on the builders. Note that this library doesn't try to sanitize the resulting HTML; that is the responsibility of the caller. +For rendering to plain text, there's also a `TextContentRenderer` with +a very similar API. + #### Use a visitor to process parsed nodes After the source text has been parsed, the result is a tree of nodes. From a5da23ccd1c1f4aa26b61a85241fceaa6dae0f98 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 23 Sep 2016 14:41:20 +1000 Subject: [PATCH 182/815] README: Extend documentation of heading anchor extension --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 498f89c9e..b3deb8adb 100644 --- a/README.md +++ b/README.md @@ -206,17 +206,22 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. -### Header IDs +### Heading anchor -Enables adding auto generated id attributes to header based on their content. +Enables adding auto generated "id" attributes to heading tags. The "id" +is based on the text of the heading. -`# Heading` will be rendered as +`# Heading` will be rendered as: ```

    Heading

    ``` -Use class `HeaderIdExtension` in artifact `commonmark-ext-heading-anchor` +Use class `HeadingAnchorExtension` in artifact `commonmark-ext-heading-anchor`. + +In case you want custom rendering of the heading instead, you can use +the `IdGenerator` class directly together with a +`HtmlNodeRendererFactory` (see example above). ### Ins From 1b529ba6490e5ab20249788bcd2e32e9496ef4b3 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 23 Sep 2016 14:53:02 +1000 Subject: [PATCH 183/815] Fix Javadoc problems --- .../heading/anchor/HeadingAnchorExtension.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index ba50af176..9b93d6c0c 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -2,34 +2,28 @@ import org.commonmark.Extension; import org.commonmark.ext.heading.anchor.internal.HeadingIdAttributeProvider; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProviderContext; import org.commonmark.renderer.html.AttributeProviderFactory; +import org.commonmark.renderer.html.HtmlRenderer; /** * Extension for adding auto generated IDs to headings. *

    * Create it with {@link #create()} and then configure it on the builder * {@link HtmlRenderer.Builder#extensions(Iterable)}). - *

    *

    * The heading text will be used to create the id. Multiple headings with the * same text will result in appending a hyphen and number. For example: - *

    - * - *
    + * 
    
      * # Heading
      * # Heading
    - * 
    - * + *
    * will result in - * - *
    + * 
    
      * <h1 id="heading">Heading</h1>
      * <h1 id="heading-1">Heading</h1>
    - * 
    - * + *
    * * @see IdGenerator the IdGenerator class if just the ID generation part is needed */ From 93ec6217e3163239336d999b1ad4e8200a7e16c1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 23 Sep 2016 14:56:21 +1000 Subject: [PATCH 184/815] Fix some more Javadoc --- .../java/org/commonmark/ext/heading/anchor/IdGenerator.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java index eb8df6336..b2211de3d 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java @@ -30,11 +30,9 @@ public static Builder builder() { /** *

    * Generate an ID based on the provided text and previously generated IDs. - *

    *

    * This method is not thread safe, concurrent calls can end up * with non-unique identifiers. - *

    *

    * Note that collision can occur in the case that *

      @@ -42,7 +40,6 @@ public static Builder builder() { *
    • Method called with 'X' again
    • *
    • Method called with 'X-1'
    • *
    - *

    *

    * In that case, the three generated IDs will be: *

      @@ -50,11 +47,9 @@ public static Builder builder() { *
    • X-1
    • *
    • X-1
    • *
    - *

    *

    * Therefore if collisions are unacceptable you should ensure that * numbers are stripped from end of {@code text}. - *

    * * @param text Text that the identifier should be based on. Will be normalised, then used to generate the * identifier. From abab0ce1fc7fed368e4fc0765e2ed049297f48cf Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 23 Sep 2016 14:57:10 +1000 Subject: [PATCH 185/815] [maven-release-plugin] prepare release commonmark-parent-0.7.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-ins/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 | 18 +++++++++--------- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 313f6241f..456790d2a 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 9fea47115..75cd3b498 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index be09a473c..f0217afae 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 5c4b109fc..e0f6bfd26 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 0bce1524d..9c73b0c01 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index bc26788c4..2aa7b94ed 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.6.1-SNAPSHOT + 0.7.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 7260f7ca6..2e040dcd6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index d04482dd1..95018a943 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 277349537..016402b2d 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark diff --git a/pom.xml b/pom.xml index d39c79c0a..bd05c046d 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.6.1-SNAPSHOT + 0.7.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -86,37 +86,37 @@ com.atlassian.commonmark commonmark - 0.6.1-SNAPSHOT + 0.7.0 com.atlassian.commonmark commonmark-ext-autolink - 0.6.1-SNAPSHOT + 0.7.0 com.atlassian.commonmark commonmark-ext-ins - 0.6.1-SNAPSHOT + 0.7.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.6.1-SNAPSHOT + 0.7.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.6.1-SNAPSHOT + 0.7.0 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.6.1-SNAPSHOT + 0.7.0 com.atlassian.commonmark commonmark-test-util - 0.6.1-SNAPSHOT + 0.7.0 @@ -194,7 +194,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.7.0 From c9bc3684e35c94e18a64e87e03627954d8336bc4 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 23 Sep 2016 14:57:11 +1000 Subject: [PATCH 186/815] [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-ins/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 | 18 +++++++++--------- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 456790d2a..afd6a195a 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 75cd3b498..0054e0005 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index f0217afae..d4592bf2f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index e0f6bfd26..83af7ec97 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 9c73b0c01..3beabe61d 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 2aa7b94ed..18c4872f5 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.7.0 + 0.7.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 2e040dcd6..cbec189bf 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 95018a943..63cb582de 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 016402b2d..9cde1853a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index bd05c046d..f055ff4c8 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.0 + 0.7.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -86,37 +86,37 @@ com.atlassian.commonmark commonmark - 0.7.0 + 0.7.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.7.0 + 0.7.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-ins - 0.7.0 + 0.7.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.7.0 + 0.7.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.7.0 + 0.7.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.7.0 + 0.7.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.7.0 + 0.7.1-SNAPSHOT @@ -194,7 +194,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.7.0 + HEAD From 11add30640a9d03ce73df20cf83bc1ac044e71e3 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 23 Sep 2016 15:38:43 +1000 Subject: [PATCH 187/815] Bump versions --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b3deb8adb..0d543d2c6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.6.0 + 0.7.0 ``` @@ -168,7 +168,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.6.0 + 0.7.0 ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index aac87c1c5..deac1d031 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -7,7 +7,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.6.0" >> test.properties +echo "version.maven=0.7.0" >> test.properties echo "version.maven_autolink=0.5.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 7af4200bc..be04c1682 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.6.0 +version.maven=0.7.0 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.5.0 # Version number of commonmark and extensions in project. -version.snapshot=0.6.1-SNAPSHOT +version.snapshot=0.7.1-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.5.0 ``` From add7fee8fd267a6365d1b08c1a3c01f9c0da716a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 24 Sep 2016 22:13:32 +1000 Subject: [PATCH 188/815] README: Wrap line to make .extensions stand out more (#63) --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0d543d2c6..20172fb73 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,12 @@ Then, configure the extension on the builders: import org.commonmark.ext.gfm.tables.TablesExtension; List extensions = Arrays.asList(TablesExtension.create()); -Parser parser = Parser.builder().extensions(extensions).build(); -HtmlRenderer renderer = HtmlRenderer.builder().extensions(extensions).build(); +Parser parser = Parser.builder() + .extensions(extensions) + .build(); +HtmlRenderer renderer = HtmlRenderer.builder() + .extensions(extensions) + .build(); ``` To configure another extension in the above example, just add it to the list. From fa299a6d565f3a7e66132410a7f7cf5752d1c700 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 26 Sep 2016 11:43:17 +1000 Subject: [PATCH 189/815] README: Add usage example for AttributeProvider (#64) --- README.md | 65 +++++++++++++++---- .../org/commonmark/test/UsageExampleTest.java | 50 +++++++++----- 2 files changed, 89 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 20172fb73..ef7afb9f3 100644 --- a/README.md +++ b/README.md @@ -99,26 +99,60 @@ class WordCountVisitor extends AbstractVisitor { } ``` -#### Customize HTML rendering +#### Add or change attributes of HTML elements -Sometimes you might want to customize how HTML is rendered, or you added -custom node subclasses that you want to render to HTML. Both can be done -using node renderers. +Sometimes you might want to customize how HTML is rendered. If all you +want to do is add or change attributes on some elements, there's a +simple way to do that. -In this example, we're changing the rendering of indented code blocks: +In this example, we register a factory for an `AttributeProvider` on the +renderer to set a `class="border"` attribute on `img` elements. ```java Parser parser = Parser.builder().build(); -HtmlNodeRendererFactory factory = new HtmlNodeRendererFactory() { +HtmlRenderer renderer = HtmlRenderer.builder() + .attributeProviderFactory(new AttributeProviderFactory() { + public AttributeProvider create(AttributeProviderContext context) { + return new ImageAttributeProvider(); + } + }) + .build(); + +Node document = parser.parse("![text](/url.png)"); +renderer.render(document); +// "

    \"text\"

    \n" + +class ImageAttributeProvider implements AttributeProvider { @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new IndentedCodeBlockNodeRenderer(context); + public void setAttributes(Node node, Map attributes) { + if (node instanceof Image) { + attributes.put("class", "border"); + } } -}; -HtmlRenderer renderer = HtmlRenderer.builder().nodeRendererFactory(factory).build(); +} +``` + +#### Customize HTML rendering + +If you want to do more than just change attributes, there's also a way +to take complete control over how HTML is rendered. + +In this example, we're changing the rendering of indented code blocks to +only wrap them in `pre` instead of `pre` and `code`: + +```java +Parser parser = Parser.builder().build(); +HtmlRenderer renderer = HtmlRenderer.builder() + .nodeRendererFactory(new HtmlNodeRendererFactory() { + public NodeRenderer create(HtmlNodeRendererContext context) { + return new IndentedCodeBlockNodeRenderer(context); + } + }) + .build(); Node document = parser.parse("Example:\n\n code"); -renderer.render(document); // "

    Example:

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

    Example:

    \n
    code\n
    \n" class IndentedCodeBlockNodeRenderer implements NodeRenderer { @@ -147,6 +181,15 @@ class IndentedCodeBlockNodeRenderer implements NodeRenderer { } ``` +#### Add your own node types + +In case you want to store additional data in the document or have custom +elements in the resulting HTML, you can create your own subclass of +`CustomNode` and add instances as child nodes to existing nodes. + +To define the HTML rendering for them, you can use a `NodeRenderer` as +explained above. + ### API documentation Javadocs are available online on diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index f45eaaf71..5d048f611 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -1,18 +1,13 @@ package org.commonmark.test; -import org.commonmark.node.AbstractVisitor; -import org.commonmark.node.IndentedCodeBlock; -import org.commonmark.node.Node; -import org.commonmark.node.Text; +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.commonmark.renderer.html.HtmlWriter; +import org.commonmark.renderer.html.*; import org.junit.Test; import java.util.Collections; +import java.util.Map; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -36,16 +31,32 @@ public void visitor() { assertEquals(4, visitor.wordCount); } + @Test + public void addAttributes() { + Parser parser = Parser.builder().build(); + HtmlRenderer renderer = HtmlRenderer.builder() + .attributeProviderFactory(new AttributeProviderFactory() { + public AttributeProvider create(AttributeProviderContext context) { + return new ImageAttributeProvider(); + } + }) + .build(); + + Node document = parser.parse("![text](/url.png)"); + assertEquals("

    \"text\"

    \n", + renderer.render(document)); + } + @Test public void customizeRendering() { Parser parser = Parser.builder().build(); - HtmlNodeRendererFactory factory = new HtmlNodeRendererFactory() { - @Override - public NodeRenderer create(HtmlNodeRendererContext context) { - return new IndentedCodeBlockNodeRenderer(context); - } - }; - HtmlRenderer renderer = HtmlRenderer.builder().nodeRendererFactory(factory).build(); + HtmlRenderer renderer = HtmlRenderer.builder() + .nodeRendererFactory(new HtmlNodeRendererFactory() { + public NodeRenderer create(HtmlNodeRendererContext context) { + return new IndentedCodeBlockNodeRenderer(context); + } + }) + .build(); Node document = parser.parse("Example:\n\n code"); assertEquals("

    Example:

    \n
    code\n
    \n", renderer.render(document)); @@ -67,6 +78,15 @@ public void visit(Text text) { } } + class ImageAttributeProvider implements AttributeProvider { + @Override + public void setAttributes(Node node, Map attributes) { + if (node instanceof Image) { + attributes.put("class", "border"); + } + } + } + class IndentedCodeBlockNodeRenderer implements NodeRenderer { private final HtmlWriter html; From 107a8937daf6a4549cf5ae8f3b976705686d02fb Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Tue, 4 Oct 2016 13:19:12 +1100 Subject: [PATCH 190/815] Add prefix/suffix to ids generated * Adds the ability for IdGenerator to be passed in prefixes and suffixes that will ensure to be added in-front/after every id. This will allow users to 'namespace' their generated ids to a certain pool. --- .../anchor/HeadingAnchorExtension.java | 64 ++++++++++++++++++- .../ext/heading/anchor/IdGenerator.java | 29 ++++++++- .../internal/HeadingIdAttributeProvider.java | 14 +++- .../HeadingAnchorConfigurationTest.java | 60 +++++++++++++++++ 4 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index 9b93d6c0c..a07e53df5 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -29,11 +29,26 @@ */ public class HeadingAnchorExtension implements HtmlRenderer.HtmlRendererExtension { - private HeadingAnchorExtension() { + private final String defaultId; + private final String idPrefix; + private final String idSuffix; + + private HeadingAnchorExtension(String defaultId, String idPrefix, String idSuffix) { + this.defaultId = defaultId; + this.idPrefix = idPrefix; + this.idSuffix = idSuffix; } public static Extension create() { - return new HeadingAnchorExtension(); + return create(builder()); + } + + public static Extension create(Builder builder) { + return new HeadingAnchorExtension(builder.defaultId, builder.idPrefix, builder.idSuffix); + } + + public static Builder builder() { + return new Builder(); } @Override @@ -41,8 +56,51 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { rendererBuilder.attributeProviderFactory(new AttributeProviderFactory() { @Override public AttributeProvider create(AttributeProviderContext context) { - return HeadingIdAttributeProvider.create(); + return HeadingIdAttributeProvider.create(defaultId, idPrefix, idSuffix); } }); } + + public static class Builder { + private String defaultId; + private String idPrefix; + private String idSuffix; + + public Builder() { + defaultId = "id"; + idPrefix = ""; + idSuffix = ""; + } + + /** + * @param value Default value for the id to take if no generated id can be extracted. Default "id" + * @return {@code this} + */ + public Builder defaultId(String value) { + this.defaultId = value; + return this; + } + + /** + * @param value Set the value to be prepended to every id generated. Default "" + * @return {@code this} + */ + public Builder idPrefix(String value) { + this.idPrefix = value; + return this; + } + + /** + * @param value Set the value to be appended to every id generated. Default "" + * @return + */ + public Builder idSuffix(String value) { + this.idSuffix = value; + return this; + } + + public Extension build() { + return HeadingAnchorExtension.create(this); + } + } } diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java index b2211de3d..b7701398b 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java @@ -13,10 +13,14 @@ public class IdGenerator { private final Pattern allowedCharacters = Pattern.compile("[\\w\\-_]+", Pattern.UNICODE_CHARACTER_CLASS); private final Map identityMap; + private final String prefix; + private final String suffix; private String defaultIdentifier; private IdGenerator(Builder builder) { this.defaultIdentifier = builder.defaultIdentifier; + this.prefix = builder.prefix; + this.suffix = builder.suffix; this.identityMap = new HashMap<>(); } @@ -67,11 +71,11 @@ public String generateId(String text) { if (!identityMap.containsKey(normalizedIdentity)) { identityMap.put(normalizedIdentity, 1); - return normalizedIdentity; + return prefix + normalizedIdentity + suffix; } else { int currentCount = identityMap.get(normalizedIdentity); identityMap.put(normalizedIdentity, currentCount + 1); - return normalizedIdentity + "-" + currentCount; + return prefix + normalizedIdentity + "-" + currentCount + suffix; } } @@ -95,6 +99,8 @@ private String normalizeText(String text) { public static class Builder { private String defaultIdentifier = "id"; + private String prefix = ""; + private String suffix = ""; public IdGenerator build() { return new IdGenerator(this); @@ -105,6 +111,25 @@ public IdGenerator build() { * @return {@code this} */ public Builder defaultId(String defaultId) { + this.defaultIdentifier = defaultId; + return this; + } + + /** + * @param prefix the text to place before the generated identity + * @return {@code this} + */ + public Builder prefix(String prefix) { + this.prefix = prefix; + return this; + } + + /** + * @param suffix the text to place after the generated identity + * @return {@code this} + */ + public Builder suffix(String suffix) { + this.suffix = suffix; return this; } } diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index 9d5d98872..690f8a723 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -12,12 +12,20 @@ public class HeadingIdAttributeProvider implements AttributeProvider { private final IdGenerator idGenerator; - private HeadingIdAttributeProvider() { - idGenerator = IdGenerator.builder().defaultId("heading").build(); + private HeadingIdAttributeProvider(String defaultId, String prefix, String suffix) { + idGenerator = IdGenerator.builder() + .defaultId(defaultId) + .prefix(prefix) + .suffix(suffix) + .build(); } public static HeadingIdAttributeProvider create() { - return new HeadingIdAttributeProvider(); + return create("default", "", ""); + } + + public static HeadingIdAttributeProvider create(String defaultId, String prefix, String suffix) { + return new HeadingIdAttributeProvider(defaultId, prefix, suffix); } @Override 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 new file mode 100644 index 000000000..db879c3e1 --- /dev/null +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java @@ -0,0 +1,60 @@ +package org.commonmark.ext.heading.anchor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +public class HeadingAnchorConfigurationTest { + + private static final Parser PARSER = Parser.builder().build(); + + private HtmlRenderer buildRenderer(String defaultId, String prefix, String suffix) { + Extension ext = HeadingAnchorExtension.builder() + .defaultId(defaultId) + .idPrefix(prefix) + .idSuffix(suffix) + .build(); + return HtmlRenderer.builder() + .extensions(Arrays.asList(ext)) + .build(); + } + + @Test + public void testDefaultConfigurationHasNoAdditions() { + HtmlRenderer renderer = HtmlRenderer.builder() + .extensions(Arrays.asList(HeadingAnchorExtension.create())) + .build(); + assertThat(doRender(renderer, "# "), equalTo("

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

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

    text

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

    text

    \n")); + } + + private String doRender(HtmlRenderer renderer, String text) { + return renderer.render(PARSER.parse(text)); + } + +} From 927a3870ca72239175f5bc090ef3ed88cb1bbbe3 Mon Sep 17 00:00:00 2001 From: Paul Thompson Date: Tue, 4 Oct 2016 16:54:31 +1100 Subject: [PATCH 191/815] Refactor API, remove/change vis on methods --- .../commonmark/ext/heading/anchor/HeadingAnchorExtension.java | 2 +- .../heading/anchor/internal/HeadingIdAttributeProvider.java | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index a07e53df5..09b8cc9d2 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -43,7 +43,7 @@ public static Extension create() { return create(builder()); } - public static Extension create(Builder builder) { + private static Extension create(Builder builder) { return new HeadingAnchorExtension(builder.defaultId, builder.idPrefix, builder.idSuffix); } diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index 690f8a723..1b6f74480 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -20,10 +20,6 @@ private HeadingIdAttributeProvider(String defaultId, String prefix, String suffi .build(); } - public static HeadingIdAttributeProvider create() { - return create("default", "", ""); - } - public static HeadingIdAttributeProvider create(String defaultId, String prefix, String suffix) { return new HeadingIdAttributeProvider(defaultId, prefix, suffix); } From f47c4866d5328e5346f1f529b4f46b19d1902c8b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 5 Oct 2016 12:25:21 +1100 Subject: [PATCH 192/815] ext-heading-anchor: Minor cleanups for consistency --- .../anchor/HeadingAnchorExtension.java | 43 +++++++++---------- .../HeadingAnchorConfigurationTest.java | 6 +-- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java index 09b8cc9d2..cee414da2 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/HeadingAnchorExtension.java @@ -10,8 +10,8 @@ /** * Extension for adding auto generated IDs to headings. *

    - * Create it with {@link #create()} and then configure it on the builder - * {@link HtmlRenderer.Builder#extensions(Iterable)}). + * Create it with {@link #create()} or {@link #builder()} and then configure it on the + * renderer builder ({@link HtmlRenderer.Builder#extensions(Iterable)}). *

    * The heading text will be used to create the id. Multiple headings with the * same text will result in appending a hyphen and number. For example: @@ -33,20 +33,22 @@ public class HeadingAnchorExtension implements HtmlRenderer.HtmlRendererExtensio private final String idPrefix; private final String idSuffix; - private HeadingAnchorExtension(String defaultId, String idPrefix, String idSuffix) { - this.defaultId = defaultId; - this.idPrefix = idPrefix; - this.idSuffix = idSuffix; + private HeadingAnchorExtension(Builder builder) { + this.defaultId = builder.defaultId; + this.idPrefix = builder.idPrefix; + this.idSuffix = builder.idSuffix; } + /** + * @return the extension built with default settings + */ public static Extension create() { - return create(builder()); - } - - private static Extension create(Builder builder) { - return new HeadingAnchorExtension(builder.defaultId, builder.idPrefix, builder.idSuffix); + return new HeadingAnchorExtension(builder()); } + /** + * @return a builder to configure the extension settings + */ public static Builder builder() { return new Builder(); } @@ -62,15 +64,9 @@ public AttributeProvider create(AttributeProviderContext context) { } public static class Builder { - private String defaultId; - private String idPrefix; - private String idSuffix; - - public Builder() { - defaultId = "id"; - idPrefix = ""; - idSuffix = ""; - } + private String defaultId = "id"; + private String idPrefix = ""; + private String idSuffix = ""; /** * @param value Default value for the id to take if no generated id can be extracted. Default "id" @@ -92,15 +88,18 @@ public Builder idPrefix(String value) { /** * @param value Set the value to be appended to every id generated. Default "" - * @return + * @return {@code this} */ public Builder idSuffix(String value) { this.idSuffix = value; return this; } + /** + * @return a configured extension + */ public Extension build() { - return HeadingAnchorExtension.create(this); + return new HeadingAnchorExtension(this); } } } 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 db879c3e1..5a7f47cd3 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorConfigurationTest.java @@ -1,14 +1,12 @@ package org.commonmark.ext.heading.anchor; -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; - import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.junit.Test; +import java.util.Arrays; + import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; From f848c4f6b8c94344703d540eb987a39f732e12fa Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 5 Oct 2016 12:33:51 +1100 Subject: [PATCH 193/815] [maven-release-plugin] prepare release commonmark-parent-0.7.1 --- 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-ins/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 | 18 +++++++++--------- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index afd6a195a..452bf163c 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 0054e0005..7169f214f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index d4592bf2f..f4d596543 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 83af7ec97..5168881a4 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 3beabe61d..d65f5e3fd 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 18c4872f5..1c09daa91 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.7.1-SNAPSHOT + 0.7.1 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index cbec189bf..569b1f74c 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 63cb582de..0900e9986 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 9cde1853a..6e4b77e55 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark diff --git a/pom.xml b/pom.xml index f055ff4c8..d56a8fb24 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1-SNAPSHOT + 0.7.1 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -86,37 +86,37 @@ com.atlassian.commonmark commonmark - 0.7.1-SNAPSHOT + 0.7.1 com.atlassian.commonmark commonmark-ext-autolink - 0.7.1-SNAPSHOT + 0.7.1 com.atlassian.commonmark commonmark-ext-ins - 0.7.1-SNAPSHOT + 0.7.1 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.7.1-SNAPSHOT + 0.7.1 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.7.1-SNAPSHOT + 0.7.1 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.7.1-SNAPSHOT + 0.7.1 com.atlassian.commonmark commonmark-test-util - 0.7.1-SNAPSHOT + 0.7.1 @@ -194,7 +194,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.7.1 From 1e1da61e1e9e2cc6ab357489f2b89bcdddce0e21 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 5 Oct 2016 12:33:51 +1100 Subject: [PATCH 194/815] [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-ins/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 | 18 +++++++++--------- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 452bf163c..17e1c7fec 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 7169f214f..529d5e192 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index f4d596543..13ebb4369 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 5168881a4..e11f981e0 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index d65f5e3fd..fcfa919bd 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 1c09daa91..a633431b8 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.7.1 + 0.7.2-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 569b1f74c..045d83392 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 0900e9986..fb80f98fe 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 6e4b77e55..b1a0fe489 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index d56a8fb24..e1e4dbdc8 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.1 + 0.7.2-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -86,37 +86,37 @@ com.atlassian.commonmark commonmark - 0.7.1 + 0.7.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.7.1 + 0.7.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-ins - 0.7.1 + 0.7.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.7.1 + 0.7.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.7.1 + 0.7.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.7.1 + 0.7.2-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.7.1 + 0.7.2-SNAPSHOT @@ -194,7 +194,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.7.1 + HEAD From 2c6cc939a03cfdaa0e5da4855d5cfc40c3de16a0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 5 Oct 2016 14:58:27 +1100 Subject: [PATCH 195/815] Bump versions --- README.md | 4 ++-- commonmark-android-test/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ef7afb9f3..6c700afc5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.7.0 + 0.7.1 ``` @@ -211,7 +211,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.7.0 + 0.7.1 ``` diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index be04c1682..cb56a39fa 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,7 +28,7 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.7.0 +version.maven=0.7.1 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.5.0 From a065a98ed5ce8c097e6f171ff126abf0fca3ecc9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 8 Nov 2016 16:14:24 +1100 Subject: [PATCH 196/815] Add missing extensions to android test build (#71) This way, we also catch Android incompatibilities in extension code. --- commonmark-android-test/app/build.gradle | 9 ++++++ .../android/test/AndroidSupportTest.java | 32 ++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 0901a5496..93499720b 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -51,6 +51,9 @@ repositories { '../../commonmark-ext-autolink/target', '../../commonmark-ext-gfm-strikethrough/target', '../../commonmark-ext-gfm-tables/target', + '../../commonmark-ext-heading-anchor/target', + '../../commonmark-ext-ins/target', + '../../commonmark-ext-yaml-front-matter/target', '../../commonmark-test-util/target' } } @@ -66,10 +69,16 @@ dependencies { androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-autolink:' + testProperties['version.maven'] androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:' + testProperties['version.maven'] androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-gfm-tables:' + testProperties['version.maven'] + androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-heading-anchor:' + testProperties['version.maven'] + androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-ins:' + testProperties['version.maven'] + androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-yaml-front-matter:' + testProperties['version.maven'] androidTestSnapshotCompile 'org.nibor.autolink:autolink:' + testProperties['version.snapshot_autolink'] androidTestSnapshotCompile ':commonmark-' + testProperties['version.snapshot'] androidTestSnapshotCompile ':commonmark-ext-autolink-' + testProperties['version.snapshot'] androidTestSnapshotCompile ':commonmark-ext-gfm-strikethrough-' + testProperties['version.snapshot'] androidTestSnapshotCompile ':commonmark-ext-gfm-tables-' + testProperties['version.snapshot'] + androidTestSnapshotCompile ':commonmark-ext-heading-anchor-' + testProperties['version.snapshot'] + androidTestSnapshotCompile ':commonmark-ext-ins-' + testProperties['version.snapshot'] + androidTestSnapshotCompile ':commonmark-ext-yaml-front-matter-' + testProperties['version.snapshot'] } diff --git a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java index d59745dad..dea699aee 100644 --- a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java +++ b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java @@ -2,8 +2,11 @@ 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.heading.anchor.HeadingAnchorExtension; +import org.commonmark.ext.ins.InsExtension; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; @@ -54,10 +57,25 @@ public void tablesExtensionTest() throws Exception { parseWithExtensionsTest(TablesExtension.create()); } + @Test + public void headingAnchorExtensionTest() throws Exception { + parseWithExtensionsTest(HeadingAnchorExtension.create()); + } + + @Test + public void insExtensionTest() throws Exception { + parseWithExtensionsTest(InsExtension.create()); + } + + @Test + public void yamlFrontMatterExtensionTest() throws Exception { + parseWithExtensionsTest(YamlFrontMatterExtension.create()); + } + @Test public void htmlRendererTest() throws Exception { - Parser parser = new Parser.Builder().build(); - HtmlRenderer renderer = new HtmlRenderer.Builder().build(); + Parser parser = Parser.builder().build(); + HtmlRenderer renderer = HtmlRenderer.builder().build(); String renderedString = renderer.render(parser.parse(spec)); @@ -65,12 +83,18 @@ public void htmlRendererTest() throws Exception { } private void parseWithExtensionsTest(Extension extension) throws Exception { - Parser parser = new Parser.Builder() + Parser parser = Parser.builder() .extensions(Collections.singletonList(extension)) .build(); Node document = parser.parse(spec); - assertNotNull(document); + + HtmlRenderer renderer = HtmlRenderer.builder() + .extensions(Collections.singletonList(extension)) + .build(); + + String renderedString = renderer.render(document); + assertNotNull(renderedString); } } From 623c8dd552febd6a2802673a997455eca5ce7262 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 8 Nov 2016 17:05:51 +1100 Subject: [PATCH 197/815] Debug Android build --- commonmark-android-test/.travis.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index deac1d031..533f5339f 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -1,6 +1,7 @@ #!/bin/sh set -e +set -x version=$(cd .. && mvn help:evaluate -Dexpression=project.version | grep -v '^\[' | tail -1) autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpression=autolink.version | grep -v '^\[' | tail -1) From 1adc5df495e65703f254cd3300baf25f6e59f2b4 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 29 Nov 2016 13:26:29 +1100 Subject: [PATCH 198/815] ext-heading-anchor: Recover from IllegalArgumentException on Android (#71) --- .../commonmark/ext/heading/anchor/IdGenerator.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java index b7701398b..6eb85b6c1 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/IdGenerator.java @@ -11,13 +11,14 @@ * Use {@link #builder()} to create an instance. */ public class IdGenerator { - private final Pattern allowedCharacters = Pattern.compile("[\\w\\-_]+", Pattern.UNICODE_CHARACTER_CLASS); + private final Pattern allowedCharacters; private final Map identityMap; private final String prefix; private final String suffix; private String defaultIdentifier; private IdGenerator(Builder builder) { + this.allowedCharacters = compileAllowedCharactersPattern(); this.defaultIdentifier = builder.defaultIdentifier; this.prefix = builder.prefix; this.suffix = builder.suffix; @@ -79,6 +80,17 @@ public String generateId(String text) { } } + private static Pattern compileAllowedCharactersPattern() { + String regex = "[\\w\\-_]+"; + try { + return Pattern.compile(regex, Pattern.UNICODE_CHARACTER_CLASS); + } catch (IllegalArgumentException e) { + // Android only supports the flag in API level 24. But it actually uses Unicode character classes by + // default, so not specifying the flag is ok. See issue #71. + return Pattern.compile(regex); + } + } + /** * Assume we've been given a space separated text. * From d329e3816ee37583f4ee2160bd4ad7e7e17ce58f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 14 Nov 2016 14:53:24 +1100 Subject: [PATCH 199/815] Try to see if build fails with Android API level 16 --- .travis.yml | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/app/build.gradle | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9dcfc6b62..b595e0055 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,10 @@ matrix: env: TEST=android android: components: - - android-15 + - android-16 - build-tools-21.1.1 - extra-android-m2repository - - sys-img-armeabi-v7a-android-15 + - sys-img-armeabi-v7a-android-16 script: - 'if [[ $TEST = java ]]; then mvn test -Dsurefire.useFile=false; fi' - 'if [[ $TEST = android ]]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index 533f5339f..e6f984663 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -13,7 +13,7 @@ echo "version.maven_autolink=0.5.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties -echo no | android create avd --force -n test -t "android-15" +echo no | android create avd --force -n test -t "android-16" emulator -avd test -no-audio -no-window & android-wait-for-emulator diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 93499720b..3ca56fbe9 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -10,13 +10,13 @@ if (testPropertiesFile.canRead()) { } android { - compileSdkVersion 15 + compileSdkVersion 16 buildToolsVersion "21.1.1" defaultConfig { applicationId "com.atlassian.commonmark.android.test" - minSdkVersion 15 - targetSdkVersion 15 + minSdkVersion 16 + targetSdkVersion 16 versionCode 1 versionName "1.0" From 63d541e7681ed12403b5b8d9bea3359a835100b0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 29 Nov 2016 15:55:02 +1100 Subject: [PATCH 200/815] Update to CommonMark spec 0.27 (#73) --- .../src/main/resources/spec.txt | 115 ++++++++++++------ 1 file changed, 81 insertions(+), 34 deletions(-) diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index e2b6834d8..c66f93b77 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.26 -date: '2016-07-15' +version: 0.27 +date: '2016-11-18' license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -1985,7 +1985,7 @@ by their start and end conditions. The block begins with a line that meets a [start condition](@) (after up to three spaces optional indentation). It ends with the first subsequent line that meets a matching [end condition](@), or the last line of -the document or other [container block](@), if no line is encountered that meets the +the document or other [container block]), if no line is encountered that meets the [end condition]. If the first line meets both the [start condition] and the [end condition], the block will contain just that line. @@ -2015,7 +2015,8 @@ followed by one of the strings (case-insensitive) `address`, `article`, `aside`, `base`, `basefont`, `blockquote`, `body`, `caption`, `center`, `col`, `colgroup`, `dd`, `details`, `dialog`, `dir`, `div`, `dl`, `dt`, `fieldset`, `figcaption`, `figure`, -`footer`, `form`, `frame`, `frameset`, `h1`, `head`, `header`, `hr`, +`footer`, `form`, `frame`, `frameset`, +`h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`, `html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, `meta`, `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, `section`, `source`, `summary`, `table`, `tbody`, `td`, @@ -3636,11 +3637,11 @@ The following rules define [list items]: If the list item is ordered, then it is also assigned a start number, based on the ordered list marker. - Exceptions: When the list item interrupts a paragraph---that - is, when it starts on a line that would otherwise count as - [paragraph continuation text]---then (a) the lines *Ls* must - not begin with a blank line, and (b) if the list item is - ordered, the start number must be 1. + Exceptions: When the first list item in a [list] interrupts + a paragraph---that is, when it starts on a line that would + otherwise count as [paragraph continuation text]---then (a) + the lines *Ls* must not begin with a blank line, and (b) if + the list item is ordered, the start number must be 1. For example, let *Ls* be the lines @@ -4730,8 +4731,7 @@ takes four spaces (a common case), but diverge in other cases. A [list](@) is a sequence of one or more list items [of the same type]. The list items -may be separated by single [blank lines], but two -blank lines end all containing lists. +may be separated by any number of blank lines. Two list items are [of the same type](@) if they begin with a [list marker] of the same type. @@ -4809,10 +4809,11 @@ Foo `Markdown.pl` does not allow this, through fear of triggering a list via a numeral in a hard-wrapped line: -```````````````````````````````` markdown +``` markdown The number of windows in my house is 14. The number of doors is 6. -```````````````````````````````` +``` + Oddly, though, `Markdown.pl` *does* allow a blockquote to interrupt a paragraph, even though the same considerations might apply. @@ -4821,10 +4822,12 @@ In CommonMark, we do allow lists to interrupt paragraphs, for two reasons. First, it is natural and not uncommon for people to start lists without blank lines: - I need to buy - - new shoes - - a coat - - a plane ticket +``` markdown +I need to buy +- new shoes +- a coat +- a plane ticket +``` Second, we are attracted to a @@ -4836,20 +4839,24 @@ Second, we are attracted to a (Indeed, the spec for [list items] and [block quotes] presupposes this principle.) This principle implies that if - * I need to buy - - new shoes - - a coat - - a plane ticket +``` markdown + * I need to buy + - new shoes + - a coat + - a plane ticket +``` is a list item containing a paragraph followed by a nested sublist, as all Markdown implementations agree it is (though the paragraph may be rendered without `

    ` tags, since the list is "tight"), then - I need to buy - - new shoes - - a coat - - a plane ticket +``` markdown +I need to buy +- new shoes +- a coat +- a plane ticket +``` by itself should be a paragraph followed by a nested sublist. @@ -5671,6 +5678,16 @@ single spaces, just as they would be by a browser: ```````````````````````````````` +Not all [Unicode whitespace] (for instance, non-breaking space) is +collapsed, however: + +```````````````````````````````` example +`a  b` +. +

    a  b

    +```````````````````````````````` + + Q: Why not just leave the spaces, since browsers will collapse them anyway? A: Because we might be targeting a non-HTML format, and we shouldn't rely on HTML-specific rendering assumptions. @@ -6558,7 +6575,7 @@ Note that in the preceding case, the interpretation is precluded by the condition that a delimiter that -can both open and close (like the `*` after `foo` +can both open and close (like the `*` after `foo`) cannot form emphasis if the sum of the lengths of the delimiter runs containing the opening and closing delimiters is a multiple of 3. @@ -6590,12 +6607,6 @@ omitted: ```````````````````````````````` -```````````````````````````````` example -*foo**bar*** -. -

    foobar

    -```````````````````````````````` - Indefinite levels of nesting are possible: ```````````````````````````````` example @@ -7361,6 +7372,16 @@ may be used in titles: ```````````````````````````````` +Titles must be separated from the link using a [whitespace]. +Other [Unicode whitespace] like non-breaking space doesn't work. + +```````````````````````````````` example +[link](/url "title") +. +

    link

    +```````````````````````````````` + + Nested balanced quotes are not allowed without escaping: ```````````````````````````````` example @@ -8025,7 +8046,8 @@ following closing bracket: ```````````````````````````````` -Full references take precedence over shortcut references: +Full and compact references take precedence over shortcut +references: ```````````````````````````````` example [foo][bar] @@ -8036,6 +8058,31 @@ Full references take precedence over shortcut references:

    foo

    ```````````````````````````````` +```````````````````````````````` example +[foo][] + +[foo]: /url1 +. +

    foo

    +```````````````````````````````` + +Inline links also take precedence: + +```````````````````````````````` example +[foo]() + +[foo]: /url1 +. +

    foo

    +```````````````````````````````` + +```````````````````````````````` example +[foo](not a link) + +[foo]: /url1 +. +

    foo(not a link)

    +```````````````````````````````` In the following case `[bar][baz]` is parsed as a reference, `[foo]` as normal text: @@ -9045,7 +9092,7 @@ blocks. But we cannot close unmatched blocks yet, because we may have a [lazy continuation line]. 2. Next, after consuming the continuation markers for existing -blocks, we look for new block starts (e.g. `>` for a block quote. +blocks, we look for new block starts (e.g. `>` for a block quote). If we encounter a new block start, we close any blocks unmatched in step 1 before creating the new block as a child of the last matched block. From 1e629f50eafecd0ac7cbdddfab818874412381c1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 29 Nov 2016 15:55:29 +1100 Subject: [PATCH 201/815] Treat h2..h6 as HTML blocks well (spec 0.27, #73) --- .../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 0e5f4fafd..c80822d3c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -39,7 +39,7 @@ public class HtmlBlockParser extends AbstractBlockParser { "caption|center|col|colgroup|" + "dd|details|dialog|dir|div|dl|dt|" + "fieldset|figcaption|figure|footer|form|frame|frameset|" + - "h1|head|header|hr|html|" + + "h1|h2|h3|h4|h5|h6|head|header|hr|html|" + "iframe|" + "legend|li|link|" + "main|menu|menuitem|meta|" + From 5bffa951284b2d15ec211c6c7049f8a7c8a3c75a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 29 Nov 2016 16:08:26 +1100 Subject: [PATCH 202/815] Adjust to change in precedence of links (spec 0.27, #73) --- .../org/commonmark/internal/InlineParserImpl.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index cba11cb5a..49b9df92d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -514,7 +514,7 @@ private boolean parseCloseBracket() { String title = null; boolean isLinkOrImage = false; - // Inline link? + // Maybe a inline link like `[foo](/uri "title")` if (peek() == '(') { index++; spnl(); @@ -528,11 +528,16 @@ private boolean parseCloseBracket() { if (peek() == ')') { index++; isLinkOrImage = true; + } else { + index = startIndex; } } - } else { // maybe reference link + } + + // Maybe a reference link like `[foo][bar]`, `[foo][]` or `[foo]` + if (!isLinkOrImage) { - // See if there's a link label + // See if there's a link label like `[bar]` or `[]` int beforeLabel = index; int labelLength = parseLinkLabel(); String ref = null; From 36172e6e58cd230568d36218a35f623db6740c8e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 9 Dec 2016 12:42:17 +1100 Subject: [PATCH 203/815] Pass HTML tag name to AttributeProvider and add call for `pre` tag (#74) This enables us to call it for both elements of a code block (`pre` and `code`), allowing users to add attributes for `pre` as well. --- README.md | 2 +- .../StrikethroughHtmlNodeRenderer.java | 2 +- .../tables/internal/TableNodeRenderer.java | 28 +++++++-------- .../commonmark/ext/gfm/tables/TablesTest.java | 2 +- .../internal/HeadingIdAttributeProvider.java | 2 +- .../ext/ins/internal/InsNodeRenderer.java | 2 +- .../renderer/html/AttributeProvider.java | 10 ++++-- .../renderer/html/CoreHtmlNodeRenderer.java | 36 +++++++++---------- .../html/HtmlNodeRendererContext.java | 5 +-- .../renderer/html/HtmlRenderer.java | 8 ++--- .../org/commonmark/test/HtmlRendererTest.java | 14 ++++---- .../org/commonmark/test/UsageExampleTest.java | 2 +- 12 files changed, 60 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 6c700afc5..efe27eea8 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ renderer.render(document); class ImageAttributeProvider implements AttributeProvider { @Override - public void setAttributes(Node node, Map attributes) { + public void setAttributes(Node node, String tagName, Map attributes) { if (node instanceof Image) { attributes.put("class", "border"); } 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 b818a87fa..4dd0de39b 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 @@ -19,7 +19,7 @@ public StrikethroughHtmlNodeRenderer(HtmlNodeRendererContext context) { @Override public void render(Node node) { - Map attributes = context.extendAttributes(node, Collections.emptyMap()); + Map attributes = context.extendAttributes(node, "del", Collections.emptyMap()); html.tag("del", attributes); renderChildren(node); html.tag("/del"); 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 b93fdeb5a..1bff26e19 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,10 +1,10 @@ package org.commonmark.ext.gfm.tables.internal; import org.commonmark.ext.gfm.tables.*; -import org.commonmark.renderer.html.HtmlWriter; -import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; import java.util.*; @@ -46,7 +46,7 @@ public void render(Node node) { private void renderBlock(TableBlock tableBlock) { htmlWriter.line(); - htmlWriter.tag("table", getAttributes(tableBlock)); + htmlWriter.tag("table", getAttributes(tableBlock, "table")); renderChildren(tableBlock); htmlWriter.tag("/table"); htmlWriter.line(); @@ -54,7 +54,7 @@ private void renderBlock(TableBlock tableBlock) { private void renderHead(TableHead tableHead) { htmlWriter.line(); - htmlWriter.tag("thead", getAttributes(tableHead)); + htmlWriter.tag("thead", getAttributes(tableHead, "thead")); renderChildren(tableHead); htmlWriter.tag("/thead"); htmlWriter.line(); @@ -62,7 +62,7 @@ private void renderHead(TableHead tableHead) { private void renderBody(TableBody tableBody) { htmlWriter.line(); - htmlWriter.tag("tbody", getAttributes(tableBody)); + htmlWriter.tag("tbody", getAttributes(tableBody, "tbody")); renderChildren(tableBody); htmlWriter.tag("/tbody"); htmlWriter.line(); @@ -70,28 +70,28 @@ private void renderBody(TableBody tableBody) { private void renderRow(TableRow tableRow) { htmlWriter.line(); - htmlWriter.tag("tr", getAttributes(tableRow)); + htmlWriter.tag("tr", getAttributes(tableRow, "tr")); renderChildren(tableRow); htmlWriter.tag("/tr"); htmlWriter.line(); } private void renderCell(TableCell tableCell) { - String tag = tableCell.isHeader() ? "th" : "td"; - htmlWriter.tag(tag, getCellAttributes(tableCell)); + String tagName = tableCell.isHeader() ? "th" : "td"; + htmlWriter.tag(tagName, getCellAttributes(tableCell, tagName)); renderChildren(tableCell); - htmlWriter.tag("/" + tag); + htmlWriter.tag("/" + tagName); } - private Map getAttributes(Node node) { - return context.extendAttributes(node, Collections.emptyMap()); + private Map getAttributes(Node node, String tagName) { + return context.extendAttributes(node, tagName, Collections.emptyMap()); } - private Map getCellAttributes(TableCell tableCell) { + private Map getCellAttributes(TableCell tableCell, String tagName) { if (tableCell.getAlignment() != null) { - return context.extendAttributes(tableCell, Collections.singletonMap("align", getAlignValue(tableCell.getAlignment()))); + return context.extendAttributes(tableCell, tagName, Collections.singletonMap("align", getAlignValue(tableCell.getAlignment()))); } else { - return context.extendAttributes(tableCell, Collections.emptyMap()); + return context.extendAttributes(tableCell, tagName, Collections.emptyMap()); } } 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 df777a2a9..9925959f4 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 @@ -315,7 +315,7 @@ public void attributeProviderIsApplied() { public AttributeProvider create(AttributeProviderContext context) { return new AttributeProvider() { @Override - public void setAttributes(Node node, Map attributes) { + public void setAttributes(Node node, String tagName, Map attributes) { if (node instanceof TableBlock) { attributes.put("test", "block"); } else if (node instanceof TableHead) { diff --git a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java index 1b6f74480..6b8792bd5 100644 --- a/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java +++ b/commonmark-ext-heading-anchor/src/main/java/org/commonmark/ext/heading/anchor/internal/HeadingIdAttributeProvider.java @@ -25,7 +25,7 @@ public static HeadingIdAttributeProvider create(String defaultId, String prefix, } @Override - public void setAttributes(Node node, final Map attributes) { + public void setAttributes(Node node, String tagName, final Map attributes) { if (node instanceof Heading) { 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 66c9dcab9..faf15cae7 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 @@ -27,7 +27,7 @@ public Set> getNodeTypes() { @Override public void render(Node node) { - Map attributes = context.extendAttributes(node, Collections.emptyMap()); + Map attributes = context.extendAttributes(node, "ins", Collections.emptyMap()); html.tag("ins", attributes); renderChildren(node); html.tag("/ins"); diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java index e56975a42..24a471d46 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/AttributeProvider.java @@ -5,21 +5,25 @@ import java.util.Map; /** - * Extension point for adding/changing attributes on the primary HTML tag for a node. + * Extension point for adding/changing attributes on HTML tags for a node. */ public interface AttributeProvider { /** - * Set the attributes for the node by modifying the provided map. + * Set the attributes for a HTML tag of the specified node by modifying the provided map. *

    * This allows to change or even remove default attributes. With great power comes great responsibility. *

    * The attribute key and values will be escaped (preserving character entities), so don't escape them here, * otherwise they will be double-escaped. + *

    + * This method may be called multiple times for the same node, if the node is rendered using multiple nested + * tags (e.g. code blocks). * * @param node the node to set attributes for + * @param tagName the HTML tag name that these attributes are for (e.g. {@code h1}, {@code pre}, {@code code}). * @param attributes the attributes, with any default attributes already set in the map */ - void setAttributes(Node node, Map attributes); + void setAttributes(Node node, String tagName, Map attributes); } 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 2a97e668a..0ab59a1fc 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -59,7 +59,7 @@ public void visit(Document document) { public void visit(Heading heading) { String htag = "h" + heading.getLevel(); html.line(); - html.tag(htag, getAttrs(heading)); + html.tag(htag, getAttrs(heading, htag)); visitChildren(heading); html.tag('/' + htag); html.line(); @@ -70,7 +70,7 @@ public void visit(Paragraph paragraph) { boolean inTightList = isInTightList(paragraph); if (!inTightList) { html.line(); - html.tag("p", getAttrs(paragraph)); + html.tag("p", getAttrs(paragraph, "p")); } visitChildren(paragraph); if (!inTightList) { @@ -82,7 +82,7 @@ public void visit(Paragraph paragraph) { @Override public void visit(BlockQuote blockQuote) { html.line(); - html.tag("blockquote", getAttrs(blockQuote)); + html.tag("blockquote", getAttrs(blockQuote, "blockquote")); html.line(); visitChildren(blockQuote); html.line(); @@ -92,7 +92,7 @@ public void visit(BlockQuote blockQuote) { @Override public void visit(BulletList bulletList) { - renderListBlock(bulletList, "ul", getAttrs(bulletList)); + renderListBlock(bulletList, "ul", getAttrs(bulletList, "ul")); } @Override @@ -110,7 +110,7 @@ public void visit(FencedCodeBlock fencedCodeBlock) { } attributes.put("class", "language-" + language); } - renderCodeBlock(literal, getAttrs(fencedCodeBlock, attributes)); + renderCodeBlock(literal, fencedCodeBlock, attributes); } @Override @@ -127,13 +127,13 @@ public void visit(HtmlBlock htmlBlock) { @Override public void visit(ThematicBreak thematicBreak) { html.line(); - html.tag("hr", getAttrs(thematicBreak), true); + html.tag("hr", getAttrs(thematicBreak, "hr"), true); html.line(); } @Override public void visit(IndentedCodeBlock indentedCodeBlock) { - renderCodeBlock(indentedCodeBlock.getLiteral(), getAttrs(indentedCodeBlock)); + renderCodeBlock(indentedCodeBlock.getLiteral(), indentedCodeBlock, Collections.emptyMap()); } @Override @@ -144,14 +144,14 @@ public void visit(Link link) { if (link.getTitle() != null) { attrs.put("title", link.getTitle()); } - html.tag("a", getAttrs(link, attrs)); + html.tag("a", getAttrs(link, "a", attrs)); visitChildren(link); html.tag("/a"); } @Override public void visit(ListItem listItem) { - html.tag("li", getAttrs(listItem)); + html.tag("li", getAttrs(listItem, "li")); visitChildren(listItem); html.tag("/li"); html.line(); @@ -164,7 +164,7 @@ public void visit(OrderedList orderedList) { if (start != 1) { attrs.put("start", String.valueOf(start)); } - renderListBlock(orderedList, "ol", getAttrs(orderedList, attrs)); + renderListBlock(orderedList, "ol", getAttrs(orderedList, "ol", attrs)); } @Override @@ -182,7 +182,7 @@ public void visit(Image image) { attrs.put("title", image.getTitle()); } - html.tag("img", getAttrs(image, attrs), true); + html.tag("img", getAttrs(image, "img", attrs), true); } @Override @@ -241,10 +241,10 @@ protected void visitChildren(Node parent) { } } - private void renderCodeBlock(String literal, Map attributes) { + private void renderCodeBlock(String literal, Node node, Map attributes) { html.line(); - html.tag("pre"); - html.tag("code", attributes); + html.tag("pre", getAttrs(node, "pre")); + html.tag("code", getAttrs(node, "code", attributes)); html.text(literal); html.tag("/code"); html.tag("/pre"); @@ -273,12 +273,12 @@ private boolean isInTightList(Paragraph paragraph) { return false; } - private Map getAttrs(Node node) { - return context.extendAttributes(node, Collections.emptyMap()); + private Map getAttrs(Node node, String tagName) { + return getAttrs(node, tagName, Collections.emptyMap()); } - private Map getAttrs(Node node, Map defaultAttributes) { - return context.extendAttributes(node, defaultAttributes); + private Map getAttrs(Node node, String tagName, Map defaultAttributes) { + return context.extendAttributes(node, tagName, defaultAttributes); } private static class AltTextVisitor extends AbstractVisitor { 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 26b2d528d..fd077a277 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java @@ -13,13 +13,14 @@ public interface HtmlNodeRendererContext { String encodeUrl(String url); /** - * Extend the attributes by extensions. + * 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 attributes the attributes that were calculated by the renderer * @return the extended attributes with added/updated/removed entries */ - Map extendAttributes(Node node, Map attributes); + Map extendAttributes(Node node, String tagName, Map attributes); /** * @return the HTML writer to use 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 59a3104b6..b830e8b3e 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -222,9 +222,9 @@ public String encodeUrl(String url) { } @Override - public Map extendAttributes(Node node, Map attributes) { + public Map extendAttributes(Node node, String tagName, Map attributes) { Map attrs = new LinkedHashMap<>(attributes); - setCustomAttributes(node, attrs); + setCustomAttributes(node, tagName, attrs); return attrs; } @@ -243,9 +243,9 @@ public void render(Node node) { nodeRendererMap.render(node); } - private void setCustomAttributes(Node node, Map attrs) { + private void setCustomAttributes(Node node, String tagName, Map attrs) { for (AttributeProvider attributeProvider : attributeProviders) { - attributeProvider.setAttributes(node, attrs); + attributeProvider.setAttributes(node, tagName, attrs); } } } diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index b314a1911..82de13d74 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -92,13 +92,15 @@ public void attributeProviderForCodeBlock() { public AttributeProvider create(AttributeProviderContext context) { return new AttributeProvider() { @Override - public void setAttributes(Node node, Map attributes) { - if (node instanceof FencedCodeBlock) { + public void setAttributes(Node node, String tagName, Map attributes) { + if (node instanceof FencedCodeBlock && tagName.equals("code")) { FencedCodeBlock fencedCodeBlock = (FencedCodeBlock) node; // Remove the default attribute for info attributes.remove("class"); // Put info in custom attribute instead attributes.put("data-custom", fencedCodeBlock.getInfo()); + } else if (node instanceof FencedCodeBlock && tagName.equals("pre")) { + attributes.put("data-code-block", "fenced"); } } }; @@ -107,10 +109,10 @@ public void setAttributes(Node node, Map attributes) { HtmlRenderer renderer = HtmlRenderer.builder().attributeProviderFactory(custom).build(); String rendered = renderer.render(parse("```info\ncontent\n```")); - assertEquals("

    content\n
    \n", rendered); + assertEquals("
    content\n
    \n", rendered); String rendered2 = renderer.render(parse("```evil\"\ncontent\n```")); - assertEquals("
    content\n
    \n", rendered2); + assertEquals("
    content\n
    \n", rendered2); } @Test @@ -120,7 +122,7 @@ public void attributeProviderForImage() { public AttributeProvider create(AttributeProviderContext context) { return new AttributeProvider() { @Override - public void setAttributes(Node node, Map attributes) { + public void setAttributes(Node node, String tagName, Map attributes) { if (node instanceof Image) { attributes.remove("alt"); attributes.put("test", "hey"); @@ -144,7 +146,7 @@ public AttributeProvider create(AttributeProviderContext context) { int i = 0; @Override - public void setAttributes(Node node, Map attributes) { + public void setAttributes(Node node, String tagName, Map attributes) { attributes.put("key", "" + i); i++; } diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 5d048f611..6109c31cf 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -80,7 +80,7 @@ public void visit(Text text) { class ImageAttributeProvider implements AttributeProvider { @Override - public void setAttributes(Node node, Map attributes) { + public void setAttributes(Node node, String tagName, Map attributes) { if (node instanceof Image) { attributes.put("class", "border"); } From e272c3fa918ec0e3c059dbc0435279055ed44693 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 9 Dec 2016 12:47:51 +1100 Subject: [PATCH 204/815] Add support for providing HTML attributes for em, strong, code and br tags --- .../commonmark/renderer/html/CoreHtmlNodeRenderer.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 0ab59a1fc..41f208813 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -187,14 +187,14 @@ public void visit(Image image) { @Override public void visit(Emphasis emphasis) { - html.tag("em"); + html.tag("em", getAttrs(emphasis, "em")); visitChildren(emphasis); html.tag("/em"); } @Override public void visit(StrongEmphasis strongEmphasis) { - html.tag("strong"); + html.tag("strong", getAttrs(strongEmphasis, "strong")); visitChildren(strongEmphasis); html.tag("/strong"); } @@ -206,7 +206,7 @@ public void visit(Text text) { @Override public void visit(Code code) { - html.tag("code"); + html.tag("code", getAttrs(code, "code")); html.text(code.getLiteral()); html.tag("/code"); } @@ -227,7 +227,7 @@ public void visit(SoftLineBreak softLineBreak) { @Override public void visit(HardLineBreak hardLineBreak) { - html.tag("br", null, true); + html.tag("br", getAttrs(hardLineBreak, "br"), true); html.line(); } From 96b7143f7f930141594a45bec48d3c50e099cd01 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 9 Dec 2016 13:00:42 +1100 Subject: [PATCH 205/815] ext-autolink: Bump version of autolink dependency No changes for affecting the extension. --- 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 17e1c7fec..c33cd352c 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.5.0 + 0.6.0 From 75fb925e18be427a01109652761bf7d2c74f9d91 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 9 Dec 2016 13:11:19 +1100 Subject: [PATCH 206/815] [maven-release-plugin] prepare release commonmark-parent-0.8.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-ins/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 | 18 +++++++++--------- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index c33cd352c..ce832d743 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 529d5e192..11fd4499f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 13ebb4369..87a059e9f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index e11f981e0..00068c77c 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index fcfa919bd..516919566 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index a633431b8..219ead35f 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.7.2-SNAPSHOT + 0.8.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 045d83392..9a9282490 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index fb80f98fe..a31c38877 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index b1a0fe489..0efd8f38b 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark diff --git a/pom.xml b/pom.xml index e1e4dbdc8..56bae87bc 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.7.2-SNAPSHOT + 0.8.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -86,37 +86,37 @@ com.atlassian.commonmark commonmark - 0.7.2-SNAPSHOT + 0.8.0 com.atlassian.commonmark commonmark-ext-autolink - 0.7.2-SNAPSHOT + 0.8.0 com.atlassian.commonmark commonmark-ext-ins - 0.7.2-SNAPSHOT + 0.8.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.7.2-SNAPSHOT + 0.8.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.7.2-SNAPSHOT + 0.8.0 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.7.2-SNAPSHOT + 0.8.0 com.atlassian.commonmark commonmark-test-util - 0.7.2-SNAPSHOT + 0.8.0 @@ -194,7 +194,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.8.0 From 89913ea9daf16f4ef4902f1cf08c1e03f4cccb11 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 9 Dec 2016 13:11:20 +1100 Subject: [PATCH 207/815] [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-ins/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 | 18 +++++++++--------- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index ce832d743..7dcddcff8 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 11fd4499f..abfc83820 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 87a059e9f..8ef587081 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 00068c77c..5385f9987 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 516919566..ac62df362 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 219ead35f..bef628c71 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.8.0 + 0.8.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 9a9282490..632ab9cc8 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index a31c38877..2c5356a86 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 0efd8f38b..f41813bdc 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 56bae87bc..4ebcc9426 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.0 + 0.8.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -86,37 +86,37 @@ com.atlassian.commonmark commonmark - 0.8.0 + 0.8.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.8.0 + 0.8.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-ins - 0.8.0 + 0.8.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.8.0 + 0.8.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.8.0 + 0.8.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.8.0 + 0.8.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.8.0 + 0.8.1-SNAPSHOT @@ -194,7 +194,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.8.0 + HEAD From 060d9bc40d3f258806923247f9de490b795bf682 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 9 Dec 2016 13:32:46 +1100 Subject: [PATCH 208/815] Bump versions --- README.md | 4 ++-- commonmark-android-test/README.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index efe27eea8..3dcaeb9fe 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.7.1 + 0.8.0 ``` @@ -211,7 +211,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.7.1 + 0.8.0 ``` diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index cb56a39fa..3c7539bcb 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,14 +28,14 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.7.1 +version.maven=0.8.0 # Version number of autolink in maven central (not bundled with extension jar). -version.maven_autolink=0.5.0 +version.maven_autolink=0.6.0 # Version number of commonmark and extensions in project. -version.snapshot=0.7.1-SNAPSHOT +version.snapshot=0.8.0-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). -version.snapshot_autolink=0.5.0 +version.snapshot_autolink=0.6.0 ``` If you're going to test on device with Android 15 then you can skip downloading emulator. From 0269288123d26a8be2903144103cc3df18907f00 Mon Sep 17 00:00:00 2001 From: Graham Bell Date: Mon, 24 Oct 2016 13:27:06 -0600 Subject: [PATCH 209/815] Added ability to include/use only specific core block factories via the Parser builder Signed-off-by: Cody Sehl --- .../commonmark/internal/DocumentParser.java | 40 +++++++++---- .../java/org/commonmark/parser/Parser.java | 35 ++++++++++- .../internal/DocumentParserTest.java | 58 +++++++++++++++++++ .../java/org/commonmark/test/ParserTest.java | 26 ++++++++- 4 files changed, 145 insertions(+), 14 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 942da3ca6..9091b2dce 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -12,14 +12,28 @@ public class DocumentParser implements ParserState { - private static List CORE_FACTORIES = Arrays.asList( - new BlockQuoteParser.Factory(), - new HeadingParser.Factory(), - new FencedCodeBlockParser.Factory(), - new HtmlBlockParser.Factory(), - new ThematicBreakParser.Factory(), - new ListBlockParser.Factory(), - new IndentedCodeBlockParser.Factory()); + private static final List> CORE_FACTORY_TYPES = Arrays.>asList( + BlockQuote.class, + Heading.class, + FencedCodeBlock.class, + HtmlBlock.class, + ThematicBreak.class, + ListBlock.class, + IndentedCodeBlock.class); + + private static final Map, BlockParserFactory> NODES_TO_CORE_FACTORIES; + static { + Map, BlockParserFactory> map = new HashMap<>(); + map.put(BlockQuote.class, new BlockQuoteParser.Factory()); + map.put(Heading.class, new HeadingParser.Factory()); + map.put(FencedCodeBlock.class, new FencedCodeBlockParser.Factory()); + map.put(HtmlBlock.class, new HtmlBlockParser.Factory()); + map.put(ThematicBreak.class, new ThematicBreakParser.Factory()); + map.put(ListBlock.class, new ListBlockParser.Factory()); + map.put(IndentedCodeBlock.class, new IndentedCodeBlockParser.Factory()); + NODES_TO_CORE_FACTORIES = Collections.unmodifiableMap(map); + } + private CharSequence line; @@ -59,11 +73,17 @@ public DocumentParser(List blockParserFactories, InlineParse activateBlockParser(this.documentBlockParser); } - public static List calculateBlockParserFactories(List customBlockParserFactories) { + public static List> getDefaultBlockParserTypes() { + return CORE_FACTORY_TYPES; + } + + public static List calculateBlockParserFactories(List customBlockParserFactories, List> allowedBlockTypes) { List list = new ArrayList<>(); // By having the custom factories come first, extensions are able to change behavior of core syntax. list.addAll(customBlockParserFactories); - list.addAll(DocumentParser.CORE_FACTORIES); + for (Class blockType : allowedBlockTypes) { + list.add(NODES_TO_CORE_FACTORIES.get(blockType)); + } return list; } diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 57ac0ad0c..fb63e957a 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.Extension; import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserImpl; +import org.commonmark.node.Block; import org.commonmark.node.Node; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -32,7 +33,7 @@ public class Parser { private final List postProcessors; private Parser(Builder builder) { - this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories); + this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.allowedBlockTypes); this.delimiterProcessors = InlineParserImpl.calculateDelimiterProcessors(builder.delimiterProcessors); this.delimiterCharacters = InlineParserImpl.calculateDelimiterCharacters(delimiterProcessors.keySet()); this.specialCharacters = InlineParserImpl.calculateSpecialCharacters(delimiterCharacters); @@ -93,6 +94,7 @@ public static class Builder { private final List blockParserFactories = new ArrayList<>(); private final List delimiterProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); + private List> allowedBlockTypes = DocumentParser.getDefaultBlockParserTypes(); /** * @return the configured {@link Parser} @@ -115,6 +117,37 @@ public Builder extensions(Iterable extensions) { return this; } + /** + * Describe the list of markdown features the parser will recognize and parse. + * + * By default, Commonmark will recognize and parse the following set of core markdown features: + * + * Heading (#) - Heading.class + * HTML () - HtmlBlock.class + * Horizontal Rule / Thematic Break (---) - ThematicBreak.class + * Fenced Code Block (```) - FencedCodeBlock.class + * Indented Code Block - IndentedCodeBlock.class + * Block Quote (>) - BlockQuote.class + * Ordered / Unordered List (>) - ListBlock.class + * + * To parse only a subset of the features listed above, pass a list of each feature's associated Node class. + * E.g., to only parse Headings and Lists: + *
    +         *     {@code
    +         *     Parser.builder().allowedBlockTypes(Heading.class, ListBlock.class);
    +         *     }
    +         * 
    + * + * @param allowedBlockTypes A list of nodes the parser will parse. + * If this list is empty, the parser will not recognize any Commonmark core markdown features. + * + * @return {@code this} + */ + public Builder allowedBlockTypes(List> allowedBlockTypes) { + this.allowedBlockTypes = allowedBlockTypes; + return this; + } + /** * Adds a custom block parser factory. *

    diff --git a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java new file mode 100644 index 000000000..a1cc3e0bf --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java @@ -0,0 +1,58 @@ +package org.commonmark.internal; + +import org.commonmark.node.*; +import org.commonmark.parser.block.BlockParserFactory; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +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( + new BlockQuoteParser.Factory(), + new HeadingParser.Factory(), + new FencedCodeBlockParser.Factory(), + new HtmlBlockParser.Factory(), + new ThematicBreakParser.Factory(), + new ListBlockParser.Factory(), + new IndentedCodeBlockParser.Factory()); + + @Test + public void calculateBlockParserFactories_givenAFullListOfAllowedNodes_includesAllCoreFactories() { + List customParserFactories = Collections.emptyList(); + List> nodes = Arrays.asList(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class); + + List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); + assertThat(blockParserFactories.size(), is(CORE_FACTORIES.size())); + + for (BlockParserFactory factory : CORE_FACTORIES) { + assertTrue(hasInstance(blockParserFactories, factory.getClass())); + } + } + + @Test + public void calculateBlockParserFactories_givenAListOfAllowedNodes_includesAssociatedFactories() { + List customParserFactories = Collections.emptyList(); + List> nodes = Collections.>singletonList(IndentedCodeBlock.class); + + List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); + + assertThat(blockParserFactories.size(), is(1)); + assertTrue(hasInstance(blockParserFactories, IndentedCodeBlockParser.Factory.class)); + } + + private boolean hasInstance(List blockParserFactories, Class factoryClass) { + for (BlockParserFactory factory : blockParserFactories) { + if (factory.getClass().equals(factoryClass)) { + return true; + } + } + return false; + } + +} diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 832b76e85..63649c0e6 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,18 +1,19 @@ package org.commonmark.test; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.commonmark.parser.block.*; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.spec.SpecReader; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Collections; +import java.util.List; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -47,6 +48,25 @@ public void customBlockParserFactory() { assertThat(document.getLastChild(), instanceOf(DashBlock.class)); } + @Test + public void allowedBlockTypes() { + String given = "# heading 1\n\nnot a heading"; + + Parser parser = Parser.builder().build(); // all core parsers by default + Node document = parser.parse(given); + assertThat(document.getFirstChild(), instanceOf(Heading.class)); + + List headersOnly = Collections.singletonList(Heading.class); + parser = Parser.builder().allowedBlockTypes(headersOnly).build(); + document = parser.parse(given); + assertThat(document.getFirstChild(), instanceOf(Heading.class)); + + List noCoreTypes = Collections.emptyList(); + parser = Parser.builder().allowedBlockTypes(noCoreTypes).build(); + document = parser.parse(given); + assertThat(document.getFirstChild(), not(instanceOf(Heading.class))); + } + @Test public void indentation() { String given = " - 1 space\n - 3 spaces\n - 5 spaces\n\t - tab + space"; From 34e43c39bcbe5a7eea48cac72028b469100a0d8a Mon Sep 17 00:00:00 2001 From: Mark Sliva Date: Tue, 3 Jan 2017 17:53:55 -0700 Subject: [PATCH 210/815] Address PR comments Styling/formatting to docs on org.commonmark.parser Parser.Builder enabledBlockTypes Change list of enabledBlockTypes to be a Set Signed-off-by: Cody Sehl --- .../commonmark/internal/DocumentParser.java | 20 ++++++--- .../java/org/commonmark/parser/Parser.java | 45 ++++++++++--------- .../internal/DocumentParserTest.java | 7 ++- .../java/org/commonmark/test/ParserTest.java | 25 ++++++----- 4 files changed, 57 insertions(+), 40 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 9091b2dce..d469aad62 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -8,18 +8,26 @@ import org.commonmark.node.*; import org.commonmark.parser.block.*; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.LinkedHashSet; +import java.util.HashSet; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; public class DocumentParser implements ParserState { - private static final List> CORE_FACTORY_TYPES = Arrays.>asList( + private static final Set> CORE_FACTORY_TYPES = new LinkedHashSet<>(Arrays.asList( BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, - IndentedCodeBlock.class); + IndentedCodeBlock.class)); private static final Map, BlockParserFactory> NODES_TO_CORE_FACTORIES; static { @@ -73,15 +81,15 @@ public DocumentParser(List blockParserFactories, InlineParse activateBlockParser(this.documentBlockParser); } - public static List> getDefaultBlockParserTypes() { + public static Set> getDefaultBlockParserTypes() { return CORE_FACTORY_TYPES; } - public static List calculateBlockParserFactories(List customBlockParserFactories, List> allowedBlockTypes) { + public static List calculateBlockParserFactories(List customBlockParserFactories, Set> enabledBlockTypes) { List list = new ArrayList<>(); // By having the custom factories come first, extensions are able to change behavior of core syntax. list.addAll(customBlockParserFactories); - for (Class blockType : allowedBlockTypes) { + for (Class blockType : enabledBlockTypes) { list.add(NODES_TO_CORE_FACTORIES.get(blockType)); } return list; diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index fb63e957a..ea6a6c72a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -2,18 +2,19 @@ import java.io.IOException; import java.io.Reader; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.commonmark.Extension; import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.node.Block; -import org.commonmark.node.Node; +import org.commonmark.node.*; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.List; -import java.util.Map; /** * Parses input text to a tree of nodes. @@ -33,7 +34,7 @@ public class Parser { private final List postProcessors; private Parser(Builder builder) { - this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.allowedBlockTypes); + this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); this.delimiterProcessors = InlineParserImpl.calculateDelimiterProcessors(builder.delimiterProcessors); this.delimiterCharacters = InlineParserImpl.calculateDelimiterCharacters(delimiterProcessors.keySet()); this.specialCharacters = InlineParserImpl.calculateSpecialCharacters(delimiterCharacters); @@ -94,7 +95,7 @@ public static class Builder { private final List blockParserFactories = new ArrayList<>(); private final List delimiterProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); - private List> allowedBlockTypes = DocumentParser.getDefaultBlockParserTypes(); + private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); /** * @return the configured {@link Parser} @@ -119,32 +120,36 @@ public Builder extensions(Iterable extensions) { /** * Describe the list of markdown features the parser will recognize and parse. - * + *

    * By default, Commonmark will recognize and parse the following set of core markdown features: * - * Heading (#) - Heading.class - * HTML () - HtmlBlock.class - * Horizontal Rule / Thematic Break (---) - ThematicBreak.class - * Fenced Code Block (```) - FencedCodeBlock.class - * Indented Code Block - IndentedCodeBlock.class - * Block Quote (>) - BlockQuote.class - * Ordered / Unordered List (>) - ListBlock.class + *

      + *
    • {@link Heading} ({@code #}) + *
    • {@link HtmlBlock} ({@code }) + *
    • {@link ThematicBreak} (Horizontal Rule) ({@code ---}) + *
    • {@link FencedCodeBlock} ({@code ```}) + *
    • {@link IndentedCodeBlock} + *
    • {@link BlockQuote} ({@code >}) + *
    • {@link ListBlock} (Ordered / Unordered List) ({@code 1. / *}) + *
    * + *

    * To parse only a subset of the features listed above, pass a list of each feature's associated Node class. + *

    * E.g., to only parse Headings and Lists: *

              *     {@code
    -         *     Parser.builder().allowedBlockTypes(Heading.class, ListBlock.class);
    +         *     Parser.builder().enabledBlockTypes(Heading.class, ListBlock.class);
              *     }
              * 
    * - * @param allowedBlockTypes A list of nodes the parser will parse. + * @param enabledBlockTypes A list of nodes the parser will parse. * If this list is empty, the parser will not recognize any Commonmark core markdown features. * * @return {@code this} */ - public Builder allowedBlockTypes(List> allowedBlockTypes) { - this.allowedBlockTypes = allowedBlockTypes; + public Builder enabledBlockTypes(Set> enabledBlockTypes) { + this.enabledBlockTypes = enabledBlockTypes; return this; } diff --git a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java index a1cc3e0bf..c4d848362 100644 --- a/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/DocumentParserTest.java @@ -7,6 +7,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Set; +import java.util.HashSet; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -25,7 +27,7 @@ public class DocumentParserTest { @Test public void calculateBlockParserFactories_givenAFullListOfAllowedNodes_includesAllCoreFactories() { List customParserFactories = Collections.emptyList(); - List> nodes = Arrays.asList(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class); + Set> nodes = new HashSet<>(Arrays.asList(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class)); List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); assertThat(blockParserFactories.size(), is(CORE_FACTORIES.size())); @@ -38,7 +40,8 @@ public void calculateBlockParserFactories_givenAFullListOfAllowedNodes_includesA @Test public void calculateBlockParserFactories_givenAListOfAllowedNodes_includesAssociatedFactories() { List customParserFactories = Collections.emptyList(); - List> nodes = Collections.>singletonList(IndentedCodeBlock.class); + Set> nodes = new HashSet<>(); + nodes.add(IndentedCodeBlock.class); List blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 63649c0e6..aa0d7d339 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -10,8 +10,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.Collections; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertEquals; @@ -47,22 +47,23 @@ public void customBlockParserFactory() { assertEquals("hey", ((Text) document.getFirstChild().getFirstChild()).getLiteral()); assertThat(document.getLastChild(), instanceOf(DashBlock.class)); } - + @Test - public void allowedBlockTypes() { + public void enabledBlockTypes() { String given = "# heading 1\n\nnot a heading"; Parser parser = Parser.builder().build(); // all core parsers by default Node document = parser.parse(given); assertThat(document.getFirstChild(), instanceOf(Heading.class)); - List headersOnly = Collections.singletonList(Heading.class); - parser = Parser.builder().allowedBlockTypes(headersOnly).build(); + Set> headersOnly = new HashSet<>(); + headersOnly.add(Heading.class); + parser = Parser.builder().enabledBlockTypes(headersOnly).build(); document = parser.parse(given); assertThat(document.getFirstChild(), instanceOf(Heading.class)); - List noCoreTypes = Collections.emptyList(); - parser = Parser.builder().allowedBlockTypes(noCoreTypes).build(); + Set> noCoreTypes = new HashSet<>(); + parser = Parser.builder().enabledBlockTypes(noCoreTypes).build(); document = parser.parse(given); assertThat(document.getFirstChild(), not(instanceOf(Heading.class))); } @@ -72,17 +73,17 @@ public void indentation() { String given = " - 1 space\n - 3 spaces\n - 5 spaces\n\t - tab + space"; Parser parser = Parser.builder().build(); Node document = parser.parse(given); - + assertThat(document.getFirstChild(), instanceOf(BulletList.class)); - + Node list = document.getFirstChild(); // first level list assertEquals("expect one child", list.getFirstChild(), list.getLastChild()); assertEquals("1 space", firstText(list.getFirstChild())); - + list = list.getFirstChild().getLastChild(); // second level list assertEquals("expect one child", list.getFirstChild(), list.getLastChild()); assertEquals("3 spaces", firstText(list.getFirstChild())); - + list = list.getFirstChild().getLastChild(); // third level list assertEquals("5 spaces", firstText(list.getFirstChild())); assertEquals("tab + space", firstText(list.getFirstChild().getNext())); From 8c5934a1f16f1ebef3e801d09ec7de72039f7d91 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 6 Jan 2017 12:34:43 +1100 Subject: [PATCH 211/815] Minor Javadoc cleanup of enabledBlockTypes --- .../main/java/org/commonmark/parser/Parser.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index ea6a6c72a..35f9e7d8d 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -121,12 +121,12 @@ public Builder extensions(Iterable extensions) { /** * Describe the list of markdown features the parser will recognize and parse. *

    - * By default, Commonmark will recognize and parse the following set of core markdown features: + * By default, CommonMark will recognize and parse the following set of "block" elements: * *

      *
    • {@link Heading} ({@code #}) *
    • {@link HtmlBlock} ({@code }) - *
    • {@link ThematicBreak} (Horizontal Rule) ({@code ---}) + *
    • {@link ThematicBreak} (Horizontal Rule) ({@code ---}) *
    • {@link FencedCodeBlock} ({@code ```}) *
    • {@link IndentedCodeBlock} *
    • {@link BlockQuote} ({@code >}) @@ -134,17 +134,17 @@ public Builder extensions(Iterable extensions) { *
    * *

    - * To parse only a subset of the features listed above, pass a list of each feature's associated Node class. + * To parse only a subset of the features listed above, pass a list of each feature's associated {@link Block} class. *

    - * E.g., to only parse Headings and Lists: + * E.g., to only parse headings and lists: *

              *     {@code
    -         *     Parser.builder().enabledBlockTypes(Heading.class, ListBlock.class);
    +         *     Parser.builder().enabledBlockTypes(new HashSet<>(Arrays.asList(Heading.class, ListBlock.class)));
              *     }
              * 
    * - * @param enabledBlockTypes A list of nodes the parser will parse. - * If this list is empty, the parser will not recognize any Commonmark core markdown features. + * @param enabledBlockTypes A list of block nodes the parser will parse. + * If this list is empty, the parser will not recognize any CommonMark core features. * * @return {@code this} */ From 88a1799a0b442361873bd24ceeb2f116774827e7 Mon Sep 17 00:00:00 2001 From: Cody Sehl Date: Mon, 24 Oct 2016 15:08:06 -0600 Subject: [PATCH 212/815] Make inline parsing customizable by providing an implemetaion of InlineParser Signed-off-by: Graham Bell Signed-off-by: Cody Sehl --- .../java/org/commonmark/internal/Bracket.java | 2 +- .../org/commonmark/internal/Delimiter.java | 2 +- .../commonmark/internal/DocumentParser.java | 5 ++- .../commonmark/internal/InlineParserImpl.java | 3 +- .../commonmark/internal/ParagraphParser.java | 2 +- .../org/commonmark/parser/InlineParser.java | 6 ++- .../java/org/commonmark/parser/Parser.java | 38 ++++++++++++++++++- .../java/org/commonmark/test/ParserTest.java | 25 +++++++++++- 8 files changed, 71 insertions(+), 12 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index 15b58c643..b41a5dc96 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -5,7 +5,7 @@ /** * Opening bracket for links ([) or images (![). */ -class Bracket { +public class Bracket { final Text node; final int index; diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index 2c4094ef9..3a22cbf09 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -6,7 +6,7 @@ /** * Delimiter (emphasis, strong emphasis or custom emphasis). */ -class Delimiter implements DelimiterRun { +public class Delimiter implements DelimiterRun { final Text node; final char delimiterChar; diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index d469aad62..a85ac174b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -6,6 +6,7 @@ import org.commonmark.internal.util.Parsing; import org.commonmark.internal.util.Substring; import org.commonmark.node.*; +import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.*; import java.util.ArrayList; @@ -66,14 +67,14 @@ public class DocumentParser implements ParserState { private boolean blank; private final List blockParserFactories; - private final InlineParserImpl inlineParser; + private final InlineParser inlineParser; private final DocumentBlockParser documentBlockParser; private List activeBlockParsers = new ArrayList<>(); private Set allBlockParsers = new HashSet<>(); private Map lastLineBlank = new HashMap<>(); - public DocumentParser(List blockParserFactories, InlineParserImpl inlineParser) { + public DocumentParser(List blockParserFactories, InlineParser inlineParser) { this.blockParserFactories = blockParserFactories; this.inlineParser = inlineParser; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 49b9df92d..5539a9d1f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -172,9 +172,8 @@ public void parse(String content, Node block) { /** * Attempt to parse a link reference, modifying the internal reference map. - * - * @return how many characters were parsed as a reference, {@code 0} if none */ + @Override public int parseReference(String s) { this.input = s; this.index = 0; diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index db1b9f187..e4cfe9fa7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -36,7 +36,7 @@ public void addLine(CharSequence line) { public void closeBlock() { } - public void closeBlock(InlineParserImpl inlineParser) { + public void closeBlock(InlineParser inlineParser) { String contentString = content.getString(); boolean hasReferenceDefs = false; diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java index 828ea7946..8a0c2ffd6 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java @@ -4,7 +4,6 @@ /** * Parser for inline content (text, links, emphasized text, etc). - *

    This interface is not intended to be implemented by clients.

    */ public interface InlineParser { @@ -14,4 +13,9 @@ public interface InlineParser { */ void parse(String input, Node node); + /** + * @return how many characters were parsed as a reference, {@code 0} if none + */ + int parseReference(String s); + } diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 35f9e7d8d..d78f3a6e8 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -31,6 +31,7 @@ public class Parser { private final Map delimiterProcessors; private final BitSet delimiterCharacters; private final BitSet specialCharacters; + private final InlineParser inlineParser; private final List postProcessors; private Parser(Builder builder) { @@ -38,6 +39,7 @@ private Parser(Builder builder) { this.delimiterProcessors = InlineParserImpl.calculateDelimiterProcessors(builder.delimiterProcessors); this.delimiterCharacters = InlineParserImpl.calculateDelimiterCharacters(delimiterProcessors.keySet()); this.specialCharacters = InlineParserImpl.calculateSpecialCharacters(delimiterCharacters); + this.inlineParser = builder.inlineParser; this.postProcessors = builder.postProcessors; } @@ -59,7 +61,7 @@ public static Builder builder() { * @return the root node */ public Node parse(String input) { - InlineParserImpl inlineParser = new InlineParserImpl(specialCharacters, delimiterCharacters, delimiterProcessors); + InlineParser inlineParser = getInlineParser(); DocumentParser documentParser = new DocumentParser(blockParserFactories, inlineParser); Node document = documentParser.parse(input); return postProcess(document); @@ -75,12 +77,20 @@ public Node parse(String input) { * @throws IOException when reading throws an exception */ public Node parseReader(Reader input) throws IOException { - InlineParserImpl inlineParser = new InlineParserImpl(specialCharacters, delimiterCharacters, delimiterProcessors); + InlineParser inlineParser = getInlineParser(); DocumentParser documentParser = new DocumentParser(blockParserFactories, inlineParser); Node document = documentParser.parse(input); return postProcess(document); } + private InlineParser getInlineParser() { + if (this.inlineParser == null) { + return new InlineParserImpl(specialCharacters, delimiterCharacters, delimiterProcessors); + } else { + return this.inlineParser; + } + } + private Node postProcess(Node document) { for (PostProcessor postProcessor : postProcessors) { document = postProcessor.process(document); @@ -96,6 +106,7 @@ public static class Builder { private final List delimiterProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); + private InlineParser inlineParser = null; /** * @return the configured {@link Parser} @@ -177,6 +188,29 @@ public Builder postProcessor(PostProcessor postProcessor) { postProcessors.add(postProcessor); return this; } + + /** + * Overrides the parser used for inline markdown processing. + * + * Provide an implementation of InlineParser to modify how the following are parsed: + * bold (**) + * italic (*) + * strikethrough (~~) + * backtick quote (`) + * link ([title](http://)) + * image (![alt](http://)) + * + *

    + * Note that if this method is not called or the inline parser is set to null, then the default + * implementation will be used. + * + * @param parser an inline parser implementation + * @return {@code this} + */ + public Builder inlineParser(InlineParser parser) { + this.inlineParser = parser; + return this; + } } /** diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index aa0d7d339..59ff1bd8b 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,9 +1,10 @@ package org.commonmark.test; +import org.commonmark.parser.InlineParser; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.commonmark.parser.block.*; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.spec.SpecReader; import org.junit.Test; @@ -88,7 +89,27 @@ public void indentation() { assertEquals("5 spaces", firstText(list.getFirstChild())); assertEquals("tab + space", firstText(list.getFirstChild().getNext())); } - + + @Test + public void inlineParser() { + InlineParser fakeInlineParser = new InlineParser() { + @Override + public void parse(String input, Node node) { + node.appendChild(new ThematicBreak()); + } + + @Override + public int parseReference(String s) { + return 0; + } + }; + + Parser parser = Parser.builder().inlineParser(fakeInlineParser).build(); + String input = "**bold** **bold** ~~strikethrough~~"; + + assertThat(parser.parse(input).getFirstChild().getFirstChild(), instanceOf(ThematicBreak.class)); + } + private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n, notNullValue()); From ebbee1e3f734e089d80bdde5b50438b84ea394cc Mon Sep 17 00:00:00 2001 From: Vera Reynolds Date: Tue, 25 Oct 2016 12:35:48 -0600 Subject: [PATCH 213/815] Exposed fields of Bracket and Delimeter for non-package parsers Signed-off-by: Graham Bell --- .../java/org/commonmark/internal/Bracket.java | 18 +++++++++--------- .../org/commonmark/internal/Delimiter.java | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index b41a5dc96..70a8a6e25 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -7,35 +7,35 @@ */ public class Bracket { - final Text node; - final int index; - final boolean image; + public final Text node; + public final int index; + public final boolean image; /** * Previous bracket. */ - final Bracket previous; + public final Bracket previous; /** * Previous delimiter (emphasis, etc) before this bracket. */ - final Delimiter previousDelimiter; + public final Delimiter previousDelimiter; /** * Whether this bracket is allowed to form a link/image (also known as "active"). */ - boolean allowed = true; + public boolean allowed = true; /** * Whether there is an unescaped bracket (opening or closing) anywhere after this opening bracket. */ - boolean bracketAfter = false; + public boolean bracketAfter = false; - static Bracket link(Text node, int index, Bracket previous, Delimiter previousDelimiter) { + static public Bracket link(Text node, int index, Bracket previous, Delimiter previousDelimiter) { return new Bracket(node, index, previous, previousDelimiter, false); } - static Bracket image(Text node, int index, Bracket previous, Delimiter previousDelimiter) { + static public Bracket image(Text node, int index, Bracket previous, Delimiter previousDelimiter) { return new Bracket(node, index, previous, previousDelimiter, true); } diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index 3a22cbf09..9f4f66e88 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -8,25 +8,25 @@ */ public class Delimiter implements DelimiterRun { - final Text node; - final char delimiterChar; + public final Text node; + public final char delimiterChar; /** * Can open emphasis, see spec. */ - final boolean canOpen; + public final boolean canOpen; /** * Can close emphasis, see spec. */ - final boolean canClose; + public final boolean canClose; - Delimiter previous; - Delimiter next; + public Delimiter previous; + public Delimiter next; - int numDelims = 1; + public int numDelims = 1; - Delimiter(Text node, char delimiterChar, boolean canOpen, boolean canClose, Delimiter previous) { + public Delimiter(Text node, char delimiterChar, boolean canOpen, boolean canClose, Delimiter previous) { this.node = node; this.delimiterChar = delimiterChar; this.canOpen = canOpen; From 22b2dff3427b683763286f3d15c1252a0c388ca0 Mon Sep 17 00:00:00 2001 From: Cody Sehl Date: Fri, 6 Jan 2017 10:45:04 -0700 Subject: [PATCH 214/815] Addressing pr comments: Changed parser builder to take a factory of an inline parser. Signed-off-by: Vera Reynolds --- .../parser/InlineParserFactory.java | 8 +++++++ .../java/org/commonmark/parser/Parser.java | 21 ++++++++++--------- .../java/org/commonmark/test/ParserTest.java | 13 ++++++++++-- 3 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java new file mode 100644 index 000000000..775ec8ad9 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java @@ -0,0 +1,8 @@ +package org.commonmark.parser; + +/** + * Factory for custom inline parser. + */ +public interface InlineParserFactory { + InlineParser create(); +} diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index d78f3a6e8..8ef8cd9c7 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -31,7 +31,7 @@ public class Parser { private final Map delimiterProcessors; private final BitSet delimiterCharacters; private final BitSet specialCharacters; - private final InlineParser inlineParser; + private final InlineParserFactory inlineParserFactory; private final List postProcessors; private Parser(Builder builder) { @@ -39,7 +39,7 @@ private Parser(Builder builder) { this.delimiterProcessors = InlineParserImpl.calculateDelimiterProcessors(builder.delimiterProcessors); this.delimiterCharacters = InlineParserImpl.calculateDelimiterCharacters(delimiterProcessors.keySet()); this.specialCharacters = InlineParserImpl.calculateSpecialCharacters(delimiterCharacters); - this.inlineParser = builder.inlineParser; + this.inlineParserFactory = builder.inlineParserFactory; this.postProcessors = builder.postProcessors; } @@ -84,10 +84,10 @@ public Node parseReader(Reader input) throws IOException { } private InlineParser getInlineParser() { - if (this.inlineParser == null) { + if (this.inlineParserFactory == null) { return new InlineParserImpl(specialCharacters, delimiterCharacters, delimiterProcessors); } else { - return this.inlineParser; + return this.inlineParserFactory.create(); } } @@ -106,7 +106,7 @@ public static class Builder { private final List delimiterProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); - private InlineParser inlineParser = null; + private InlineParserFactory inlineParserFactory = null; /** * @return the configured {@link Parser} @@ -192,7 +192,8 @@ public Builder postProcessor(PostProcessor postProcessor) { /** * Overrides the parser used for inline markdown processing. * - * Provide an implementation of InlineParser to modify how the following are parsed: + * Provide an implementation of InlineParserFactory which provides a custom inline parser + * to modify how the following are parsed: * bold (**) * italic (*) * strikethrough (~~) @@ -201,14 +202,14 @@ public Builder postProcessor(PostProcessor postProcessor) { * image (![alt](http://)) * *

    - * Note that if this method is not called or the inline parser is set to null, then the default + * Note that if this method is not called or the inline parser factory is set to null, then the default * implementation will be used. * - * @param parser an inline parser implementation + * @param inlineParserFactory an inline parser factory implementation * @return {@code this} */ - public Builder inlineParser(InlineParser parser) { - this.inlineParser = parser; + public Builder inlineParserFactory(InlineParserFactory inlineParserFactory) { + this.inlineParserFactory = inlineParserFactory; return this; } } diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 59ff1bd8b..34c3d1d11 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,6 +1,7 @@ package org.commonmark.test; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserFactory; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.*; import org.commonmark.parser.Parser; @@ -92,7 +93,7 @@ public void indentation() { @Test public void inlineParser() { - InlineParser fakeInlineParser = new InlineParser() { + final InlineParser fakeInlineParser = new InlineParser() { @Override public void parse(String input, Node node) { node.appendChild(new ThematicBreak()); @@ -104,7 +105,15 @@ public int parseReference(String s) { } }; - Parser parser = Parser.builder().inlineParser(fakeInlineParser).build(); + InlineParserFactory fakeInlineParserFactory = new InlineParserFactory(){ + + @Override + public InlineParser create() { + return fakeInlineParser; + } + }; + + Parser parser = Parser.builder().inlineParserFactory(fakeInlineParserFactory).build(); String input = "**bold** **bold** ~~strikethrough~~"; assertThat(parser.parse(input).getFirstChild().getFirstChild(), instanceOf(ThematicBreak.class)); From 9e0a3f8d3d5b62148e91fa128353322e22db440f Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Thu, 19 Jan 2017 20:18:27 +0100 Subject: [PATCH 215/815] Support multiple DelimiterProcessors with the same delimiter char Previously it was not possible to use the same delimiter char for different DelimiterProcessors even if they require a different number of delimiters (for example, using `~` for subscript together with `~~` for strikethrough). This change adds support for multiple DelimiterProcessors with the same delimiter char as long as all DelimiterProcessors for that char have the same opening and closing char and different minimum lengths. When multiple processors are defined for a char, the one with the largest acceptable minimum length is chosen in each case. --- .../commonmark/internal/InlineParserImpl.java | 18 ++++- .../internal/StaggeredDelimiterProcessor.java | 80 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 49b9df92d..0577fd871 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -135,9 +135,23 @@ public static Map calculateDelimiterProcessors(Li private static void addDelimiterProcessors(Iterable delimiterProcessors, Map map) { for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { char opening = delimiterProcessor.getOpeningCharacter(); - addDelimiterProcessorForChar(opening, delimiterProcessor, map); char closing = delimiterProcessor.getClosingCharacter(); - if (opening != closing) { + if (opening == closing) { + DelimiterProcessor old = map.get(opening); + if(old != null && old.getOpeningCharacter() == old.getClosingCharacter()) { + StaggeredDelimiterProcessor s; + if(old instanceof StaggeredDelimiterProcessor) s = (StaggeredDelimiterProcessor)old; + else { + s = new StaggeredDelimiterProcessor(opening); + s.add(old); + } + s.add(delimiterProcessor); + map.put(opening, s); + } else { + addDelimiterProcessorForChar(opening, delimiterProcessor, map); + } + } else { + addDelimiterProcessorForChar(opening, delimiterProcessor, map); addDelimiterProcessorForChar(closing, delimiterProcessor, map); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java new file mode 100644 index 000000000..026b3889b --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java @@ -0,0 +1,80 @@ +package org.commonmark.internal; + +import org.commonmark.node.Text; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; + +import java.util.LinkedList; +import java.util.ListIterator; + +/** + * An implementation of DelimiterProcessor that dispatches all calls to two or more other DelimiterProcessors + * depending on the length of the delimiter run. All child DelimiterProcessors must have different minimum + * lengths. A given delimiter run is dispatched to the child with the largest acceptable minimum length. If no + * child is applicable, the one with the largest minimum length is chosen. + */ +class StaggeredDelimiterProcessor implements DelimiterProcessor { + + private final char delim; + private int minLength = 0; + private LinkedList processors = new LinkedList<>(); // in reverse getMinLength order + + StaggeredDelimiterProcessor(char delim) { + this.delim = delim; + } + + + @Override + public char getOpeningCharacter() { + return delim; + } + + @Override + public char getClosingCharacter() { + return delim; + } + + @Override + public int getMinLength() { + return minLength; + } + + void add(DelimiterProcessor dp) { + final int len = dp.getMinLength(); + ListIterator it = processors.listIterator(); + boolean added = false; + while(it.hasNext()) { + DelimiterProcessor p = it.next(); + int pLen = p.getMinLength(); + if(len > pLen) { + it.previous(); + it.add(dp); + added = true; + break; + } else if(len == pLen) { + throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length "+len); + } + } + if(!added) { + processors.add(dp); + this.minLength = len; + } + } + + private DelimiterProcessor findProcessor(int len) { + for(DelimiterProcessor p : processors) { + if(p.getMinLength() <= len) return p; + } + return processors.getFirst(); + } + + @Override + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + return findProcessor(opener.length()).getDelimiterUse(opener, closer); + } + + @Override + public void process(Text opener, Text closer, int delimiterUse) { + findProcessor(delimiterUse).process(opener, closer, delimiterUse); + } +} From d15cf7690e601053c04a1cdfb66471860dac805a Mon Sep 17 00:00:00 2001 From: Daniel Robert Date: Mon, 23 Jan 2017 16:27:56 -0500 Subject: [PATCH 216/815] Adding ext-heading-anchor to dependencyManagement in parent pom --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 4ebcc9426..5652e2738 100644 --- a/pom.xml +++ b/pom.xml @@ -108,6 +108,11 @@ commonmark-ext-gfm-tables 0.8.1-SNAPSHOT + + com.atlassian.commonmark + commonmark-ext-heading-anchor + 0.8.1-SNAPSHOT + com.atlassian.commonmark commonmark-ext-yaml-front-matter From 778b7b47309c8444e069e3ba7043e2c3799fb657 Mon Sep 17 00:00:00 2001 From: Vera Reynolds Date: Tue, 24 Jan 2017 13:53:20 -0700 Subject: [PATCH 217/815] Addressing PR comments. Split out ReferenceParser and fixed delimiter processors in custom inline parsers. Signed-off-by: Cody Sehl --- .../org/commonmark/internal/DocumentParser.java | 5 +++-- .../org/commonmark/internal/InlineParserImpl.java | 10 +++++----- .../org/commonmark/internal/ParagraphParser.java | 2 +- .../org/commonmark/internal/ReferenceParser.java | 11 +++++++++++ .../java/org/commonmark/parser/InlineParser.java | 6 ------ .../org/commonmark/parser/InlineParserFactory.java | 6 +++++- .../main/java/org/commonmark/parser/Parser.java | 14 ++++---------- .../test/java/org/commonmark/test/ParserTest.java | 9 +++------ 8 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/ReferenceParser.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index a85ac174b..4d705b6cd 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -392,9 +392,10 @@ private void finalize(BlockParser blockParser) { blockParser.closeBlock(); - if (blockParser instanceof ParagraphParser) { + if (blockParser instanceof ParagraphParser + && inlineParser instanceof ReferenceParser) { ParagraphParser paragraphParser = (ParagraphParser) blockParser; - paragraphParser.closeBlock(inlineParser); + paragraphParser.closeBlock((ReferenceParser) inlineParser); } else if (blockParser instanceof ListBlockParser) { ListBlockParser listBlockParser = (ListBlockParser) blockParser; finalizeListTight(listBlockParser); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 5539a9d1f..87b454be8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -13,7 +13,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class InlineParserImpl implements InlineParser { +public class InlineParserImpl implements InlineParser, ReferenceParser { private static final String ESCAPED_CHAR = "\\\\" + Escaping.ESCAPABLE; private static final String REG_CHAR = "[^\\\\()\\x00-\\x20]"; @@ -97,10 +97,10 @@ public class InlineParserImpl implements InlineParser { */ private Bracket lastBracket; - public InlineParserImpl(BitSet specialCharacters, BitSet delimiterCharacters, Map delimiterProcessors) { - this.delimiterProcessors = delimiterProcessors; - this.delimiterCharacters = delimiterCharacters; - this.specialCharacters = specialCharacters; + public InlineParserImpl(List delimiterProcessors) { + this.delimiterProcessors = calculateDelimiterProcessors(delimiterProcessors); + this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); + this.specialCharacters = calculateSpecialCharacters(delimiterCharacters); } public static BitSet calculateDelimiterCharacters(Set characters) { diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index e4cfe9fa7..fac8cfadc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -36,7 +36,7 @@ public void addLine(CharSequence line) { public void closeBlock() { } - public void closeBlock(InlineParser inlineParser) { + public void closeBlock(ReferenceParser inlineParser) { String contentString = content.getString(); boolean hasReferenceDefs = false; diff --git a/commonmark/src/main/java/org/commonmark/internal/ReferenceParser.java b/commonmark/src/main/java/org/commonmark/internal/ReferenceParser.java new file mode 100644 index 000000000..35f36cb59 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/ReferenceParser.java @@ -0,0 +1,11 @@ +package org.commonmark.internal; + +/** + * Parser for inline references + */ +public interface ReferenceParser { + /** + * @return how many characters were parsed as a reference, {@code 0} if none + */ + int parseReference(String s); +} diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java index 8a0c2ffd6..492c3cc8a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java @@ -12,10 +12,4 @@ public interface InlineParser { * @param node the node to append resulting nodes to (as children) */ void parse(String input, Node node); - - /** - * @return how many characters were parsed as a reference, {@code 0} if none - */ - int parseReference(String s); - } diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java index 775ec8ad9..8c412b28b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java @@ -1,8 +1,12 @@ package org.commonmark.parser; +import org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.util.List; + /** * Factory for custom inline parser. */ public interface InlineParserFactory { - InlineParser create(); + InlineParser create(List customDelimiterProcessors); } diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 8ef8cd9c7..7496b9581 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -3,9 +3,7 @@ import java.io.IOException; import java.io.Reader; import java.util.ArrayList; -import java.util.BitSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.commonmark.Extension; @@ -28,19 +26,15 @@ public class Parser { private final List blockParserFactories; - private final Map delimiterProcessors; - private final BitSet delimiterCharacters; - private final BitSet specialCharacters; + private final List delimiterProcessors; private final InlineParserFactory inlineParserFactory; private final List postProcessors; private Parser(Builder builder) { this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); - this.delimiterProcessors = InlineParserImpl.calculateDelimiterProcessors(builder.delimiterProcessors); - this.delimiterCharacters = InlineParserImpl.calculateDelimiterCharacters(delimiterProcessors.keySet()); - this.specialCharacters = InlineParserImpl.calculateSpecialCharacters(delimiterCharacters); this.inlineParserFactory = builder.inlineParserFactory; this.postProcessors = builder.postProcessors; + this.delimiterProcessors = builder.delimiterProcessors; } /** @@ -85,9 +79,9 @@ public Node parseReader(Reader input) throws IOException { private InlineParser getInlineParser() { if (this.inlineParserFactory == null) { - return new InlineParserImpl(specialCharacters, delimiterCharacters, delimiterProcessors); + return new InlineParserImpl(delimiterProcessors); } else { - return this.inlineParserFactory.create(); + return this.inlineParserFactory.create(delimiterProcessors); } } diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 34c3d1d11..fc32f4162 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -2,6 +2,7 @@ import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserFactory; +import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.*; import org.commonmark.parser.Parser; @@ -13,6 +14,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; +import java.util.List; import java.util.Set; import static org.hamcrest.CoreMatchers.*; @@ -98,17 +100,12 @@ public void inlineParser() { public void parse(String input, Node node) { node.appendChild(new ThematicBreak()); } - - @Override - public int parseReference(String s) { - return 0; - } }; InlineParserFactory fakeInlineParserFactory = new InlineParserFactory(){ @Override - public InlineParser create() { + public InlineParser create(List delimiterProcessors) { return fakeInlineParser; } }; From 90f48047a4cd7c122e41595b64504cbfd4563dff Mon Sep 17 00:00:00 2001 From: Vera Reynolds Date: Wed, 25 Jan 2017 11:56:26 -0700 Subject: [PATCH 218/815] Addressing PR comments. Wrapping inline parser create parameters in a context Signed-off-by: Sasha Heinen --- .../commonmark/parser/InlineParserContext.java | 13 +++++++++++++ .../commonmark/parser/InlineParserFactory.java | 6 +----- .../main/java/org/commonmark/parser/Parser.java | 17 ++++++++++++++++- .../java/org/commonmark/test/ParserTest.java | 3 ++- 4 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java new file mode 100644 index 000000000..7a3be522d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -0,0 +1,13 @@ +package org.commonmark.parser; + +import org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.util.List; +import java.util.Map; + +/** + * Parameter context for custom inline parser. + */ +public interface InlineParserContext { + List getCustomDelimiterProcessors(); +} diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java index 8c412b28b..34c384a8a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserFactory.java @@ -1,12 +1,8 @@ package org.commonmark.parser; -import org.commonmark.parser.delimiter.DelimiterProcessor; - -import java.util.List; - /** * Factory for custom inline parser. */ public interface InlineParserFactory { - InlineParser create(List customDelimiterProcessors); + 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 7496b9581..d9d904cd5 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -81,7 +81,8 @@ private InlineParser getInlineParser() { if (this.inlineParserFactory == null) { return new InlineParserImpl(delimiterProcessors); } else { - return this.inlineParserFactory.create(delimiterProcessors); + CustomInlineParserContext inlineParserContext = new CustomInlineParserContext(delimiterProcessors); + return this.inlineParserFactory.create(inlineParserContext); } } @@ -92,6 +93,20 @@ private Node postProcess(Node document) { return document; } + private class CustomInlineParserContext implements InlineParserContext { + + private List delimiterProcessors; + + CustomInlineParserContext(List delimiterProcessors) { + this.delimiterProcessors = delimiterProcessors; + } + + @Override + public List getCustomDelimiterProcessors() { + return delimiterProcessors; + } + } + /** * Builder for configuring a {@link Parser}. */ diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index fc32f4162..ddf030283 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,6 +1,7 @@ package org.commonmark.test; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.renderer.html.HtmlRenderer; @@ -105,7 +106,7 @@ public void parse(String input, Node node) { InlineParserFactory fakeInlineParserFactory = new InlineParserFactory(){ @Override - public InlineParser create(List delimiterProcessors) { + public InlineParser create(InlineParserContext inlineParserContext) { return fakeInlineParser; } }; From 0c6c3c6a76055af9c2d3200a5d0b7b8be2d2376f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 3 Mar 2017 13:17:57 +1100 Subject: [PATCH 219/815] Wrap escaped HTML blocks in a

    tag (fixes #78) It makes more sense to wrap them in a paragraph instead of having text content directly, because other block types are also rendered in some kind of block element. --- .../org/commonmark/renderer/html/CoreHtmlNodeRenderer.java | 2 ++ .../src/test/java/org/commonmark/test/HtmlRendererTest.java | 4 ++-- 2 files changed, 4 insertions(+), 2 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 41f208813..9bb3225d0 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -117,7 +117,9 @@ public void visit(FencedCodeBlock fencedCodeBlock) { public void visit(HtmlBlock htmlBlock) { html.line(); if (context.shouldEscapeHtml()) { + html.tag("p", getAttrs(htmlBlock, "p")); html.text(htmlBlock.getLiteral()); + html.tag("/p"); } else { html.raw(htmlBlock.getLiteral()); } diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 82de13d74..ca9af91fe 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -44,7 +44,7 @@ public void htmlEscapingShouldEscapeInlineHtml() { @Test public void htmlEscapingShouldEscapeHtmlBlocks() { String rendered = htmlEscapingRenderer().render(parse("

    block &
    ")); - assertEquals("<div id='foo' class="bar">block &amp;</div>\n", rendered); + assertEquals("

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

    \n", rendered); } @Test @@ -54,7 +54,7 @@ public void textEscaping() { } @Test - public void percendEncodeUrlDisabled() { + 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)"))); From a83bda694c5dc2e46354af779e8ba840396fd9e4 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 3 Mar 2017 13:29:32 +1100 Subject: [PATCH 220/815] Bump maven-compiler-plugin version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5652e2738..63ecb5dde 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.6.1 7 7 From 969179a5faf4902e1ce2295eaa67ee0bf8f3b815 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 3 Mar 2017 13:31:21 +1100 Subject: [PATCH 221/815] Bump jmh version --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 63ecb5dde..25ffbec7d 100644 --- a/pom.xml +++ b/pom.xml @@ -133,12 +133,12 @@ org.openjdk.jmh jmh-core - 1.13 + 1.17.5 org.openjdk.jmh jmh-generator-annprocess - 1.13 + 1.17.5
    From 19e2f81d5adfcbcb46a299d6bce3efc465826577 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 3 Mar 2017 13:31:46 +1100 Subject: [PATCH 222/815] Bump jacoco and coveralls versions --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 25ffbec7d..6add95158 100644 --- a/pom.xml +++ b/pom.xml @@ -151,7 +151,7 @@ org.jacoco jacoco-maven-plugin - 0.7.7.201606060606 + 0.7.9 @@ -171,7 +171,7 @@ org.eluder.coveralls coveralls-maven-plugin - 4.2.0 + 4.3.0 From 4f589b2d768c50b9c8a847dd4c8122f50f9fa205 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 3 Mar 2017 15:50:47 +1100 Subject: [PATCH 223/815] [maven-release-plugin] prepare release commonmark-parent-0.9.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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 7dcddcff8..938afd884 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index abfc83820..1b0e8efe4 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 8ef587081..c09477e65 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 5385f9987..e12c52211 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index ac62df362..962deaee8 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index bef628c71..7bd2543b8 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.8.1-SNAPSHOT + 0.9.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 632ab9cc8..930ab94b3 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 2c5356a86..e56dae3ba 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index f41813bdc..9ad0cb87b 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark diff --git a/pom.xml b/pom.xml index 6add95158..541900f28 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.8.1-SNAPSHOT + 0.9.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -86,42 +86,42 @@ com.atlassian.commonmark commonmark - 0.8.1-SNAPSHOT + 0.9.0 com.atlassian.commonmark commonmark-ext-autolink - 0.8.1-SNAPSHOT + 0.9.0 com.atlassian.commonmark commonmark-ext-ins - 0.8.1-SNAPSHOT + 0.9.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.8.1-SNAPSHOT + 0.9.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.8.1-SNAPSHOT + 0.9.0 com.atlassian.commonmark commonmark-ext-heading-anchor - 0.8.1-SNAPSHOT + 0.9.0 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.8.1-SNAPSHOT + 0.9.0 com.atlassian.commonmark commonmark-test-util - 0.8.1-SNAPSHOT + 0.9.0 @@ -199,7 +199,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.9.0 From 862b64affd2cb81cf23da33497099294fcbf8051 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 3 Mar 2017 15:50:47 +1100 Subject: [PATCH 224/815] [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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 938afd884..dadc2419d 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 1b0e8efe4..a43d52e47 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index c09477e65..2c6df1935 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index e12c52211..1c9a09906 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 962deaee8..d033ed5a9 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 7bd2543b8..41525e516 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.9.0 + 0.9.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 930ab94b3..cd7dec8c4 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index e56dae3ba..b0a460c80 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 9ad0cb87b..564f0ac92 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 541900f28..bb7fde7cb 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.0 + 0.9.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -86,42 +86,42 @@ com.atlassian.commonmark commonmark - 0.9.0 + 0.9.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.9.0 + 0.9.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-ins - 0.9.0 + 0.9.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.9.0 + 0.9.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.9.0 + 0.9.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-heading-anchor - 0.9.0 + 0.9.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.9.0 + 0.9.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.9.0 + 0.9.1-SNAPSHOT @@ -199,7 +199,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.9.0 + HEAD From 7a9f3b9a3fe21ce7a8b3261490436a1143a22c6a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 3 Mar 2017 17:56:02 +1100 Subject: [PATCH 225/815] Bump versions --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 4 ++-- commonmark-android-test/README.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3dcaeb9fe..ca39f87df 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.8.0 + 0.9.0 ``` @@ -211,7 +211,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.8.0 + 0.9.0 ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index e6f984663..a9a66f484 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -8,8 +8,8 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.7.0" >> test.properties -echo "version.maven_autolink=0.5.0" >> test.properties +echo "version.maven=0.9.0" >> test.properties +echo "version.maven_autolink=0.6.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 3c7539bcb..874ae0f7b 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.8.0 +version.maven=0.9.0 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.6.0 # Version number of commonmark and extensions in project. -version.snapshot=0.8.0-SNAPSHOT +version.snapshot=0.9.0-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.6.0 ``` From 13871392e121c70c1374b3cf18c2288c0e8c89e8 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 2 Aug 2017 16:18:23 +0300 Subject: [PATCH 226/815] Update tests for TextContent --- .../test/TextContentRendererTest.java | 201 +++++++++++++----- .../test/TextContentWriterTest.java | 2 +- 2 files changed, 149 insertions(+), 54 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index af74c36b4..1321ee99b 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -9,130 +9,225 @@ public class TextContentRendererTest { + @Test + public void textContentText() { + String source; + String rendered; + + source = "foo bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("foo bar", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("foo bar", rendered); + + source = "foo foo\n\nbar\nbar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("foo foo\nbar\nbar", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("foo foo bar bar", rendered); + } + @Test public void textContentEmphasis() { + String source; String rendered; - rendered = defaultRenderer().render(parse("foo\n***foo***\nbar\n\n***bar***")); - assertEquals("foo\nfoo\nbar\nbar", rendered); + source = "***foo***"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("foo", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("foo", rendered); + + source = "foo ***foo*** bar ***bar***"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("foo foo bar bar", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("foo foo bar bar", rendered); - rendered = strippedRenderer().render(parse("foo\n***foo\nbar***\n\n***bar***")); + source = "foo\n***foo***\nbar\n\n***bar***"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("foo\nfoo\nbar\nbar", rendered); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo foo bar bar", rendered); } @Test public void textContentQuotes() { + String source; String rendered; - rendered = defaultRenderer().render(parse("foo\n>foo\nbar\n\nbar")); + source = "foo\n>foo\nbar\n\nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\n«foo\nbar»\nbar", rendered); - - rendered = strippedRenderer().render(parse("foo\n>foo\nbar\n\nbar")); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo «foo bar» bar", rendered); } @Test public void textContentLinks() { + String source; + String expected; String rendered; - rendered = defaultRenderer().render(parse("foo [text](http://link \"title\") bar")); - assertEquals("foo \"text\" (title: http://link) bar", rendered); - - rendered = defaultRenderer().render(parse("foo [text](http://link) bar")); - assertEquals("foo \"text\" (http://link) bar", rendered); - - rendered = defaultRenderer().render(parse("foo [text]() bar")); - assertEquals("foo \"text\" bar", rendered); - - rendered = defaultRenderer().render(parse("foo http://link bar")); - assertEquals("foo http://link bar", rendered); + source = "foo [text](http://link \"title\") bar"; + expected = "foo \"text\" (title: http://link) bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); + + source = "foo [text](http://link \"http://link\") bar"; + expected = "foo \"text\" (http://link) bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); + + source = "foo [text](http://link) bar"; + expected = "foo \"text\" (http://link) bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); + + source = "foo [text]() bar"; + expected = "foo \"text\" bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); + + source = "foo http://link bar"; + expected = "foo http://link bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); } @Test public void textContentImages() { + String source; + String expected; String rendered; - rendered = defaultRenderer().render(parse("foo ![text](http://link \"title\") bar")); - assertEquals("foo \"text\" (title: http://link) bar", rendered); - - rendered = defaultRenderer().render(parse("foo ![text](http://link) bar")); - assertEquals("foo \"text\" (http://link) bar", rendered); - - rendered = defaultRenderer().render(parse("foo ![text]() bar")); - assertEquals("foo \"text\" bar", rendered); + source = "foo ![text](http://link \"title\") bar"; + expected = "foo \"text\" (title: http://link) bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); + + source = "foo ![text](http://link) bar"; + expected = "foo \"text\" (http://link) bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); + + source = "foo ![text]() bar"; + expected = "foo \"text\" bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); } @Test public void textContentLists() { + String source; String rendered; - rendered = defaultRenderer().render(parse("foo\n* foo\n* bar\n\nbar")); + source = "foo\n* foo\n* bar\n\nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\n* foo\n* bar\nbar", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("foo foo bar bar", rendered); - rendered = defaultRenderer().render(parse("foo\n- foo\n- bar\n\nbar")); + source = "foo\n- foo\n- bar\n\nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\n- foo\n- bar\nbar", rendered); - - rendered = strippedRenderer().render(parse("foo\n* foo\n* bar\n\nbar")); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo foo bar bar", rendered); - rendered = defaultRenderer().render(parse("foo\n1. foo\n2. bar\n\nbar")); + source = "foo\n1. foo\n2. bar\n\nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\n1. foo\n2. bar\nbar", rendered); - - rendered = defaultRenderer().render(parse("foo\n0) foo\n1) bar\n\nbar")); - assertEquals("foo\n0) foo\n1) bar\nbar", rendered); - - rendered = strippedRenderer().render(parse("foo\n1. foo\n2. bar\n\nbar")); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo 1. foo 2. bar bar", rendered); - rendered = strippedRenderer().render(parse("foo\n0) foo\n1) bar\n\nbar")); + source = "foo\n0) foo\n1) bar\n\nbar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("foo\n0) foo\n1) bar\nbar", rendered); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo 0) foo 1) bar bar", rendered); + + source = "bar\n1. foo\n 1. bar\n2. foo"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("bar\n1. foo\n 1. bar\n2. foo", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("bar 1. foo 1. bar 2. foo", rendered); + + source = "bar\n* foo\n - bar\n* foo"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("bar\n* foo\n - bar\n* foo", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("bar foo bar foo", rendered); } @Test public void textContentCode() { + String source; + String expected; String rendered; - rendered = defaultRenderer().render(parse("foo `code` bar")); - assertEquals("foo \"code\" bar", rendered); + source = "foo `code` bar"; + expected = "foo \"code\" bar"; + rendered = defaultRenderer().render(parse(source)); + assertEquals(expected, rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals(expected, rendered); } @Test public void textContentCodeBlock() { + String source; String rendered; - rendered = defaultRenderer().render(parse("foo\n```\nfoo\nbar\n```\nbar")); + source = "foo\n```\nfoo\nbar\n```\nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\nfoo\nbar\nbar", rendered); - - rendered = strippedRenderer().render(parse("foo\n```\nfoo\nbar\n```\nbar")); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo foo bar bar", rendered); - rendered = defaultRenderer().render(parse("foo\n\n foo\n bar\nbar")); + source = "foo\n\n foo\n bar\nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\nfoo\n bar\nbar", rendered); - - rendered = strippedRenderer().render(parse("foo\n\n foo\n bar\nbar")); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo foo bar bar", rendered); } @Test public void textContentBrakes() { + String source; String rendered; - rendered = defaultRenderer().render(parse("foo\nbar")); + source = "foo\nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\nbar", rendered); - - rendered = strippedRenderer().render(parse("foo\nbar")); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo bar", rendered); - rendered = defaultRenderer().render(parse("foo \nbar")); + source = "foo \nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\nbar", rendered); - - rendered = strippedRenderer().render(parse("foo \nbar")); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo bar", rendered); - rendered = defaultRenderer().render(parse("foo\n___\nbar")); + source = "foo\n___\nbar"; + rendered = defaultRenderer().render(parse(source)); assertEquals("foo\n***\nbar", rendered); - - rendered = strippedRenderer().render(parse("foo\n___\nbar")); + rendered = strippedRenderer().render(parse(source)); assertEquals("foo bar", rendered); } diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java index 0ffaf0e1f..0be668a70 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentWriterTest.java @@ -41,7 +41,7 @@ public void line() throws Exception { public void writeStripped() throws Exception { StringBuilder stringBuilder = new StringBuilder(); TextContentWriter writer = new TextContentWriter(stringBuilder); - writer.writeStripped("foo\n bar\n"); + writer.writeStripped("foo\n bar"); assertEquals("foo bar", stringBuilder.toString()); } From 373c7b949485592035e7b12a218e4406025c5936 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 2 Aug 2017 16:21:52 +0300 Subject: [PATCH 227/815] Fix missing whitespaces --- .../java/org/commonmark/renderer/text/TextContentWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 46c2f09de..0ea56e621 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java @@ -31,7 +31,7 @@ public void line() { } public void writeStripped(String s) { - append(s.replaceAll("[\\r\\n\\s]+", " ").trim()); + append(s.replaceAll("[\\r\\n\\s]+", " ")); } public void write(String s) { From f9295d3f90d0040b2d703d017883e7f9c5c06a5d Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 2 Aug 2017 16:57:56 +0300 Subject: [PATCH 228/815] Add sub list support for TextContent Fix for https://github.com/atlassian/commonmark-java/issues/90 --- .../text/CoreTextContentNodeRenderer.java | 77 ++++++++++++------- .../text/holder/BulletListHolder.java | 16 ++++ .../renderer/text/holder/ListHolder.java | 27 +++++++ .../text/holder/OrderedListHolder.java | 26 +++++++ 4 files changed, 118 insertions(+), 28 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.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 a78a7aadb..031c89fd7 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -2,6 +2,8 @@ import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.text.holder.BulletListHolder; +import org.commonmark.renderer.text.holder.OrderedListHolder; import java.util.Arrays; import java.util.HashSet; @@ -15,10 +17,8 @@ public class CoreTextContentNodeRenderer extends AbstractVisitor implements Node protected final TextContentNodeRendererContext context; private final TextContentWriter textContent; - private Integer orderedListCounter; - private Character orderedListDelimiter; - - private Character bulletListMarker; + private OrderedListHolder orderedListHolder; + private BulletListHolder bulletListHolder; public CoreTextContentNodeRenderer(TextContentNodeRendererContext context) { this.context = context; @@ -68,15 +68,22 @@ public void visit(BlockQuote blockQuote) { visitChildren(blockQuote); textContent.write('»'); - writeEndOfLine(blockQuote, null); + writeEndOfLineIfNeeded(blockQuote, null); } @Override public void visit(BulletList bulletList) { - bulletListMarker = bulletList.getBulletMarker(); + if (bulletListHolder != null) { + writeEndOfLine(); + } + bulletListHolder = new BulletListHolder(bulletListHolder, bulletList); visitChildren(bulletList); - writeEndOfLine(bulletList, null); - bulletListMarker = null; + writeEndOfLineIfNeeded(bulletList, null); + if (bulletListHolder.getParent() != null) { + bulletListHolder = (BulletListHolder) bulletListHolder.getParent(); + } else { + bulletListHolder = null; + } } @Override @@ -90,7 +97,7 @@ public void visit(Code code) { public void visit(FencedCodeBlock fencedCodeBlock) { if (context.stripNewlines()) { textContent.writeStripped(fencedCodeBlock.getLiteral()); - writeEndOfLine(fencedCodeBlock, null); + writeEndOfLineIfNeeded(fencedCodeBlock, null); } else { textContent.write(fencedCodeBlock.getLiteral()); } @@ -98,13 +105,13 @@ public void visit(FencedCodeBlock fencedCodeBlock) { @Override public void visit(HardLineBreak hardLineBreak) { - writeEndOfLine(hardLineBreak, null); + writeEndOfLineIfNeeded(hardLineBreak, null); } @Override public void visit(Heading heading) { visitChildren(heading); - writeEndOfLine(heading, ':'); + writeEndOfLineIfNeeded(heading, ':'); } @Override @@ -112,7 +119,7 @@ public void visit(ThematicBreak thematicBreak) { if (!context.stripNewlines()) { textContent.write("***"); } - writeEndOfLine(thematicBreak, null); + writeEndOfLineIfNeeded(thematicBreak, null); } @Override @@ -134,7 +141,7 @@ public void visit(Image image) { public void visit(IndentedCodeBlock indentedCodeBlock) { if (context.stripNewlines()) { textContent.writeStripped(indentedCodeBlock.getLiteral()); - writeEndOfLine(indentedCodeBlock, null); + writeEndOfLineIfNeeded(indentedCodeBlock, null); } else { textContent.write(indentedCodeBlock.getLiteral()); } @@ -147,28 +154,34 @@ public void visit(Link link) { @Override public void visit(ListItem listItem) { - if (orderedListCounter != null) { - textContent.write(String.valueOf(orderedListCounter) + orderedListDelimiter + " "); + if (orderedListHolder != null) { + String indent = context.stripNewlines() ? "" : orderedListHolder.getIndent(); + textContent.write(indent + orderedListHolder.getCounter() + orderedListHolder.getDelimiter() + " "); visitChildren(listItem); - writeEndOfLine(listItem, null); - orderedListCounter++; - } else if (bulletListMarker != null) { + writeEndOfLineIfNeeded(listItem, null); + orderedListHolder.increaseCounter(); + } else if (bulletListHolder != null) { if (!context.stripNewlines()) { - textContent.write(bulletListMarker + " "); + textContent.write(bulletListHolder.getIndent() + bulletListHolder.getMarker() + " "); } visitChildren(listItem); - writeEndOfLine(listItem, null); + writeEndOfLineIfNeeded(listItem, null); } } @Override public void visit(OrderedList orderedList) { - orderedListCounter = orderedList.getStartNumber(); - orderedListDelimiter = orderedList.getDelimiter(); + if (orderedListHolder != null) { + writeEndOfLine(); + } + orderedListHolder = new OrderedListHolder(orderedListHolder, orderedList); visitChildren(orderedList); - writeEndOfLine(orderedList, null); - orderedListCounter = null; - orderedListDelimiter = null; + writeEndOfLineIfNeeded(orderedList, null); + if (orderedListHolder.getParent() != null) { + orderedListHolder = (OrderedListHolder) orderedListHolder.getParent(); + } else { + orderedListHolder = null; + } } @Override @@ -176,13 +189,13 @@ public void visit(Paragraph paragraph) { visitChildren(paragraph); // Add "end of line" only if its "root paragraph. if (paragraph.getParent() == null || paragraph.getParent() instanceof Document) { - writeEndOfLine(paragraph, null); + writeEndOfLineIfNeeded(paragraph, null); } } @Override public void visit(SoftLineBreak softLineBreak) { - writeEndOfLine(softLineBreak, null); + writeEndOfLineIfNeeded(softLineBreak, null); } @Override @@ -240,7 +253,7 @@ private void writeLink(Node node, String title, String destination) { } } - private void writeEndOfLine(Node node, Character c) { + private void writeEndOfLineIfNeeded(Node node, Character c) { if (context.stripNewlines()) { if (c != null) { textContent.write(c); @@ -254,4 +267,12 @@ private void writeEndOfLine(Node node, Character c) { } } } + + private void writeEndOfLine() { + if (context.stripNewlines()) { + textContent.whitespace(); + } else { + textContent.line(); + } + } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java b/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java new file mode 100644 index 000000000..99ea09bd6 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java @@ -0,0 +1,16 @@ +package org.commonmark.renderer.text.holder; + +import org.commonmark.node.BulletList; + +public class BulletListHolder extends ListHolder { + private final char marker; + + public BulletListHolder(BulletListHolder parent, BulletList list) { + super(parent); + marker = list.getBulletMarker(); + } + + public char getMarker() { + return marker; + } +} diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java b/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java new file mode 100644 index 000000000..10d65d8a5 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java @@ -0,0 +1,27 @@ +package org.commonmark.renderer.text.holder; + +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/holder/OrderedListHolder.java b/commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java new file mode 100644 index 000000000..5acb51e4d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java @@ -0,0 +1,26 @@ +package org.commonmark.renderer.text.holder; + +import org.commonmark.node.OrderedList; + +public class OrderedListHolder extends ListHolder { + private final char delimiter; + private int counter; + + public OrderedListHolder(OrderedListHolder parent, OrderedList list) { + super(parent); + delimiter = list.getDelimiter(); + counter = list.getStartNumber(); + } + + public char getDelimiter() { + return delimiter; + } + + public int getCounter() { + return counter; + } + + public void increaseCounter() { + counter++; + } +} From fc0ebbec7057f68534fc6ee07bb8f92ad4492b9c Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Wed, 2 Aug 2017 17:07:47 +0300 Subject: [PATCH 229/815] Avoid url doubling in TextContent E.g.: Destination and title are equals when parsing by autolink-ext, that leads to displaying url 2 times --- .../commonmark/renderer/text/CoreTextContentNodeRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java index 031c89fd7..105339957 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -223,7 +223,7 @@ private void writeText(String text) { private void writeLink(Node node, String title, String destination) { boolean hasChild = node.getFirstChild() != null; - boolean hasTitle = title != null; + boolean hasTitle = title != null && !title.equals(destination); boolean hasDestination = destination != null && !destination.equals(""); if (hasChild) { From 9c4e2a0f356f07dd537f13d7428d9eefcd70a98b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 9 Aug 2017 13:36:33 +1000 Subject: [PATCH 230/815] Fix formatting --- .../commonmark/internal/InlineParserImpl.java | 9 +++---- .../internal/StaggeredDelimiterProcessor.java | 24 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 5a5335d07..07bd65890 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -6,8 +6,8 @@ import org.commonmark.internal.util.Html5Entities; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; -import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.*; import java.util.regex.Matcher; @@ -138,10 +138,11 @@ private static void addDelimiterProcessors(Iterable delimite char closing = delimiterProcessor.getClosingCharacter(); if (opening == closing) { DelimiterProcessor old = map.get(opening); - if(old != null && old.getOpeningCharacter() == old.getClosingCharacter()) { + if (old != null && old.getOpeningCharacter() == old.getClosingCharacter()) { StaggeredDelimiterProcessor s; - if(old instanceof StaggeredDelimiterProcessor) s = (StaggeredDelimiterProcessor)old; - else { + if (old instanceof StaggeredDelimiterProcessor) { + s = (StaggeredDelimiterProcessor) old; + } else { s = new StaggeredDelimiterProcessor(opening); s.add(old); } diff --git a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java index 026b3889b..c510edbe5 100644 --- a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java @@ -43,38 +43,40 @@ void add(DelimiterProcessor dp) { final int len = dp.getMinLength(); ListIterator it = processors.listIterator(); boolean added = false; - while(it.hasNext()) { + while (it.hasNext()) { DelimiterProcessor p = it.next(); int pLen = p.getMinLength(); - if(len > pLen) { + if (len > pLen) { it.previous(); it.add(dp); added = true; break; - } else if(len == pLen) { - throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length "+len); + } else if (len == pLen) { + throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len); } } - if(!added) { + if (!added) { processors.add(dp); this.minLength = len; } } private DelimiterProcessor findProcessor(int len) { - for(DelimiterProcessor p : processors) { - if(p.getMinLength() <= len) return p; - } - return processors.getFirst(); + for (DelimiterProcessor p : processors) { + if (p.getMinLength() <= len) { + return p; + } + } + return processors.getFirst(); } @Override public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { - return findProcessor(opener.length()).getDelimiterUse(opener, closer); + return findProcessor(opener.length()).getDelimiterUse(opener, closer); } @Override public void process(Text opener, Text closer, int delimiterUse) { - findProcessor(delimiterUse).process(opener, closer, delimiterUse); + findProcessor(delimiterUse).process(opener, closer, delimiterUse); } } From f7f890218e35ef977a61d72c8c21cca576e4d111 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 9 Aug 2017 13:51:32 +1000 Subject: [PATCH 231/815] Add test for staggered delimiter processors and documentation --- .../java/org/commonmark/parser/Parser.java | 22 +++-- .../test/DelimiterProcessorTest.java | 82 ++++++++++++++++++- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index d9d904cd5..b51f10238 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -35,6 +35,9 @@ private Parser(Builder builder) { this.inlineParserFactory = builder.inlineParserFactory; this.postProcessors = builder.postProcessors; this.delimiterProcessors = builder.delimiterProcessors; + + // Try to construct an inline parser. This might raise exceptions in case of invalid configuration. + getInlineParser(); } /** @@ -142,7 +145,6 @@ public Builder extensions(Iterable extensions) { * Describe the list of markdown features the parser will recognize and parse. *

    * By default, CommonMark will recognize and parse the following set of "block" elements: - * *

      *
    • {@link Heading} ({@code #}) *
    • {@link HtmlBlock} ({@code }) @@ -152,7 +154,6 @@ public Builder extensions(Iterable extensions) { *
    • {@link BlockQuote} ({@code >}) *
    • {@link ListBlock} (Ordered / Unordered List) ({@code 1. / *}) *
    - * *

    * To parse only a subset of the features listed above, pass a list of each feature's associated {@link Block} class. *

    @@ -164,8 +165,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) { @@ -188,6 +188,16 @@ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { return this; } + /** + * Adds 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 + * than one delimiter processor with the same character and minimum length is invalid. + * + * @param delimiterProcessor a delimiter processor implementation + * @return {@code this} + */ public Builder customDelimiterProcessor(DelimiterProcessor delimiterProcessor) { delimiterProcessors.add(delimiterProcessor); return this; @@ -200,7 +210,7 @@ public Builder postProcessor(PostProcessor postProcessor) { /** * Overrides the parser used for inline markdown processing. - * + *

    * Provide an implementation of InlineParserFactory which provides a custom inline parser * to modify how the following are parsed: * bold (**) @@ -209,7 +219,7 @@ public Builder postProcessor(PostProcessor postProcessor) { * backtick quote (`) * link ([title](http://)) * image (![alt](http://)) - * + *

    *

    * Note that if this method is not called or the inline parser factory is set to null, then the default * implementation will be used. diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 899a70d29..97a7543e5 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -1,8 +1,5 @@ package org.commonmark.test; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.node.CustomNode; import org.commonmark.node.Node; import org.commonmark.node.Text; @@ -10,6 +7,9 @@ import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; 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.Collections; @@ -47,6 +47,24 @@ public void asymmetricDelimiter() { assertRendering("}foo{ bar", "

    }foo{ bar

    \n"); } + @Test + public void multipleDelimitersWithDifferentLengths() { + Parser parser = Parser.builder() + .customDelimiterProcessor(new OneTildeDelimiterProcessor()) + .customDelimiterProcessor(new TwoTildesDelimiterProcessor()) + .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~~~"))); + } + + @Test(expected = IllegalArgumentException.class) + public void multipleDelimitersWithSameLength() { + Parser.builder() + .customDelimiterProcessor(new OneTildeDelimiterProcessor()) + .customDelimiterProcessor(new OneTildeDelimiterProcessor()) + .build(); + } + @Override protected String render(String source) { Node node = PARSER.parse(source); @@ -159,4 +177,62 @@ public void render(Node node) { } } } + + private static class OneTildeDelimiterProcessor implements DelimiterProcessor { + + @Override + public char getOpeningCharacter() { + return '~'; + } + + @Override + public char getClosingCharacter() { + return '~'; + } + + @Override + public int getMinLength() { + return 1; + } + + @Override + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + return 1; + } + + @Override + public void process(Text opener, Text closer, int delimiterUse) { + opener.insertAfter(new Text("(1)")); + closer.insertBefore(new Text("(/1)")); + } + } + + private static class TwoTildesDelimiterProcessor implements DelimiterProcessor { + + @Override + public char getOpeningCharacter() { + return '~'; + } + + @Override + public char getClosingCharacter() { + return '~'; + } + + @Override + public int getMinLength() { + return 2; + } + + @Override + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + return 2; + } + + @Override + public void process(Text opener, Text closer, int delimiterUse) { + opener.insertAfter(new Text("(2)")); + closer.insertBefore(new Text("(/2)")); + } + } } From 810e39bb5ad2c9631b96f6f397c7169f5d35d23b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 9 Aug 2017 15:52:46 +1000 Subject: [PATCH 232/815] Add tests for thread-safety and a section to the readme (#83) --- README.md | 10 ++++ .../org/commonmark/test/HtmlRendererTest.java | 46 +++++++++++++++---- .../java/org/commonmark/test/ParserTest.java | 39 ++++++++++++++-- 3 files changed, 82 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ca39f87df..b5d737e7c 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,16 @@ 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. +#### Thread-safety + +Both the `Parser` and `HtmlRenderer` are designed so that you can +configure them once using the builders and then use them multiple +times/from multiple threads. This is done by separating the state for +parsing/rendering from the configuration. + +Having said that, there might be bugs of course. If you find one, please +report an issue. + ### API documentation Javadocs are available online on diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index ca9af91fe..016fbee94 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -1,24 +1,24 @@ package org.commonmark.test; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.html.AttributeProvider; -import org.commonmark.renderer.html.AttributeProviderContext; -import org.commonmark.renderer.html.AttributeProviderFactory; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.Image; import org.commonmark.node.Link; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.*; +import org.commonmark.spec.SpecReader; import org.junit.Test; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; +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.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class HtmlRendererTest { @@ -207,6 +207,34 @@ public void imageAltTextWithEntities() { defaultRenderer().render(parse("![foo ä](/url)\n"))); } + @Test + public void threading() throws Exception { + Parser parser = Parser.builder().build(); + String spec = SpecReader.readSpec(); + final Node document = parser.parse(spec); + + final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build(); + String expectedRendering = htmlRenderer.render(document); + + // Render in parallel using the same HtmlRenderer instance. + List> futures = new ArrayList<>(); + ExecutorService 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); + } + }); + futures.add(future); + } + + for (Future future : futures) { + String rendering = future.get(); + assertThat(rendering, is(expectedRendering)); + } + } + private static HtmlRenderer defaultRenderer() { return HtmlRenderer.builder().build(); } diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index ddf030283..f54172c4e 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,22 +1,26 @@ package org.commonmark.test; +import org.commonmark.node.*; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.InlineParserFactory; -import org.commonmark.parser.delimiter.DelimiterProcessor; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.commonmark.parser.block.*; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.spec.SpecReader; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +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; +import java.util.concurrent.Future; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertEquals; @@ -103,7 +107,7 @@ public void parse(String input, Node node) { } }; - InlineParserFactory fakeInlineParserFactory = new InlineParserFactory(){ + InlineParserFactory fakeInlineParserFactory = new InlineParserFactory() { @Override public InlineParser create(InlineParserContext inlineParserContext) { @@ -117,6 +121,33 @@ public InlineParser create(InlineParserContext inlineParserContext) { assertThat(parser.parse(input).getFirstChild().getFirstChild(), instanceOf(ThematicBreak.class)); } + @Test + public void threading() throws Exception { + final Parser parser = Parser.builder().build(); + final String spec = SpecReader.readSpec(); + + HtmlRenderer renderer = HtmlRenderer.builder().build(); + String expectedRendering = renderer.render(parser.parse(spec)); + + // Parse in parallel using the same Parser instance. + List> futures = new ArrayList<>(); + ExecutorService 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); + } + }); + futures.add(future); + } + + for (Future future : futures) { + Node node = future.get(); + assertThat(renderer.render(node), is(expectedRendering)); + } + } + private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n, notNullValue()); From 2f016c43362f289850cf1e0cc48825bebb8e5bf5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Aug 2017 18:44:58 +1000 Subject: [PATCH 233/815] List punctuation characters in same order as spec --- .../src/main/java/org/commonmark/internal/InlineParserImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 07bd65890..8f9873404 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -26,7 +26,7 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { + "|" + PROCESSINGINSTRUCTION + "|" + DECLARATION + "|" + CDATA + ")"; private static final String ENTITY = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"; - private static final String ASCII_PUNCTUATION = "'!\"#\\$%&\\(\\)\\*\\+,\\-\\./:;<=>\\?@\\[\\\\\\]\\^_`\\{\\|\\}~"; + private static final String ASCII_PUNCTUATION = "!\"#\\$%&'\\(\\)\\*\\+,\\-\\./:;<=>\\?@\\[\\\\\\]\\^_`\\{\\|\\}~"; private static final Pattern PUNCTUATION = Pattern .compile("^[" + ASCII_PUNCTUATION + "\\p{Pc}\\p{Pd}\\p{Pe}\\p{Pf}\\p{Pi}\\p{Po}\\p{Ps}]"); From 82f106c6d107c955520b6d43069984c8b2334e00 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Aug 2017 19:01:07 +1000 Subject: [PATCH 234/815] Fix `[\]` being parsed as link label --- .../main/java/org/commonmark/internal/InlineParserImpl.java | 2 +- .../src/test/java/org/commonmark/test/SpecialInputTest.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 8f9873404..ccfeb5711 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -46,7 +46,7 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { "^(?:" + REG_CHAR + "+|" + ESCAPED_CHAR + "|\\\\|" + IN_PARENS_NOSP + ")*"); private static final Pattern LINK_LABEL = Pattern - .compile("^\\[(?:[^\\\\\\[\\]]|" + ESCAPED_CHAR + "|\\\\){0,999}\\]"); + .compile("^\\[(?:[^\\\\\\[\\]]|" + ESCAPED_CHAR + "){0,999}\\]"); private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 3d0b6e536..a00149c57 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -106,4 +106,9 @@ public void linkLabelLength() { assertRendering("[foo][12" + label2 + "]\n\n[12" + label2 + "]: /", "

    [foo][12" + label2 + "]

    \n

    [12" + label2 + "]: /

    \n"); } + + @Test + public void linkReferenceBackslash() { + assertRendering("[\\]: test", "

    []: test

    \n"); + } } From abe7269c43d3da8eae61b7557cf80940be2cee05 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Aug 2017 19:05:45 +1000 Subject: [PATCH 235/815] Simplify left/right flanking logic to be closer to spec --- .../main/java/org/commonmark/internal/InlineParserImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index ccfeb5711..2b577584f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -774,9 +774,9 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char boolean afterIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(after).matches(); boolean leftFlanking = !afterIsWhitespace && - !(afterIsPunctuation && !beforeIsWhitespace && !beforeIsPunctuation); + (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation); boolean rightFlanking = !beforeIsWhitespace && - !(beforeIsPunctuation && !afterIsWhitespace && !afterIsPunctuation); + (!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation); boolean canOpen; boolean canClose; if (delimiterChar == '_') { From ba53b3576b3c537ecf615313360cf0780014c973 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Aug 2017 19:14:19 +1000 Subject: [PATCH 236/815] Fix multiple of 3 rule for emphasis parsing (see commonmark/cmark#177) --- .../java/org/commonmark/internal/Delimiter.java | 10 ++++++++-- .../org/commonmark/internal/InlineParserImpl.java | 15 ++++++++------- .../inline/EmphasisDelimiterProcessor.java | 2 +- .../commonmark/parser/delimiter/DelimiterRun.java | 6 ++++++ .../org/commonmark/test/SpecialInputTest.java | 7 +++++++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index 9f4f66e88..5988e9508 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -24,7 +24,8 @@ public class Delimiter implements DelimiterRun { public Delimiter previous; public Delimiter next; - public int numDelims = 1; + public int length = 1; + public int originalLength = 1; public Delimiter(Text node, char delimiterChar, boolean canOpen, boolean canClose, Delimiter previous) { this.node = node; @@ -46,6 +47,11 @@ public boolean canClose() { @Override public int length() { - return numDelims; + return length; + } + + @Override + public int originalLength() { + return originalLength; } } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 2b577584f..c32f64b67 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -448,15 +448,16 @@ private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char deli if (res == null) { return false; } - int numDelims = res.count; + int length = res.count; int startIndex = index; - index += numDelims; + index += length; Text node = appendText(input, startIndex, index); // Add entry to stack for this opener lastDelimiter = new Delimiter(node, delimiterChar, res.canOpen, res.canClose, lastDelimiter); - lastDelimiter.numDelims = numDelims; + lastDelimiter.length = length; + lastDelimiter.originalLength = length; if (lastDelimiter.previous != null) { lastDelimiter.previous.next = lastDelimiter; } @@ -853,8 +854,8 @@ private void processDelimiters(Delimiter stackBottom) { Text closerNode = closer.node; // Remove number of used delimiters from stack and inline nodes. - opener.numDelims -= useDelims; - closer.numDelims -= useDelims; + opener.length -= useDelims; + closer.length -= useDelims; openerNode.setLiteral( openerNode.getLiteral().substring(0, openerNode.getLiteral().length() - useDelims)); @@ -869,11 +870,11 @@ private void processDelimiters(Delimiter stackBottom) { delimiterProcessor.process(openerNode, closerNode, useDelims); // No delimiter characters left to process, so we can remove delimiter and the now empty node. - if (opener.numDelims == 0) { + if (opener.length == 0) { removeDelimiterAndNode(opener); } - if (closer.numDelims == 0) { + if (closer.length == 0) { Delimiter next = closer.next; removeDelimiterAndNode(closer); closer = next; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index d116f6126..414ffa603 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -33,7 +33,7 @@ public int getMinLength() { @Override public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { // "multiple of 3" rule for internal delimiter runs - if ((opener.canClose() || closer.canOpen()) && (opener.length() + closer.length()) % 3 == 0) { + if ((opener.canClose() || closer.canOpen()) && (opener.originalLength() + closer.originalLength()) % 3 == 0) { return 0; } // calculate actual number of delimiters used from this closer diff --git a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java index 949b2cb29..29bdb8731 100644 --- a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java +++ b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java @@ -19,4 +19,10 @@ public interface DelimiterRun { * @return the number of characters in this delimiter run (that are left for processing) */ int length(); + + /** + * @return the number of characters originally in this delimiter run; at the start of processing, this is the same + * as {{@link #length()}} + */ + int originalLength(); } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index a00149c57..b6b66887f 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -107,8 +107,15 @@ public void linkLabelLength() { "

    [foo][12" + label2 + "]

    \n

    [12" + label2 + "]: /

    \n"); } + // commonmark/CommonMark#468 @Test public void linkReferenceBackslash() { assertRendering("[\\]: test", "

    []: test

    \n"); } + + // commonmark/cmark#177 + @Test + public void emphasisMultipleOf3Rule() { + assertRendering("a***b* c*", "

    a*b c

    \n"); + } } From 95b916597e3a032b06cf25607af16f1a5fdc9665 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Aug 2017 12:50:38 +1000 Subject: [PATCH 237/815] Update to CommonMark spec 0.28 (#94) --- .../src/main/resources/spec.txt | 152 ++++++++++++------ 1 file changed, 103 insertions(+), 49 deletions(-) diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index c66f93b77..9fd584139 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.27 -date: '2016-11-18' +version: 0.28 +date: '2017-08-01' license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -11,10 +11,12 @@ license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ## What is Markdown? Markdown is a plain text format for writing structured documents, -based on conventions used for indicating formatting in email and -usenet posts. It was developed in 2004 by John Gruber, who wrote -the first Markdown-to-HTML converter in Perl, and it soon became -ubiquitous. In the next decade, dozens of implementations were +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) +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 Markdown syntax with conventions for footnotes, tables, and other document elements. Some allowed Markdown documents to be @@ -312,7 +314,7 @@ form feed (`U+000C`), or carriage return (`U+000D`). characters]. A [Unicode whitespace character](@) is -any code point in the Unicode `Zs` class, or a tab (`U+0009`), +any code point in the Unicode `Zs` general category, or a tab (`U+0009`), carriage return (`U+000D`), newline (`U+000A`), or form feed (`U+000C`). @@ -331,7 +333,7 @@ is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, A [punctuation character](@) is an [ASCII punctuation character] or anything in -the Unicode classes `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. +the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. ## Tabs @@ -402,8 +404,8 @@ as indentation with four spaces would: Normally the `>` that begins a block quote may be followed optionally by a space, which is not considered part of the content. In the following case `>` is followed by a tab, -which is treated as if it were expanded into spaces. -Since one of theses spaces is considered part of the +which is treated as if it were expanded into three spaces. +Since one of these spaces is considered part of the delimiter, `foo` is considered to be indented six spaces inside the block quote context, so we get an indented code block starting with two spaces. @@ -481,7 +483,7 @@ We can think of a document as a sequence of quotations, lists, headings, rules, and code blocks. Some blocks (like block quotes and list items) contain other blocks; others (like headings and paragraphs) contain [inline](@) content---text, -links, emphasized text, images, code, and so on. +links, emphasized text, images, code spans, and so on. ## Precedence @@ -1643,6 +1645,15 @@ With tildes:
    ```````````````````````````````` +Fewer than three backticks is not enough: + +```````````````````````````````` example +`` +foo +`` +. +

    foo

    +```````````````````````````````` The closing code fence must use the same character as the opening fence: @@ -2031,6 +2042,37 @@ or [closing tag] (with any [tag name] other than `script`, or the end of the line.\ **End condition:** line is followed by a [blank line]. +HTML blocks continue until they are closed by their appropriate +[end condition], or the last line of the document or other [container block]. +This means any HTML **within an HTML block** that might otherwise be recognised +as a start condition will be ignored by the parser and passed through as-is, +without changing the parser's state. + +For instance, `
    ` within a HTML block started by `` will not affect
    +the parser state; as the HTML block was started in by start condition 6, it
    +will end at any blank line. This can be surprising:
    +
    +```````````````````````````````` example
    +
    +
    +**Hello**,
    +
    +_world_.
    +
    +
    +. +
    +
    +**Hello**,
    +

    world. +

    +
    +```````````````````````````````` + +In this case, the HTML block is terminated by the newline — the `**hello**` +text remains verbatim — and regular parsing resumes, with a paragraph, +emphasised `world` and inline and block HTML following. + All types of [HTML blocks] except type 7 may interrupt a paragraph. Blocks of type 7 may not interrupt a paragraph. (This restriction is intended to prevent unwanted interpretation @@ -3637,11 +3679,15 @@ The following rules define [list items]: If the list item is ordered, then it is also assigned a start number, based on the ordered list marker. - Exceptions: When the first list item in a [list] interrupts - a paragraph---that is, when it starts on a line that would - otherwise count as [paragraph continuation text]---then (a) - the lines *Ls* must not begin with a blank line, and (b) if - the list item is ordered, the start number must be 1. + Exceptions: + + 1. When the first list item in a [list] interrupts + a paragraph---that is, when it starts on a line that would + otherwise count as [paragraph continuation text]---then (a) + the lines *Ls* must not begin with a blank line, and (b) if + the list item is ordered, the start number must be 1. + 2. If any line is a [thematic break][thematic breaks] then + that line is not a list item. For example, let *Ls* be the lines @@ -5796,6 +5842,15 @@ we just have literal backticks:

    `foo

    ```````````````````````````````` +The following case also illustrates the need for opening and +closing backtick strings to be equal in length: + +```````````````````````````````` example +`foo``bar`` +. +

    `foobar

    +```````````````````````````````` + ## Emphasis and strong emphasis @@ -5845,19 +5900,20 @@ for efficient parsing strategies that do not backtrack. First, some definitions. A [delimiter run](@) is either a sequence of one or more `*` characters that is not preceded or -followed by a `*` character, or a sequence of one or more `_` -characters that is not preceded or followed by a `_` character. +followed by a non-backslash-escaped `*` character, or a sequence +of one or more `_` characters that is not preceded or followed by +a non-backslash-escaped `_` character. A [left-flanking delimiter run](@) is a [delimiter run] that is (a) not followed by [Unicode whitespace], -and (b) either not followed by a [punctuation character], or +and (b) not followed by a [punctuation character], or preceded by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. A [right-flanking delimiter run](@) is a [delimiter run] that is (a) not preceded by [Unicode whitespace], -and (b) either not preceded by a [punctuation character], or +and (b) not preceded by a [punctuation character], or followed by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. @@ -5936,7 +5992,7 @@ The following rules define emphasis and strong emphasis: 7. A double `**` [can close strong emphasis](@) iff it is part of a [right-flanking delimiter run]. -8. A double `__` [can close strong emphasis] +8. A double `__` [can close strong emphasis] iff it is part of a [right-flanking delimiter run] and either (a) not part of a [left-flanking delimiter run] or (b) part of a [left-flanking delimiter run] @@ -5976,8 +6032,8 @@ the following principles resolve ambiguity: an interpretation `...` is always preferred to `...`. -14. An interpretation `...` is always - preferred to `..`. +14. An interpretation `...` is always + preferred to `...`. 15. When two potential emphasis or strong emphasis spans overlap, so that the second begins before the first ends and ends after @@ -7000,14 +7056,14 @@ Rule 14: ```````````````````````````````` example ***foo*** . -

    foo

    +

    foo

    ```````````````````````````````` ```````````````````````````````` example _____foo_____ . -

    foo

    +

    foo

    ```````````````````````````````` @@ -7148,8 +7204,9 @@ A [link destination](@) consists of either - a nonempty sequence of characters that does not include ASCII space or control characters, and includes parentheses only if (a) they are backslash-escaped or (b) they are part of - a balanced pair of unescaped parentheses that is not itself - inside a balanced pair of unescaped parentheses. + a balanced pair of unescaped parentheses. (Implementations + may impose limits on parentheses nesting to avoid performance + issues, but at least three levels of nesting should be supported.) A [link title](@) consists of either @@ -7255,35 +7312,29 @@ Parentheses inside the link destination may be escaped:

    link

    ```````````````````````````````` -One level of balanced parentheses is allowed without escaping: - -```````````````````````````````` example -[link]((foo)and(bar)) -. -

    link

    -```````````````````````````````` - -However, if you have parentheses within parentheses, you need to escape -or use the `<...>` form: +Any number of parentheses are allowed without escaping, as long as they are +balanced: ```````````````````````````````` example [link](foo(and(bar))) . -

    [link](foo(and(bar)))

    +

    link

    ```````````````````````````````` +However, if you have unbalanced parentheses, you need to escape or use the +`<...>` form: ```````````````````````````````` example -[link](foo(and\(bar\))) +[link](foo\(and\(bar\)) . -

    link

    +

    link

    ```````````````````````````````` ```````````````````````````````` example -[link]() +[link]() . -

    link

    +

    link

    ```````````````````````````````` @@ -7567,13 +7618,16 @@ that [matches] a [link reference definition] elsewhere in the document. A [link label](@) begins with a left bracket (`[`) and ends with the first right bracket (`]`) that is not backslash-escaped. Between these brackets there must be at least one [non-whitespace character]. -Unescaped square bracket characters are not allowed in -[link labels]. A link label can have at most 999 -characters inside the square brackets. +Unescaped square bracket characters are not allowed inside the +opening and closing square brackets of [link labels]. A link +label can have at most 999 characters inside the square +brackets. One label [matches](@) another just in case their normalized forms are equal. To normalize a -label, perform the *Unicode case fold* and collapse consecutive internal +label, strip off the opening and closing brackets, +perform the *Unicode case fold*, strip leading and trailing +[whitespace] and collapse consecutive internal [whitespace] to a single space. If there are multiple matching reference link definitions, the one that comes first in the document is used. (It is desirable in such cases to emit a warning.) @@ -8326,11 +8380,11 @@ The link labels are case-insensitive: ```````````````````````````````` -If you just want bracketed text, you can backslash-escape the -opening `!` and `[`: +If you just want a literal `!` followed by bracketed text, you can +backslash-escape the opening `[`: ```````````````````````````````` example -\!\[foo] +!\[foo] [foo]: /url "title" . From 41ad23dbd7d7a4658afde389efc38d43a81ab97b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Aug 2017 12:53:15 +1000 Subject: [PATCH 238/815] Adapt to changed emphasis parsing rule (spec 0.28, #94) From changelog: > Change Rule 14 for Emphasis. Previously the nesting > Strong (Emph (...)) was preferred over Emph (Strong (...)). > This change makes Emph (Strong (...)) preferred. --- .../internal/inline/EmphasisDelimiterProcessor.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 414ffa603..60f192bef 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -37,11 +37,10 @@ public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { return 0; } // calculate actual number of delimiters used from this closer - if (opener.length() < 3 || closer.length() < 3) { - return closer.length() <= opener.length() ? - closer.length() : opener.length(); + if (opener.length() >= 2 && closer.length() >= 2) { + return 2; } else { - return closer.length() % 2 == 0 ? 2 : 1; + return 1; } } From 787e85a751aac19a41731f457fabcd80cfaf7b0a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 24 Aug 2017 18:08:26 +1000 Subject: [PATCH 239/815] Allow nested parentheses in link destination parsing (spec 0.28, #94) From changelog: > Allow unlimited balanced pairs of parentheses in link URLs Before, only one level of parentheses was allowed. --- .../commonmark/internal/InlineParserImpl.java | 51 +++++++++++++++---- .../org/commonmark/test/SpecialInputTest.java | 6 +++ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index c32f64b67..01e5b63ca 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -16,8 +16,6 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { private static final String ESCAPED_CHAR = "\\\\" + Escaping.ESCAPABLE; - private static final String REG_CHAR = "[^\\\\()\\x00-\\x20]"; - private static final String IN_PARENS_NOSP = "\\((" + REG_CHAR + '|' + ESCAPED_CHAR + ")*\\)"; private static final String HTMLCOMMENT = "|"; private static final String PROCESSINGINSTRUCTION = "[<][?].*?[?][>]"; private static final String DECLARATION = "]*>"; @@ -42,9 +40,6 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile( "^(?:[<](?:[^<> \\t\\n\\\\\\x00]" + '|' + ESCAPED_CHAR + '|' + "\\\\)*[>])"); - private static final Pattern LINK_DESTINATION = Pattern.compile( - "^(?:" + REG_CHAR + "+|" + ESCAPED_CHAR + "|\\\\|" + IN_PARENS_NOSP + ")*"); - private static final Pattern LINK_LABEL = Pattern .compile("^\\[(?:[^\\\\\\[\\]]|" + ESCAPED_CHAR + "){0,999}\\]"); @@ -641,12 +636,48 @@ private String parseLinkDestination() { return Escaping.unescapeString(res.substring(1, res.length() - 1)); } } else { - res = match(LINK_DESTINATION); - if (res != null) { - return Escaping.unescapeString(res); - } else { - return null; + int startIndex = index; + parseLinkDestinationWithBalancedParens(); + return Escaping.unescapeString(input.substring(startIndex, index)); + } + } + + private void parseLinkDestinationWithBalancedParens() { + int parens = 0; + while (true) { + char c = peek(); + switch (c) { + case '\0': + return; + case '\\': + // go to character after backslash + index++; + // stop if that took us to the end of input + if (peek() == '\0') { + return; + } + // otherwise, we'll skip over character after backslash + break; + case '(': + parens++; + break; + case ')': + if (parens == 0) { + return; + } else { + parens--; + } + break; + case ' ': + // ASCII space + return; + default: + // or control character + if (Character.isISOControl(c)) { + return; + } } + index++; } } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index b6b66887f..d0dfbeb99 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -107,6 +107,12 @@ public void linkLabelLength() { "

    [foo][12" + label2 + "]

    \n

    [12" + label2 + "]: /

    \n"); } + @Test + public void linkDestinationEscaping() { + assertRendering("[link](\\))", "

    link

    \n"); + assertRendering("[link](\\ )", "

    link

    \n"); + } + // commonmark/CommonMark#468 @Test public void linkReferenceBackslash() { From 726006e755bd61ca08762deeb041f8c938bd25de Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 28 Aug 2017 18:25:03 +1000 Subject: [PATCH 240/815] Better fix for `[\]` being parsed as a link label The previous solution had the problem that `[a\b]` no longer worked. But that should result in `\` being a literal backslash because `b` is not escapable. --- .../java/org/commonmark/internal/InlineParserImpl.java | 10 +++++++--- .../java/org/commonmark/test/SpecialInputTest.java | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 01e5b63ca..6cec32020 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -40,8 +40,7 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile( "^(?:[<](?:[^<> \\t\\n\\\\\\x00]" + '|' + ESCAPED_CHAR + '|' + "\\\\)*[>])"); - private static final Pattern LINK_LABEL = Pattern - .compile("^\\[(?:[^\\\\\\[\\]]|" + ESCAPED_CHAR + "){0,999}\\]"); + private static final Pattern LINK_LABEL = Pattern.compile("^\\[(?:[^\\\\\\[\\]]|\\\\.)*\\]"); private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); @@ -699,7 +698,12 @@ private String parseLinkTitle() { */ private int parseLinkLabel() { String m = match(LINK_LABEL); - return m == null ? 0 : m.length(); + // Spec says "A link label can have at most 999 characters inside the square brackets" + if (m == null || m.length() > 1001) { + return 0; + } else { + return m.length(); + } } /** diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index d0dfbeb99..8eb3cb530 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -116,7 +116,12 @@ public void linkDestinationEscaping() { // commonmark/CommonMark#468 @Test public void linkReferenceBackslash() { + // Backslash escapes ']', so not a valid link label assertRendering("[\\]: test", "

    []: test

    \n"); + // Backslash is a literal, so valid + assertRendering("[a\\b]\n\n[a\\b]: test", "

    a\\b

    \n"); + // Backslash escapes `]` but there's another `]`, valid + assertRendering("[a\\]]\n\n[a\\]]: test", "

    a]

    \n"); } // commonmark/cmark#177 From e303d1c4e01d4e1ab7ce04a37c1947409dca3082 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 28 Aug 2017 19:13:33 +1000 Subject: [PATCH 241/815] Fix `[foo](<\>)` resulting in `\` in href This was the same problem as in link labels. --- .../org/commonmark/internal/InlineParserImpl.java | 3 +-- .../java/org/commonmark/test/SpecialInputTest.java | 11 +++++++++-- 2 files changed, 10 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 6cec32020..6ddf6a23e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -37,8 +37,7 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { '|' + "\\((" + ESCAPED_CHAR + "|[^)\\x00])*\\))"); - private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile( - "^(?:[<](?:[^<> \\t\\n\\\\\\x00]" + '|' + ESCAPED_CHAR + '|' + "\\\\)*[>])"); + private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile("^(?:[<](?:[^<> \\t\\n\\\\]|\\\\.)*[>])"); private static final Pattern LINK_LABEL = Pattern.compile("^\\[(?:[^\\\\\\[\\]]|\\\\.)*\\]"); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 8eb3cb530..5d7ae2919 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -109,8 +109,15 @@ public void linkLabelLength() { @Test public void linkDestinationEscaping() { - assertRendering("[link](\\))", "

    link

    \n"); - assertRendering("[link](\\ )", "

    link

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

    foo

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

    foo

    \n"); + + // Backslash escapes `>`, so it's not a `(<...>)` link, but a `(...)` link instead + assertRendering("[foo](<\\>)", "

    foo

    \n"); + // Backslash is a literal, so valid + assertRendering("[foo]()", "

    foo

    \n"); + // Backslash escapes `>` but there's another `>`, valid + assertRendering("[foo](>)", "

    foo

    \n"); } // commonmark/CommonMark#468 From 02fc1e5f2191f70d891be603a9f40033a5bff7f3 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 28 Aug 2017 20:25:50 +1000 Subject: [PATCH 242/815] Fix text node merging when opening/closing delimiters are adjacent (#96) When using asymmetric delimiters, e.g. `{` as an opener and `}` as a closer, the input `{}` means there are no nodes between the delimiters. So we have the following nodes: 1. `{` (opener) 2. `}` (closer) The code to merge text nodes was merging nodes starting from opener.next and ending with closer.previous. In the above situation, that would mean merging starting from "2" (and ending at "1"). Because it never got to node "1", it would just keep merging nodes until the end of the block. The NPE in #96 is a possible symptom of that. In other cases, delimiter processing might lead to the wrong result. --- .../commonmark/internal/InlineParserImpl.java | 28 +++++++++++++++---- .../test/DelimiterProcessorTest.java | 1 + 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 6ddf6a23e..45da99d90 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -175,7 +175,7 @@ public void parse(String content, Node block) { } while (moreToParse); processDelimiters(null); - mergeTextNodes(block.getFirstChild(), block.getLastChild()); + mergeChildTextNodes(block); } /** @@ -582,7 +582,7 @@ private boolean parseCloseBracket() { // Process delimiters such as emphasis inside link/image processDelimiters(opener.previousDelimiter); - mergeTextNodes(linkOrImage.getFirstChild(), linkOrImage.getLastChild()); + mergeChildTextNodes(linkOrImage); // We don't need the corresponding text node anymore, we turned it into a link/image node opener.node.unlink(); removeLastBracket(); @@ -899,8 +899,8 @@ private void processDelimiters(Delimiter stackBottom) { removeDelimitersBetween(opener, closer); // The delimiter processor can re-parent the nodes between opener and closer, - // so make sure they're contiguous already. - mergeTextNodes(openerNode.getNext(), closerNode.getPrevious()); + // so make sure they're contiguous already. Exclusive because we want to keep opener/closer themselves. + mergeTextNodesBetweenExclusive(openerNode, closerNode); delimiterProcessor.process(openerNode, closerNode, useDelims); // No delimiter characters left to process, so we can remove delimiter and the now empty node. @@ -958,7 +958,25 @@ private void removeDelimiter(Delimiter delim) { } } - private void mergeTextNodes(Node fromNode, Node toNode) { + private void mergeTextNodesBetweenExclusive(Node fromNode, Node toNode) { + // No nodes between them + if (fromNode == toNode || fromNode.getNext() == toNode) { + return; + } + + mergeTextNodesInclusive(fromNode.getNext(), toNode.getPrevious()); + } + + private void mergeChildTextNodes(Node node) { + // No children or just one child node, no need for merging + if (node.getFirstChild() == node.getLastChild()) { + return; + } + + mergeTextNodesInclusive(node.getFirstChild(), node.getLastChild()); + } + + private void mergeTextNodesInclusive(Node fromNode, Node toNode) { Text first = null; Text last = null; int length = 0; diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 97a7543e5..c460826db 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -45,6 +45,7 @@ public void asymmetricDelimiter() { assertRendering("}foo} bar", "

    }foo} bar

    \n"); assertRendering("{foo{ bar", "

    {foo{ bar

    \n"); assertRendering("}foo{ bar", "

    }foo{ bar

    \n"); + assertRendering("{} {foo}", "

    FOO

    \n"); } @Test From 72df12d9b9e79c2e6c0ce2e75df7040447fd2983 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 1 Sep 2017 10:18:50 +1000 Subject: [PATCH 243/815] Update to autolink 0.7.0 to fix (#99) This change stops these and other examples from being linked: http://. http://" http:// --- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 ++-- commonmark-ext-autolink/pom.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index a9a66f484..a4985d9da 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -9,7 +9,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties echo "version.maven=0.9.0" >> test.properties -echo "version.maven_autolink=0.6.0" >> test.properties +echo "version.maven_autolink=0.7.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 874ae0f7b..865396896 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -30,12 +30,12 @@ path.report=../report # Version number of commonmark and extensions in maven central. version.maven=0.9.0 # Version number of autolink in maven central (not bundled with extension jar). -version.maven_autolink=0.6.0 +version.maven_autolink=0.7.0 # Version number of commonmark and extensions in project. version.snapshot=0.9.0-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). -version.snapshot_autolink=0.6.0 +version.snapshot_autolink=0.7.0 ``` If you're going to test on device with Android 15 then you can skip downloading emulator. diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index dadc2419d..37fb543a9 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.6.0 + 0.7.0 From fd78532d73d93d3ae90fc2b0975f3801c31717e9 Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Tue, 5 Sep 2017 13:06:05 +0300 Subject: [PATCH 244/815] Add test for mixed lists --- .../org/commonmark/test/TextContentRendererTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 1321ee99b..7a873b19d 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -173,6 +173,18 @@ public void textContentLists() { assertEquals("bar\n* foo\n - bar\n* foo", rendered); rendered = strippedRenderer().render(parse(source)); assertEquals("bar foo bar foo", rendered); + + source = "bar\n* foo\n 1. bar\n 2. bar\n* foo"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("bar\n* foo\n 1. bar\n 2. bar\n* foo", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("bar foo 1. bar 2. bar foo", rendered); + + source = "bar\n1. foo\n * bar\n * bar\n2. foo"; + rendered = defaultRenderer().render(parse(source)); + assertEquals("bar\n1. foo\n * bar\n * bar\n2. foo", rendered); + rendered = strippedRenderer().render(parse(source)); + assertEquals("bar 1. foo bar bar 2. foo", rendered); } @Test From d75e4807add3267677f461a1cc48a4e506151f6b Mon Sep 17 00:00:00 2001 From: JinneeJ Date: Tue, 5 Sep 2017 13:06:32 +0300 Subject: [PATCH 245/815] Add support for mixed lists for content renderer --- .../text/CoreTextContentNodeRenderer.java | 30 ++++++++++--------- .../text/holder/BulletListHolder.java | 2 +- .../renderer/text/holder/ListHolder.java | 2 +- .../text/holder/OrderedListHolder.java | 2 +- 4 files changed, 19 insertions(+), 17 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 105339957..952561322 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -3,6 +3,7 @@ import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.text.holder.BulletListHolder; +import org.commonmark.renderer.text.holder.ListHolder; import org.commonmark.renderer.text.holder.OrderedListHolder; import java.util.Arrays; @@ -17,8 +18,7 @@ public class CoreTextContentNodeRenderer extends AbstractVisitor implements Node protected final TextContentNodeRendererContext context; private final TextContentWriter textContent; - private OrderedListHolder orderedListHolder; - private BulletListHolder bulletListHolder; + private ListHolder listHolder; public CoreTextContentNodeRenderer(TextContentNodeRendererContext context) { this.context = context; @@ -73,16 +73,16 @@ public void visit(BlockQuote blockQuote) { @Override public void visit(BulletList bulletList) { - if (bulletListHolder != null) { + if (listHolder != null) { writeEndOfLine(); } - bulletListHolder = new BulletListHolder(bulletListHolder, bulletList); + listHolder = new BulletListHolder(listHolder, bulletList); visitChildren(bulletList); writeEndOfLineIfNeeded(bulletList, null); - if (bulletListHolder.getParent() != null) { - bulletListHolder = (BulletListHolder) bulletListHolder.getParent(); + if (listHolder.getParent() != null) { + listHolder = listHolder.getParent(); } else { - bulletListHolder = null; + listHolder = null; } } @@ -154,13 +154,15 @@ public void visit(Link link) { @Override public void visit(ListItem listItem) { - if (orderedListHolder != null) { + if (listHolder != null && listHolder instanceof OrderedListHolder) { + OrderedListHolder orderedListHolder = (OrderedListHolder) listHolder; String indent = context.stripNewlines() ? "" : orderedListHolder.getIndent(); textContent.write(indent + orderedListHolder.getCounter() + orderedListHolder.getDelimiter() + " "); visitChildren(listItem); writeEndOfLineIfNeeded(listItem, null); orderedListHolder.increaseCounter(); - } else if (bulletListHolder != null) { + } else if (listHolder != null && listHolder instanceof BulletListHolder) { + BulletListHolder bulletListHolder = (BulletListHolder) listHolder; if (!context.stripNewlines()) { textContent.write(bulletListHolder.getIndent() + bulletListHolder.getMarker() + " "); } @@ -171,16 +173,16 @@ public void visit(ListItem listItem) { @Override public void visit(OrderedList orderedList) { - if (orderedListHolder != null) { + if (listHolder != null) { writeEndOfLine(); } - orderedListHolder = new OrderedListHolder(orderedListHolder, orderedList); + listHolder = new OrderedListHolder(listHolder, orderedList); visitChildren(orderedList); writeEndOfLineIfNeeded(orderedList, null); - if (orderedListHolder.getParent() != null) { - orderedListHolder = (OrderedListHolder) orderedListHolder.getParent(); + if (listHolder.getParent() != null) { + listHolder = listHolder.getParent(); } else { - orderedListHolder = null; + listHolder = null; } } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java b/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java index 99ea09bd6..c58cb4a07 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java @@ -5,7 +5,7 @@ public class BulletListHolder extends ListHolder { private final char marker; - public BulletListHolder(BulletListHolder parent, BulletList list) { + public BulletListHolder(ListHolder parent, BulletList list) { super(parent); marker = list.getBulletMarker(); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java b/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java index 10d65d8a5..2db1d8fbe 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java @@ -1,6 +1,6 @@ package org.commonmark.renderer.text.holder; -abstract class ListHolder { +public abstract class ListHolder { private static final String INDENT_DEFAULT = " "; private static final String INDENT_EMPTY = ""; diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java b/commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java index 5acb51e4d..7cdb41d98 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java @@ -6,7 +6,7 @@ public class OrderedListHolder extends ListHolder { private final char delimiter; private int counter; - public OrderedListHolder(OrderedListHolder parent, OrderedList list) { + public OrderedListHolder(ListHolder parent, OrderedList list) { super(parent); delimiter = list.getDelimiter(); counter = list.getStartNumber(); From bdd007b9e295d9cd54744653dd275d729d4e1dc8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 6 Sep 2017 10:55:31 +1000 Subject: [PATCH 246/815] Try to fix android build on travis Getting "InvalidKeyException: EC parameters error" when downloading gradle. Running on precise might fix it. --- .travis.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index b595e0055..73fa9f08b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,13 @@ language: android -jdk: - - openjdk7 - - oraclejdk8 -env: - - TEST=java - - TEST=android matrix: - exclude: + include: - jdk: oraclejdk8 + env: TEST=java + - jdk: openjdk7 + env: TEST=java + - jdk: openjdk7 env: TEST=android + dist: precise android: components: - android-16 From b64321cc317f758723498ed6f985759c1a2305a2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 6 Sep 2017 11:20:51 +1000 Subject: [PATCH 247/815] Move text renderer util classes to internal package They are not API, so they should be in an internal package. --- .../holder => internal/renderer/text}/BulletListHolder.java | 2 +- .../text/holder => internal/renderer/text}/ListHolder.java | 2 +- .../renderer/text}/OrderedListHolder.java | 2 +- .../renderer/text/CoreTextContentNodeRenderer.java | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) rename commonmark/src/main/java/org/commonmark/{renderer/text/holder => internal/renderer/text}/BulletListHolder.java (87%) rename commonmark/src/main/java/org/commonmark/{renderer/text/holder => internal/renderer/text}/ListHolder.java (92%) rename commonmark/src/main/java/org/commonmark/{renderer/text/holder => internal/renderer/text}/OrderedListHolder.java (91%) diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java similarity index 87% rename from commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java rename to commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java index c58cb4a07..f08ccebd6 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/holder/BulletListHolder.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/text/BulletListHolder.java @@ -1,4 +1,4 @@ -package org.commonmark.renderer.text.holder; +package org.commonmark.internal.renderer.text; import org.commonmark.node.BulletList; diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java similarity index 92% rename from commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java rename to commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java index 2db1d8fbe..cb06d4a9d 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/holder/ListHolder.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/text/ListHolder.java @@ -1,4 +1,4 @@ -package org.commonmark.renderer.text.holder; +package org.commonmark.internal.renderer.text; public abstract class ListHolder { private static final String INDENT_DEFAULT = " "; diff --git a/commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java b/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java similarity index 91% rename from commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java rename to commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java index 7cdb41d98..e02ecea7c 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/holder/OrderedListHolder.java +++ b/commonmark/src/main/java/org/commonmark/internal/renderer/text/OrderedListHolder.java @@ -1,4 +1,4 @@ -package org.commonmark.renderer.text.holder; +package org.commonmark.internal.renderer.text; import org.commonmark.node.OrderedList; 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 952561322..a5f9db518 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/CoreTextContentNodeRenderer.java @@ -2,9 +2,9 @@ import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.text.holder.BulletListHolder; -import org.commonmark.renderer.text.holder.ListHolder; -import org.commonmark.renderer.text.holder.OrderedListHolder; +import org.commonmark.internal.renderer.text.BulletListHolder; +import org.commonmark.internal.renderer.text.ListHolder; +import org.commonmark.internal.renderer.text.OrderedListHolder; import java.util.Arrays; import java.util.HashSet; From 41f4be2d14f91fdcd4c598234e806b5e33700890 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 8 Sep 2017 16:56:46 +1000 Subject: [PATCH 248/815] Add CHANGELOG.md See http://keepachangelog.com/en/1.0.0/ --- CHANGELOG.md | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..fd7c4376b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,218 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +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 +- Support multiple `DelimiterProcessor` with the same delimiter char as long + as they have different length, thanks @szeiger +- Add tests for thread-safety and a section to the readme (#83) +### Changed +- Update to CommonMark spec 0.28 (#94): + - Adapt to changed emphasis parsing rule + - Allow nested parentheses in inline link destinations +### Fixed +- Fixes for text content rendering, thanks @JinneeJ: + - Support for mixed lists + - Fixed that whitespaces between text elements are removed in "stripped" mode. + For example `**text** and text` had rendered as `textand text` + - Improved rendering for auto links +- Fix `[\]` being parsed as link label +- Fix `[foo](<\>)` resulting in `\` in href +- Fix multiple of 3 rule for emphasis parsing (see commonmark/cmark#177) +- Fix text node merging when opening/closing delimiters are adjacent (#96) +- autolink: Fix linking of URLs without host, e.g. `http://.` (#99) + +## [0.9.0] - 2017-03-03 +### Added +- Support restricting which block types are parsed, see `enabledBlockTypes` + method on `Parser.Builder` (#43), thanks @marksliva, @pivotal-graham-bell and + @lalunamel. This allows you to disable parsing of e.g. headings, they will + just be parsed as paragraphs instead. +- Allow customizing the inline parser, see `inlineParserFactory` method on + `Parser.Builder` (#68), thanks @vreynolds and @lalunamel. Note that this is + experimental and currently requires using internal classes. +### Changed +- Wrap escaped HTML blocks in a `

    ` tag (#78) +- Add missing `ext-heading-anchor` to `dependencyManagement` in parent pom, + thanks @drobert + +## [0.8.0] - 2016-12-09 +### Changed +- Update to CommonMark spec 0.27 (#73): + - Treat h2..h6 as HTML blocks well + - Allow shortcut reference link before open parenthesis (if parenthesis is not + part of a valid inline link) +- `AttributeProvider.setAttributes` now has an additional `tagName` argument and + is called for all HTML tags of a block. This allows users to add attributes + for the `pre` tag of a code block in addition to `code`. Also added attribute + provider support for additional HTML tags, namely `em`, `strong`, `code` and + `br`. (#74) +### Fixed +- ext-heading-anchor: Fix IllegalArgumentException on Android (#71) + +## [0.7.1] - 2016-10-05 +### Added +- Allow to configure prefix/suffix for ID on `HeadingAnchorExtension` (#66), + thanks @paulthom12345 + +## [0.7.0] - 2016-09-23 +### Added +- Plain text content renderer (#58), thanks to @JinneeJ! + - Renders a plain text representation of a document instead of HTML, see + `TextContentRenderer` in core. + - Extensible in the same way as HTML rendering. +- Heading anchor extension (#26), thanks to @paulthom12345! + - Adds "id" attribute to heading tags (e.g. `

    Heading

    `), + useful for linking to sections of a document. + - ID generation logic can also be used by itself via the `IdGenerator` class. + - Use class `HeadingAnchorExtension` in artifact `commonmark-ext-heading-anchor` +- Ins (underline) extension (#54), thanks to @pabranch! + - Enables underlining of text by enclosing it in `++`. It's rendered as an + `ins` tag in HTML. + - Use class `InsExtension` in artifact `commonmark-ext-ins`. +### Changed +- `HtmlRenderer` and related classes moved from `org.commonmark.html` to + `org.commonmark.renderer.html` +- `HtmlRenderer.Builder` no longer takes an `AttributeProvider`, but uses a + `AttributeProviderFactory` to instantiate a new provider for each rendering. + Code needs to be changed to create a factory and then return the existing + provider from its `create` method, similar to node renderers. +- `NodeRendererFactory` was renamed to `HtmlNodeRendererFactory`, same for + related classes (there's a corresponsing interface for text content rendering) + +## [0.6.0] - 2016-07-25 +### Added +- Add coverage data to build. Currently at 97 %. +### Changed +- Update to CommonMark spec 0.26 (#55) + - empty list items can no longer interrupt a paragraph; this resolves an + ambiguity with setext headers + - ordered lists can interrupt a paragraph only when beginning with 1 + - the two-blank-lines-breaks-out-of-lists rule has been removed + - the spec for emphasis and strong emphasis has been refined to give more + intuitive results in some cases + - tabs can be used after the # in an ATX header and between the markers in a + thematic break +- Simplify and speed up brackets processing (links/images) + - Improves the nested brackets pathological case (e.g. `[[[[a]]]]` with a lot + of brackets) + - Also contributed these changes upstream to + [commonmark.js](https://talk.commonmark.org/t/ann-commonmark-0-26-cmark-0-26-0-commonmark-js-0-26-0/2165) +- Simplify merging of adjacent text nodes +- Extended `DelimiterProcessor` interface so that implementations get more + information in `getDelimiterUse` and can reject delimiters by returning `0` + from it. Also rename the methods: + - `getOpeningDelimiterChar` -> `getOpeningCharacter` + - `getClosingDelimiterChar` -> `getClosingCharacter` + - `getMinDelimiterCount` -> `getMinLength` +### Fixed +- Fix max length for link labels (999, not 1000) +- autolink: Stop URLs at more invalid characters, notably '<' and '>'. + According to RFC 3987, angle brackets are not allowed in URLs, and + other linkers don't seem to allow them either. + +## [0.5.1] - 2016-05-25 +### Fixed +- Fix `StringIndexOutOfBoundsException` on line after tab (#52) + +## [0.5.0] - 2016-04-22 +### Added +- Add YAML front matter extension for document metadata blocks (#24), thanks to + @chiwanpark +- Add information about delimiter character and length to delimiter nodes (#10), + thanks to @pcj +- Make HTML rendering for nodes extensible (#35) +- Add support for asymmetric delimiters (#17): + `DelimiterProcessor#getDelimiterChar` was split into `getOpeningDelimiterChar` + and `getClosingDelimiterChar` +### Changed +- Make `AttributeProvider` work for image and table nodes (#31) +- Update to CommonMark spec 0.25: + - Changes how partially consumed tabs are handled. +- Add Android test project to build so that we won't break Android support + (#38), thanks to @JinneeJ +- Replace `CustomHtmlRenderer` with `NodeRenderer` which also allows overriding + rendering for built-in node types (#35) +### Fixed +- Fix blank line after empty list item to terminate list +- Fix nested bullet list indented with mix of tab and spaces (#41), thanks to + @derari +- Fix package name in Javadoc, thanks to @jiakuan +- autolink: Treat more special characters as trailing delimiters to not include + `">`, `"/>` and `");` at the end of URLs +- autolink: Fix unexpected link end with unfinished delimiter pairs in URLs +- autolink: Fix Android incompatibility by not using `java.util.Objects` + +## [0.4.1] - 2016-02-11 +### Fixed +- Fix problematic regex that doesn't work on some Java versions and Android +- Fix problems with Android (usage of `java.util.Objects`, `StandardCharsets`, + ProGuard, see #30), thanks to @JinneeJ! +### Changed +- autolink extension: Update to autolink 0.3.0. This stops recognizing + "abc://foo" within "1abc://foo" as a link + +## [0.4.0] - 2016-01-18 +### Changed +Update to CommonMark spec 0.24 (#28): +- No longer allow whitespace between link text and link label +- Don't allow whitespace in link destination even with <> +- Don't use whitelist for schemes in autolinks, recognize all 2-32 length + schemes (see [spec](http://spec.commonmark.org/0.24/#scheme)) +- Allow multi-line content in setext headings + +API breaking changes (caused by changes in spec): +- Rename `Header` to `Heading` +- 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 + +## [0.3.2] - 2016-01-07 +### Fixed +- Add more bounds checks to internal Substring class (might affect extensions) + +## [0.3.1] - 2015-12-01 +### Fixed +- Fix StringIndexOutOfBoundsException with unclosed inline link (#27) + +## [0.3.0] - 2015-10-15 +### Changed +- Update to spec 0.22 (#14) +- Allow block parsers from extensions to override core behavior (#18) +- Fix compilation without `install` (#19) +- Parent pom, build and README updates + +## [0.2.0] - 2015-08-20 +### Added +- Add method `Node parseReader(java.io.Reader)` to `Parser` (#2) +- Extend Javadoc and publish online (#4) +### Fixed +- Fix StringIndexOutOfBoundsException on some inputs (#13) +- ext-gfm-tables: Implement single-column tables (#7) + +## [0.1.0] - 2015-07-22 +### Added +Initial release of commonmark-java, a port of commonmark.js with extensions +for autolinking URLs, GitHub flavored strikethrough and tables. + + +[0.9.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.8.0...commonmark-parent-0.9.0 +[0.8.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.7.1...commonmark-parent-0.8.0 +[0.7.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.7.0...commonmark-parent-0.7.1 +[0.7.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.6.0...commonmark-parent-0.7.0 +[0.6.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.5.1...commonmark-parent-0.6.0 +[0.5.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.5.0...commonmark-parent-0.5.1 +[0.5.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.4.1...commonmark-parent-0.5.0 +[0.4.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.4.0...commonmark-parent-0.4.1 +[0.4.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.3.2...commonmark-parent-0.4.0 +[0.3.2]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.3.1...commonmark-parent-0.3.2 +[0.3.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.3.0...commonmark-parent-0.3.1 +[0.3.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.2.0...commonmark-parent-0.3.0 +[0.2.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.1.0...commonmark-parent-0.2.0 +[0.1.0]: https://github.com/atlassian/commonmark-java/commits/commonmark-parent-0.1.0 From caf1f40659b6bafc18fa4ae468a8956b17d694ab Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 11 Sep 2017 11:39:33 +1000 Subject: [PATCH 249/815] CHANGELOG: Bump version --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd7c4376b..9ef3b38b0 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.10.0] - 2017-09-11 ### Added - Support multiple `DelimiterProcessor` with the same delimiter char as long as they have different length, thanks @szeiger From 248266c068b81f3ffac85ac08aa2e3e030fce16e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 13 Sep 2017 16:51:25 +1000 Subject: [PATCH 250/815] Enable pushing when releasing again This is in preparation for releasing from build servers. --- pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pom.xml b/pom.xml index bb7fde7cb..732fd487e 100644 --- a/pom.xml +++ b/pom.xml @@ -68,13 +68,6 @@ org.apache.maven.plugins maven-release-plugin - - - false - true - From ac3a98cf555a8798ac7d8e2d801e76ff0a34ee05 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 14 Sep 2017 13:47:04 +1000 Subject: [PATCH 251/815] Change date for 0.10.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ef3b38b0..a9be6fc0c 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. -## [0.10.0] - 2017-09-11 +## [0.10.0] - 2017-09-14 ### Added - Support multiple `DelimiterProcessor` with the same delimiter char as long as they have different length, thanks @szeiger From 00e4eb92339000ee89bf702d6a103e8d745bbc9c Mon Sep 17 00:00:00 2001 From: bambooagent Date: Thu, 14 Sep 2017 03:51:49 +0000 Subject: [PATCH 252/815] [maven-release-plugin] prepare release commonmark-parent-0.10.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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 37fb543a9..521b6ffd6 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index a43d52e47..222cb9293 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 2c6df1935..a43caaa3d 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 1c9a09906..86ba0474f 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index d033ed5a9..0712715f5 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 41525e516..83d703378 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.9.1-SNAPSHOT + 0.10.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index cd7dec8c4..fa22a63a2 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index b0a460c80..648f349f0 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 564f0ac92..22ecf132e 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark diff --git a/pom.xml b/pom.xml index 732fd487e..ae214e210 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.9.1-SNAPSHOT + 0.10.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -79,42 +79,42 @@ com.atlassian.commonmark commonmark - 0.9.1-SNAPSHOT + 0.10.0 com.atlassian.commonmark commonmark-ext-autolink - 0.9.1-SNAPSHOT + 0.10.0 com.atlassian.commonmark commonmark-ext-ins - 0.9.1-SNAPSHOT + 0.10.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.9.1-SNAPSHOT + 0.10.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.9.1-SNAPSHOT + 0.10.0 com.atlassian.commonmark commonmark-ext-heading-anchor - 0.9.1-SNAPSHOT + 0.10.0 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.9.1-SNAPSHOT + 0.10.0 com.atlassian.commonmark commonmark-test-util - 0.9.1-SNAPSHOT + 0.10.0 @@ -192,7 +192,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.10.0 From bd3f6d19393a6fb45f0aa66fe2ff7a3bf0d0f330 Mon Sep 17 00:00:00 2001 From: bambooagent Date: Thu, 14 Sep 2017 03:51:52 +0000 Subject: [PATCH 253/815] [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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 521b6ffd6..ddfa30fd8 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 222cb9293..789c4896c 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index a43caaa3d..c1fae362a 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 86ba0474f..772eda419 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 0712715f5..8ecfb33f9 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 83d703378..dab2e907d 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.10.0 + 0.10.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index fa22a63a2..8b5fc2e34 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 648f349f0..0604164b7 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 22ecf132e..f2a446e1a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index ae214e210..2b4ced33b 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.0 + 0.10.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -79,42 +79,42 @@ com.atlassian.commonmark commonmark - 0.10.0 + 0.10.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.10.0 + 0.10.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-ins - 0.10.0 + 0.10.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.10.0 + 0.10.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.10.0 + 0.10.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-heading-anchor - 0.10.0 + 0.10.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.10.0 + 0.10.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.10.0 + 0.10.1-SNAPSHOT @@ -192,7 +192,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.10.0 + HEAD From b5713a9399dc9d09422117f66b3e676fea3ad166 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 19 Sep 2017 17:20:06 +1000 Subject: [PATCH 254/815] Remove nexus.staging.autoRelease property Now that releasing is done through a build, it's not needed anymore. --- pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2b4ced33b..f0ffbcbdb 100644 --- a/pom.xml +++ b/pom.xml @@ -34,8 +34,6 @@ UTF-8 ${project.basedir}/../commonmark/target/apidocs/ - - false From 17860a547e568c28d1b7fc2e3d6945b4525fcf22 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 19 Sep 2017 17:23:18 +1000 Subject: [PATCH 255/815] Bump versions in docs --- CHANGELOG.md | 1 + README.md | 2 +- commonmark-android-test/README.md | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9be6fc0c..f4d79ec17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -202,6 +202,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.10.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.9.0...commonmark-parent-0.10.0 [0.9.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.8.0...commonmark-parent-0.9.0 [0.8.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.7.1...commonmark-parent-0.8.0 [0.7.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.7.0...commonmark-parent-0.7.1 diff --git a/README.md b/README.md index b5d737e7c..b3846716b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.9.0 + 0.10.0 ``` diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 865396896..6d498af5a 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.9.0 +version.maven=0.10.0 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.7.0 # Version number of commonmark and extensions in project. -version.snapshot=0.9.0-SNAPSHOT +version.snapshot=0.10.0-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.7.0 ``` From d04616ccde1af6bdeb71fa653bb1db13c5365ee1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 19 Sep 2017 17:23:54 +1000 Subject: [PATCH 256/815] Bump copyright year --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3846716b..d18b4485f 100644 --- a/README.md +++ b/README.md @@ -326,7 +326,7 @@ an issue and explaining the intended change. License ------- -Copyright (c) 2015-2016 Atlassian and others. +Copyright (c) 2015-2017 Atlassian and others. BSD (2-clause) licensed, see LICENSE.txt file. From 176f9187b378e1877138dc3f099d54285e3374c2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 20 Sep 2017 13:40:52 +1000 Subject: [PATCH 257/815] Switch to codecov for code coverage --- .travis.yml | 17 +++++++++-------- commonmark-integration-test/pom.xml | 27 --------------------------- pom.xml | 5 ----- 3 files changed, 9 insertions(+), 40 deletions(-) diff --git a/.travis.yml b/.travis.yml index 73fa9f08b..a9f481fe0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,11 +15,12 @@ android: - extra-android-m2repository - sys-img-armeabi-v7a-android-16 script: - - 'if [[ $TEST = java ]]; then mvn test -Dsurefire.useFile=false; fi' - - 'if [[ $TEST = android ]]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' -deploy: - provider: script - script: mvn clean test coveralls:report -Pcoverage - on: - jdk: oraclejdk8 - condition: "$TEST = java" + - 'if [ $TEST = java ]; then mvn test -Dsurefire.useFile=false; fi' + - 'if [ $TEST = android ]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' + +after_success: | + if [ $TRAVIS_JDK_VERSION = oraclejdk8 ] && [ $TEST = java ]; then + # Calculate test coverage + mvn clean test jacoco:report -Pcoverage + bash <(curl -s https://codecov.io/bash) + fi diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 8b5fc2e34..ed1026538 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -61,33 +61,6 @@
    - - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - ${project.parent.reporting.outputDirectory}/jacoco - - - - report-aggregate - - report-aggregate - - test - - - - - - - - diff --git a/pom.xml b/pom.xml index f0ffbcbdb..c5d324779 100644 --- a/pom.xml +++ b/pom.xml @@ -159,11 +159,6 @@ - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - From 89aa8f41c8f4b85862cb1a8b513a42859e643f0f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 20 Sep 2017 14:25:36 +1000 Subject: [PATCH 258/815] Add codecov badge, move badges up --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d18b4485f..221319aa8 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,13 @@ commonmark-java Java library for parsing and rendering [Markdown] text according to the [CommonMark] specification (and some extensions). +[![Maven Central status](https://img.shields.io/maven-central/v/com.atlassian.commonmark/commonmark.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.atlassian.commonmark%22) +[![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) +[![codecov](https://codecov.io/gh/atlassian/commonmark-java/branch/master/graph/badge.svg)](https://codecov.io/gh/atlassian/commonmark-java) + +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 @@ -39,10 +46,6 @@ file if you're wondering which version of the spec is currently implemented. Also check out the [CommonMark dingus] for getting familiar with the syntax or trying out edge cases. -[![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) -[![Coverage status](https://coveralls.io/repos/github/atlassian/commonmark-java/badge.svg?branch=master)](https://coveralls.io/github/atlassian/commonmark-java?branch=master) -[![Maven Central status](https://img.shields.io/maven-central/v/com.atlassian.commonmark/commonmark.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.atlassian.commonmark%22) - Usage ----- From f5ecc98fd20cb10e59b28846489daefd49e246d1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 20 Sep 2017 14:44:11 +1000 Subject: [PATCH 259/815] Allow Android build to fail It's flakey because it times out waiting for the emulator to start, rerunning it fixes it most of the times. --- .travis.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index a9f481fe0..380e6512b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: android + matrix: include: - jdk: oraclejdk8 @@ -8,12 +9,16 @@ matrix: - jdk: openjdk7 env: TEST=android dist: precise -android: - components: - - android-16 - - build-tools-21.1.1 - - extra-android-m2repository - - sys-img-armeabi-v7a-android-16 + android: + components: + - android-16 + - build-tools-21.1.1 + - extra-android-m2repository + - sys-img-armeabi-v7a-android-16 + + allow_failures: + - env: TEST=android + script: - 'if [ $TEST = java ]; then mvn test -Dsurefire.useFile=false; fi' - 'if [ $TEST = android ]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' From eaad83263bffe09dd946f558119fb1a1875cb26f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Sep 2017 10:05:43 +1000 Subject: [PATCH 260/815] Improve coverage for YAML front matter extension Turns out there's some unused code as well. --- .../internal/YamlFrontMatterBlockParser.java | 75 ++++++++----------- .../ext/front/matter/YamlFrontMatterTest.java | 46 +++++++++++- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java b/commonmark-ext-yaml-front-matter/src/main/java/org/commonmark/ext/front/matter/internal/YamlFrontMatterBlockParser.java index 4de4ae12f..5612d9ffd 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 @@ -19,14 +19,12 @@ public class YamlFrontMatterBlockParser extends AbstractBlockParser { private static final Pattern REGEX_BEGIN = Pattern.compile("^-{3}(\\s.*)?"); private static final Pattern REGEX_END = Pattern.compile("^(-{3}|\\.{3})(\\s.*)?"); - private boolean inYAMLBlock; private boolean inLiteral; private String currentKey; private List currentValues; private YamlFrontMatterBlock block; public YamlFrontMatterBlockParser() { - inYAMLBlock = true; inLiteral = false; currentKey = null; currentValues = new ArrayList<>(); @@ -46,55 +44,48 @@ public void addLine(CharSequence line) { public BlockContinue tryContinue(ParserState parserState) { final CharSequence line = parserState.getLine(); - if (inYAMLBlock) { - if (REGEX_END.matcher(line).matches()) { - if (currentKey != null) { - block.appendChild(new YamlFrontMatterNode(currentKey, currentValues)); - } - return BlockContinue.finished(); + if (REGEX_END.matcher(line).matches()) { + if (currentKey != null) { + block.appendChild(new YamlFrontMatterNode(currentKey, currentValues)); } + return BlockContinue.finished(); + } - Matcher matcher = REGEX_METADATA.matcher(line); - if (matcher.matches()) { - if (currentKey != null) { - block.appendChild(new YamlFrontMatterNode(currentKey, currentValues)); - } + Matcher matcher = REGEX_METADATA.matcher(line); + if (matcher.matches()) { + if (currentKey != null) { + block.appendChild(new YamlFrontMatterNode(currentKey, currentValues)); + } - inLiteral = false; - currentKey = matcher.group(1); - currentValues = new ArrayList<>(); - if ("|".equals(matcher.group(2))) { - inLiteral = true; - } else if (!"".equals(matcher.group(2))) { - currentValues.add(matcher.group(2)); - } + inLiteral = false; + currentKey = matcher.group(1); + currentValues = new ArrayList<>(); + if ("|".equals(matcher.group(2))) { + inLiteral = true; + } else if (!"".equals(matcher.group(2))) { + currentValues.add(matcher.group(2)); + } - return BlockContinue.atIndex(parserState.getIndex()); - } else { - if (inLiteral) { - matcher = REGEX_METADATA_LITERAL.matcher(line); - if (matcher.matches()) { - if (currentValues.size() == 1) { - currentValues.set(0, currentValues.get(0) + "\n" + matcher.group(1).trim()); - } else { - currentValues.add(matcher.group(1).trim()); - } - } - } else { - matcher = REGEX_METADATA_LIST.matcher(line); - if (matcher.matches()) { - currentValues.add(matcher.group(1)); + return BlockContinue.atIndex(parserState.getIndex()); + } else { + if (inLiteral) { + matcher = REGEX_METADATA_LITERAL.matcher(line); + if (matcher.matches()) { + if (currentValues.size() == 1) { + currentValues.set(0, currentValues.get(0) + "\n" + matcher.group(1).trim()); + } else { + currentValues.add(matcher.group(1).trim()); } } - - return BlockContinue.atIndex(parserState.getIndex()); + } else { + matcher = REGEX_METADATA_LIST.matcher(line); + if (matcher.matches()) { + currentValues.add(matcher.group(1)); + } } - } else if (REGEX_BEGIN.matcher(line).matches()) { - inYAMLBlock = true; + return BlockContinue.atIndex(parserState.getIndex()); } - - return BlockContinue.none(); } @Override 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 84e20b774..d03d33a69 100644 --- a/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java +++ b/commonmark-ext-yaml-front-matter/src/test/java/org/commonmark/ext/front/matter/YamlFrontMatterTest.java @@ -1,9 +1,10 @@ package org.commonmark.ext.front.matter; import org.commonmark.Extension; -import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.node.CustomNode; import org.commonmark.node.Node; import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.test.RenderingTestCase; import org.junit.Test; @@ -216,8 +217,51 @@ public void nonMatchedStartTag() { assertRendering(input, rendered); } + @Test + public void visitorIgnoresOtherCustomNodes() { + final String input = "---" + + "\nhello: world" + + "\n---" + + "\n"; + + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); + Node document = PARSER.parse(input); + document.appendChild(new TestNode()); + document.accept(visitor); + + Map> data = visitor.getData(); + assertEquals(1, data.size()); + assertTrue(data.containsKey("hello")); + assertEquals(Collections.singletonList("world"), data.get("hello")); + } + + @Test + public void nodesCanBeModified() { + final String input = "---" + + "\nhello: world" + + "\n---" + + "\n"; + + Node document = PARSER.parse(input); + YamlFrontMatterNode node = (YamlFrontMatterNode) document.getFirstChild().getFirstChild(); + node.setKey("see"); + node.setValues(Collections.singletonList("you")); + + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); + document.accept(visitor); + + Map> data = visitor.getData(); + assertEquals(1, data.size()); + assertTrue(data.containsKey("see")); + assertEquals(Collections.singletonList("you"), data.get("see")); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); } + + // Custom node for tests + private static class TestNode extends CustomNode { + } } From 790330ab59679cabcb0a46886989728675e1f127 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Sep 2017 10:44:06 +1000 Subject: [PATCH 261/815] Cover some more edge cases --- .../ext/front/matter/YamlFrontMatterTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) 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 d03d33a69..462429d29 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 @@ -180,6 +180,24 @@ public void complexValues() { assertRendering(input, rendered); } + @Test + public void empty() { + final String input = "---\n" + + "---\n" + + "test"; + final String rendered = "

    test

    \n"; + + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertTrue(data.isEmpty()); + + assertRendering(input, rendered); + } + @Test public void yamlInParagraph() { final String input = "# hello\n" + @@ -200,6 +218,25 @@ public void yamlInParagraph() { assertRendering(input, rendered); } + @Test + public void yamlOnSecondLine() { + final String input = "hello\n" + + "\n---" + + "\nhello: world" + + "\n---"; + final String rendered = "

    hello

    \n
    \n

    hello: world

    \n"; + + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertTrue(data.isEmpty()); + + assertRendering(input, rendered); + } + @Test public void nonMatchedStartTag() { final String input = "----\n" + @@ -217,6 +254,24 @@ public void nonMatchedStartTag() { assertRendering(input, rendered); } + @Test + public void inList() { + final String input = "* ---\n" + + " ---\n" + + "test"; + final String rendered = "
      \n
    • \n
      \n
      \n
    • \n
    \n

    test

    \n"; + + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map> data = visitor.getData(); + + assertTrue(data.isEmpty()); + + assertRendering(input, rendered); + } + @Test public void visitorIgnoresOtherCustomNodes() { final String input = "---" + From cde5cb0aba74a7d6800210b59095793f7f195ce1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Sep 2017 10:49:30 +1000 Subject: [PATCH 262/815] Make codecov comments less verbose --- .codecov.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..3f9d1b9aa --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,2 @@ +comment: + layout: "diff, flags, files" From 5ea7a7afd21e3d3d6ad8207013df6f6994639c29 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 21 Sep 2017 10:57:12 +1000 Subject: [PATCH 263/815] Run report-aggregate so that integration tests add to coverage of core --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 380e6512b..42dba458e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,6 @@ script: after_success: | if [ $TRAVIS_JDK_VERSION = oraclejdk8 ] && [ $TEST = java ]; then # Calculate test coverage - mvn clean test jacoco:report -Pcoverage + mvn clean test jacoco:report-aggregate -Pcoverage bash <(curl -s https://codecov.io/bash) fi From f322ad6dddb6dc8a2aa87289de25b0e2844d0857 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 11 Oct 2017 21:05:32 -0700 Subject: [PATCH 264/815] Add issue template --- .github/issue_template.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/issue_template.md diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 000000000..24a01da04 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,7 @@ +Steps to reproduce the problem (provide example input): + + +Expected behavior: + + +Actual behavior: From 1c94b1a6115dfc1035574720ba6a2ed43bb65bfb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 19 Oct 2017 14:52:12 -0700 Subject: [PATCH 265/815] README: Add badge for javadoc.io --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 221319aa8..aac78a543 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Java library for parsing and rendering [Markdown] text according to the [CommonMark] specification (and some extensions). [![Maven Central status](https://img.shields.io/maven-central/v/com.atlassian.commonmark/commonmark.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.atlassian.commonmark%22) +[![javadoc](https://www.javadoc.io/badge/com.atlassian.commonmark/commonmark.svg?color=blue)](https://www.javadoc.io/doc/com.atlassian.commonmark/commonmark) [![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) [![codecov](https://codecov.io/gh/atlassian/commonmark-java/branch/master/graph/badge.svg)](https://codecov.io/gh/atlassian/commonmark-java) @@ -206,7 +207,7 @@ report an issue. ### API documentation Javadocs are available online on -[javadoc.io](http://www.javadoc.io/doc/com.atlassian.commonmark/commonmark). +[javadoc.io](https://www.javadoc.io/doc/com.atlassian.commonmark/commonmark). Extensions From e800d276275b90c1088790e12c103003808424b2 Mon Sep 17 00:00:00 2001 From: Antek Jaworski Date: Fri, 8 Dec 2017 20:40:50 +0100 Subject: [PATCH 266/815] Issue 80: TextContent extension for GFM tables --- ...xtension.java => HtmlTablesExtension.java} | 10 +- .../tables/TextContentTablesExtension.java | 46 +++++ ...nderer.java => HtmlTableNodeRenderer.java} | 4 +- .../TextContentTableNodeRenderer.java | 104 ++++++++++ .../{TablesTest.java => HtmlTablesTest.java} | 4 +- .../ext/gfm/tables/TextContentTablesTest.java | 179 ++++++++++++++++++ .../integration/SpecIntegrationTest.java | 4 +- .../commonmark/test/RenderingTestCase.java | 8 +- 8 files changed, 344 insertions(+), 15 deletions(-) rename commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/{TablesExtension.java => HtmlTablesExtension.java} (80%) create mode 100644 commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TextContentTablesExtension.java rename commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/{TableNodeRenderer.java => HtmlTableNodeRenderer.java} (96%) create mode 100644 commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TextContentTableNodeRenderer.java rename commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/{TablesTest.java => HtmlTablesTest.java} (99%) create mode 100644 commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TextContentTablesTest.java 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/HtmlTablesExtension.java similarity index 80% rename from commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java rename to commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/HtmlTablesExtension.java index 5f562fc63..6073b5282 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/HtmlTablesExtension.java @@ -2,7 +2,7 @@ import org.commonmark.Extension; import org.commonmark.ext.gfm.tables.internal.TableBlockParser; -import org.commonmark.ext.gfm.tables.internal.TableNodeRenderer; +import org.commonmark.ext.gfm.tables.internal.HtmlTableNodeRenderer; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlNodeRendererFactory; @@ -20,13 +20,13 @@ * The parsed tables are turned into {@link TableBlock} blocks. *

    */ -public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +public class HtmlTablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { - private TablesExtension() { + private HtmlTablesExtension() { } public static Extension create() { - return new TablesExtension(); + return new HtmlTablesExtension(); } @Override @@ -39,7 +39,7 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { @Override public NodeRenderer create(HtmlNodeRendererContext context) { - return new TableNodeRenderer(context); + return new HtmlTableNodeRenderer(context); } }); } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TextContentTablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TextContentTablesExtension.java new file mode 100644 index 000000000..9ca89ae93 --- /dev/null +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TextContentTablesExtension.java @@ -0,0 +1,46 @@ +package org.commonmark.ext.gfm.tables; + +import org.commonmark.Extension; +import org.commonmark.ext.gfm.tables.internal.TableBlockParser; +import org.commonmark.ext.gfm.tables.internal.TextContentTableNodeRenderer; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentNodeRendererFactory; +import org.commonmark.renderer.text.TextContentRenderer; + +/** + * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). + *

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

    + *

    + * The parsed tables are turned into {@link TableBlock} blocks. + *

    + */ +public class TextContentTablesExtension implements Parser.ParserExtension, TextContentRenderer.TextContentRendererExtension { + + private TextContentTablesExtension() { + } + + public static Extension create() { + return new TextContentTablesExtension(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customBlockParserFactory(new TableBlockParser.Factory()); + } + + @Override + public void extend(TextContentRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { + @Override + public NodeRenderer create(TextContentNodeRendererContext context) { + return new TextContentTableNodeRenderer(context); + } + }); + } +} 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/HtmlTableNodeRenderer.java similarity index 96% rename from commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java rename to commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/HtmlTableNodeRenderer.java index 1bff26e19..2be284d66 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/HtmlTableNodeRenderer.java @@ -8,12 +8,12 @@ import java.util.*; -public class TableNodeRenderer implements NodeRenderer { +public class HtmlTableNodeRenderer implements NodeRenderer { private final HtmlWriter htmlWriter; private final HtmlNodeRendererContext context; - public TableNodeRenderer(HtmlNodeRendererContext context) { + public HtmlTableNodeRenderer(HtmlNodeRendererContext context) { this.htmlWriter = context.getWriter(); this.context = context; } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TextContentTableNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TextContentTableNodeRenderer.java new file mode 100644 index 000000000..f3088867d --- /dev/null +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TextContentTableNodeRenderer.java @@ -0,0 +1,104 @@ +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.node.Node; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentWriter; + +/** + * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text. + */ +public class TextContentTableNodeRenderer implements NodeRenderer { + + private final TextContentWriter textContentWriter; + private final TextContentNodeRendererContext context; + + public TextContentTableNodeRenderer(TextContentNodeRendererContext context) { + this.textContentWriter = context.getWriter(); + this.context = context; + } + + @Override + public Set> getNodeTypes() { + return new HashSet<>(Arrays.asList( + TableBlock.class, + TableHead.class, + TableBody.class, + TableRow.class, + TableCell.class + )); + } + + @Override + public void render(Node node) { + // We don't render the table header (node instanceof TableHead) and its children for the text content. + + if (node instanceof TableBlock) { + renderBlock((TableBlock) node); + } else if (node instanceof TableHead) { + renderHead((TableHead) node); + } else if (node instanceof TableBody) { + renderBody((TableBody) node); + } else if (node instanceof TableRow) { + renderRow((TableRow) node); + } else if (node instanceof TableCell) { + renderCell((TableCell) node); + } + } + + private void renderBlock(TableBlock tableBlock) { + renderChildren(tableBlock); + if (tableBlock.getNext() != null) { + textContentWriter.write("\n"); + } + } + + private void renderHead(TableHead tableHead) { + renderChildren(tableHead); + } + + private void renderBody(TableBody tableBody) { + renderChildren(tableBody); + } + + private void renderRow(TableRow tableRow) { + textContentWriter.line(); + renderChildren(tableRow); + textContentWriter.line(); + } + + private void renderCell(TableCell tableCell) { + renderChildren(tableCell); + textContentWriter.colon(); + textContentWriter.whitespace(); + } + + private void renderLastCell(TableCell tableCell) { + renderChildren(tableCell); + } + + 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); + } + + node = next; + } + } +} 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/HtmlTablesTest.java similarity index 99% rename from commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java rename to commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/HtmlTablesTest.java index 9925959f4..eb2ed14fc 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/HtmlTablesTest.java @@ -17,9 +17,9 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -public class TablesTest extends RenderingTestCase { +public class HtmlTablesTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set EXTENSIONS = Collections.singleton(HtmlTablesExtension.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/TextContentTablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TextContentTablesTest.java new file mode 100644 index 000000000..c3c239ce1 --- /dev/null +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TextContentTablesTest.java @@ -0,0 +1,179 @@ +package org.commonmark.ext.gfm.tables; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.commonmark.Extension; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.AttributeProvider; +import org.commonmark.renderer.html.AttributeProviderContext; +import org.commonmark.renderer.html.AttributeProviderFactory; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.text.TextContentNodeRendererFactory; +import org.commonmark.renderer.text.TextContentRenderer; +import org.commonmark.test.RenderingTestCase; +import org.junit.Test; + +public class TextContentTablesTest extends RenderingTestCase { + + private static final Set EXTENSIONS = Collections.singleton(TextContentTablesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void mustHaveHeaderAndSeparator() { + assertRendering("Abc|Def", "Abc|Def"); + assertRendering("Abc | Def", "Abc | Def"); + } + + @Test + public void separatorMustBeThreeOrMore() { + assertRendering("Abc|Def\n-|-", "Abc|Def\n-|-"); + assertRendering("Abc|Def\n--|--", "Abc|Def\n--|--"); + } + + @Test + public void separatorCanNotHaveLeadingSpaceThenPipe() { + assertRendering("Abc|Def\n |---|---", "Abc|Def\n|---|---"); + } + + @Test + public void headerMustBeOneLine() { + assertRendering("No\nAbc|Def\n---|---", "No\nAbc|Def\n---|---"); + } + + @Test + public void oneHeadNoBody() { + assertRendering("Abc|Def\n---|---", "Abc: Def\n"); + } + + @Test + public void oneColumnOneHeadNoBody() { + String expected = "Abc\n"; + assertRendering("|Abc\n|---\n", expected); + assertRendering("|Abc|\n|---|\n", expected); + assertRendering("Abc|\n---|\n", expected); + + // Pipe required on separator + assertRendering("|Abc\n---\n", "|Abc"); + // Pipe required on head + assertRendering("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); + + // Pipe required on separator + assertRendering("|Abc\n---\n|1", "|Abc\n|1"); + + // Pipe required on body + assertRendering("|Abc\n|---\n1\n", "Abc\n\n1"); + } + + @Test + public void oneHeadOneBody() { + assertRendering("Abc|Def\n---|---\n1|2", "Abc: Def\n1: 2\n"); + } + + @Test + public void separatorMustNotHaveLessPartsThanHead() { + assertRendering("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"); + } + + @Test + public void paddingWithCodeBlockIndentation() { + assertRendering("Abc|Def\n---|---\n 1|2", "Abc: Def\n1: 2\n"); + } + + @Test + public void pipesOnOutside() { + assertRendering("|Abc|Def|\n|---|---|\n|1|2|", "Abc: Def\n1: 2\n"); + } + + @Test + public void inlineElements() { + assertRendering("*Abc*|Def\n---|---\n1|2", "Abc: Def\n1: 2\n"); + } + + @Test + public void escapedPipe() { + assertRendering("Abc|Def\n---|---\n1\\|2|20", "Abc: Def\n1|2: 20\n"); + } + + @Test + public void escapedBackslash() { + assertRendering("Abc|Def\n---|---\n1\\\\|2", "Abc: Def\n1\\: 2\n"); + } + + @Test + public void alignLeft() { + assertRendering("Abc|Def\n:---|---\n1|2", "Abc: Def\n1: 2\n"); + } + + @Test + public void alignRight() { + assertRendering("Abc|Def\n---:|---\n1|2", "Abc: Def\n1: 2\n"); + } + + @Test + public void alignCenter() { + assertRendering("Abc|Def\n:---:|---\n1|2", "Abc: Def\n1: 2\n"); + } + + @Test + public void alignCenterSecond() { + assertRendering("Abc|Def\n---|:---:\n1|2", "Abc: Def\n1: 2\n"); + } + + @Test + public void alignLeftWithSpaces() { + assertRendering("Abc|Def\n :--- |---\n1|2", "Abc: Def\n1: 2\n"); + } + + @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---|--- :"); + } + + @Test + public void bodyCanNotHaveMoreColumnsThanHead() { + assertRendering("Abc|Def\n---|---\n1|2|3", "Abc: Def\n1: 2\n"); + } + + @Test + public void bodyWithFewerColumnsThanHeadResultsInEmptyCells() { + assertRendering("Abc|Def|Ghi\n---|---|---\n1|2", "Abc: Def: Ghi\n1: 2: \n"); + } + + @Test + public void insideBlockQuote() { + assertRendering("> Abc|Def\n> ---|---\n> 1|2", "«\nAbc: Def\n1: 2\n»"); + } + + @Test + public void tableEndWithoutEmptyLine() { + assertRendering("Abc|Def\n---|---\n1|2\ntable, you are over", "Abc: Def\n1: 2\n\ntable, you are over"); + } + + @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 af4edf23d..c3dd08d02 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.ext.autolink.AutolinkExtension; import org.commonmark.ext.ins.InsExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; -import org.commonmark.ext.gfm.tables.TablesExtension; +import org.commonmark.ext.gfm.tables.HtmlTablesExtension; import org.commonmark.ext.front.matter.YamlFrontMatterExtension; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; @@ -23,7 +23,7 @@ public class SpecIntegrationTest extends SpecTestCase { AutolinkExtension.create(), InsExtension.create(), StrikethroughExtension.create(), - TablesExtension.create(), + HtmlTablesExtension.create(), YamlFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. diff --git a/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java index 1ee19dbb2..b182cc735 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java @@ -6,12 +6,12 @@ public abstract class RenderingTestCase { protected abstract String render(String source); - protected void assertRendering(String source, String expectedHtml) { - String html = render(source); + protected void assertRendering(String source, String expectedResult) { + String renderedContent = render(source); // include source for better assertion errors - String expected = showTabs(expectedHtml + "\n\n" + source); - String actual = showTabs(html + "\n\n" + source); + String expected = showTabs(expectedResult + "\n\n" + source); + String actual = showTabs(renderedContent + "\n\n" + source); assertEquals(expected, actual); } From 50c3895869d43a5d3b0b9411a870f4ee982ac972 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 22 Dec 2017 14:42:13 +1100 Subject: [PATCH 267/815] Bump parent pom and maven-compiler-plugin --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c5d324779..092676709 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.atlassian.pom central-pom - 3.0.98 + 5.0.13 pom @@ -42,7 +42,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.1 + 3.7.0 7 7 From 65ca720c9c5ab4732477e0cb0462a0ded859dc4f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 22 Dec 2017 15:44:04 +1100 Subject: [PATCH 268/815] Move classes commonmark-test-util to org.commonmark.testutil package This is in preparation for Java 9, so that we have one common prefix for this module. --- .../commonmark/android/test/AndroidSupportTest.java | 2 +- .../java/org/commonmark/ext/autolink/AutolinkTest.java | 4 ++-- .../ext/gfm/strikethrough/StrikethroughTest.java | 6 +++--- .../java/org/commonmark/ext/gfm/tables/TablesTest.java | 8 ++++---- .../commonmark/ext/heading/anchor/HeadingAnchorTest.java | 4 ++-- .../src/test/java/org/commonmark/ext/ins/InsTest.java | 4 ++-- .../commonmark/ext/front/matter/YamlFrontMatterTest.java | 2 +- .../org/commonmark/integration/BoundsIntegrationTest.java | 4 ++-- .../java/org/commonmark/integration/PegDownBenchmark.java | 2 +- .../org/commonmark/integration/SpecIntegrationTest.java | 4 ++-- .../commonmark/{test => testutil}/RenderingTestCase.java | 2 +- .../org/commonmark/{test => testutil}/SpecTestCase.java | 6 +++--- .../java/org/commonmark/{test => testutil}/Strings.java | 2 +- .../org/commonmark/{ => testutil}/spec/SpecExample.java | 2 +- .../org/commonmark/{ => testutil}/spec/SpecReader.java | 2 +- .../java/org/commonmark/test/CoreRenderingTestCase.java | 3 ++- .../java/org/commonmark/test/DelimiterProcessorTest.java | 1 + .../test/java/org/commonmark/test/HtmlRendererTest.java | 2 +- .../src/test/java/org/commonmark/test/ParserTest.java | 2 +- .../test/java/org/commonmark/test/PathologicalTest.java | 2 +- .../src/test/java/org/commonmark/test/SpecBenchmark.java | 2 +- .../src/test/java/org/commonmark/test/SpecCoreTest.java | 3 ++- .../test/java/org/commonmark/test/SpecialInputTest.java | 1 + 23 files changed, 37 insertions(+), 33 deletions(-) rename commonmark-test-util/src/main/java/org/commonmark/{test => testutil}/RenderingTestCase.java (95%) rename commonmark-test-util/src/main/java/org/commonmark/{test => testutil}/SpecTestCase.java (86%) rename commonmark-test-util/src/main/java/org/commonmark/{test => testutil}/Strings.java (88%) rename commonmark-test-util/src/main/java/org/commonmark/{ => testutil}/spec/SpecExample.java (94%) rename commonmark-test-util/src/main/java/org/commonmark/{ => testutil}/spec/SpecReader.java (99%) diff --git a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java index dea699aee..ecb9fcd86 100644 --- a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java +++ b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java @@ -10,7 +10,7 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.spec.SpecReader; +import org.commonmark.testutil.spec.SpecReader; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; 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 43346c079..ae586b6f0 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -1,9 +1,9 @@ package org.commonmark.ext.autolink; import org.commonmark.Extension; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; -import org.commonmark.test.RenderingTestCase; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import java.util.Collections; 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 3ad8ee915..85ae72eb2 100644 --- a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java @@ -1,11 +1,11 @@ package org.commonmark.ext.gfm.strikethrough; import org.commonmark.Extension; -import org.commonmark.renderer.text.TextContentRenderer; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; -import org.commonmark.test.RenderingTestCase; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.text.TextContentRenderer; +import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import java.util.Collections; 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 9925959f4..3d6434fdd 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,13 +1,13 @@ package org.commonmark.ext.gfm.tables; import org.commonmark.Extension; -import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProviderContext; import org.commonmark.renderer.html.AttributeProviderFactory; -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.commonmark.test.RenderingTestCase; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import java.util.Collections; 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 3b797a1ab..821aa9a84 100644 --- a/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java +++ b/commonmark-ext-heading-anchor/src/test/java/org/commonmark/ext/heading/anchor/HeadingAnchorTest.java @@ -1,9 +1,9 @@ package org.commonmark.ext.heading.anchor; import org.commonmark.Extension; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; -import org.commonmark.test.RenderingTestCase; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import java.util.Collections; 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 3719078fc..2b97431c3 100644 --- a/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java +++ b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java @@ -1,10 +1,10 @@ package org.commonmark.ext.ins; import org.commonmark.Extension; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.Node; import org.commonmark.parser.Parser; -import org.commonmark.test.RenderingTestCase; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import java.util.Collections; 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 462429d29..505c70e6a 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 @@ -5,7 +5,7 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.test.RenderingTestCase; +import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import java.util.Collections; 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 e0c382be5..981da1540 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 @@ -2,8 +2,8 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; -import org.commonmark.spec.SpecExample; -import org.commonmark.spec.SpecReader; +import org.commonmark.testutil.spec.SpecExample; +import org.commonmark.testutil.spec.SpecReader; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; 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 7348a32f3..2056868ec 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java @@ -1,6 +1,6 @@ package org.commonmark.integration; -import org.commonmark.spec.SpecReader; +import org.commonmark.testutil.spec.SpecReader; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; 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 af4edf23d..77dd26608 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 @@ -8,8 +8,8 @@ import org.commonmark.ext.front.matter.YamlFrontMatterExtension; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; -import org.commonmark.spec.SpecExample; -import org.commonmark.test.SpecTestCase; +import org.commonmark.testutil.spec.SpecExample; +import org.commonmark.testutil.SpecTestCase; import org.junit.Test; import java.util.*; diff --git a/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java similarity index 95% rename from commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java rename to commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java index 1ee19dbb2..e9e65f330 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/RenderingTestCase.java @@ -1,4 +1,4 @@ -package org.commonmark.test; +package org.commonmark.testutil; import static org.junit.Assert.assertEquals; diff --git a/commonmark-test-util/src/main/java/org/commonmark/test/SpecTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java similarity index 86% rename from commonmark-test-util/src/main/java/org/commonmark/test/SpecTestCase.java rename to commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java index c81300cb4..a87d4dad4 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/test/SpecTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java @@ -1,7 +1,7 @@ -package org.commonmark.test; +package org.commonmark.testutil; -import org.commonmark.spec.SpecExample; -import org.commonmark.spec.SpecReader; +import org.commonmark.testutil.spec.SpecExample; +import org.commonmark.testutil.spec.SpecReader; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; diff --git a/commonmark-test-util/src/main/java/org/commonmark/test/Strings.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/Strings.java similarity index 88% rename from commonmark-test-util/src/main/java/org/commonmark/test/Strings.java rename to commonmark-test-util/src/main/java/org/commonmark/testutil/Strings.java index 79c91b592..ed709ed81 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/test/Strings.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/Strings.java @@ -1,4 +1,4 @@ -package org.commonmark.test; +package org.commonmark.testutil; public class Strings { diff --git a/commonmark-test-util/src/main/java/org/commonmark/spec/SpecExample.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecExample.java similarity index 94% rename from commonmark-test-util/src/main/java/org/commonmark/spec/SpecExample.java rename to commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecExample.java index 17ed90162..decade455 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/spec/SpecExample.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecExample.java @@ -1,4 +1,4 @@ -package org.commonmark.spec; +package org.commonmark.testutil.spec; public class SpecExample { diff --git a/commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecReader.java similarity index 99% rename from commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java rename to commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecReader.java index 77a2ead4e..4db1aa491 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/spec/SpecReader.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecReader.java @@ -1,4 +1,4 @@ -package org.commonmark.spec; +package org.commonmark.testutil.spec; import java.io.BufferedReader; import java.io.IOException; diff --git a/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java b/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java index 078c0ba12..38f319e1c 100644 --- a/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java +++ b/commonmark/src/test/java/org/commonmark/test/CoreRenderingTestCase.java @@ -1,7 +1,8 @@ package org.commonmark.test; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; public class CoreRenderingTestCase extends RenderingTestCase { diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index c460826db..e7348f5dd 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -10,6 +10,7 @@ import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import java.util.Collections; diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 016fbee94..d393ee12a 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -7,7 +7,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; -import org.commonmark.spec.SpecReader; +import org.commonmark.testutil.spec.SpecReader; import org.junit.Test; import java.util.*; diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index f54172c4e..b5f487312 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -7,7 +7,7 @@ import org.commonmark.parser.Parser; import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.spec.SpecReader; +import org.commonmark.testutil.spec.SpecReader; import org.junit.Test; import java.io.IOException; diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 8b2125e8f..bd28e578e 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -10,7 +10,7 @@ import java.util.concurrent.TimeUnit; -import static org.commonmark.test.Strings.repeat; +import static org.commonmark.testutil.Strings.repeat; /** * Pathological input cases (from commonmark.js). diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index 33f072ed1..9b10b0f34 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -2,7 +2,7 @@ import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; -import org.commonmark.spec.SpecReader; +import org.commonmark.testutil.spec.SpecReader; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index 521d4e9b6..2a5ffe510 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -5,7 +5,8 @@ import org.commonmark.node.Node; import org.commonmark.node.Text; import org.commonmark.parser.Parser; -import org.commonmark.spec.SpecExample; +import org.commonmark.testutil.SpecTestCase; +import org.commonmark.testutil.spec.SpecExample; import org.junit.Test; import static org.junit.Assert.fail; diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 5d7ae2919..930ecb023 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -1,5 +1,6 @@ package org.commonmark.test; +import org.commonmark.testutil.Strings; import org.junit.Test; public class SpecialInputTest extends CoreRenderingTestCase { From 94a8de28cb801e7b796c24cde2602fdb3dc6b282 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 22 Dec 2017 15:55:02 +1100 Subject: [PATCH 269/815] Add Automatic-Module-Name for Java 9 usage See #114. --- CHANGELOG.md | 6 ++++++ commonmark-ext-autolink/pom.xml | 16 ++++++++++++++++ commonmark-ext-gfm-strikethrough/pom.xml | 16 ++++++++++++++++ commonmark-ext-gfm-tables/pom.xml | 16 ++++++++++++++++ commonmark-ext-heading-anchor/pom.xml | 16 ++++++++++++++++ commonmark-ext-ins/pom.xml | 16 ++++++++++++++++ commonmark-ext-yaml-front-matter/pom.xml | 16 ++++++++++++++++ commonmark-test-util/pom.xml | 16 ++++++++++++++++ commonmark/pom.xml | 16 ++++++++++++++++ pom.xml | 5 +++++ 10 files changed, 139 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d79ec17..221b32ad4 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 +### Changed +- Add `Automatic-Module-Name` manifest entries so that library can be used + nicely in Java 9 modules. The module names correspond to the root + package name: `org.commonmark`, `org.commonmark.ext.autolink`, etc. + ## [0.10.0] - 2017-09-14 ### Added - Support multiple `DelimiterProcessor` with the same delimiter char as long diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index ddfa30fd8..f1ff149de 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -33,4 +33,20 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.commonmark.ext.autolink + + + + + + + diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 789c4896c..99a1b89c6 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -24,4 +24,20 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.commonmark.ext.gfm.strikethrough + + + + + + + diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index c1fae362a..f05ff6f92 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -24,4 +24,20 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.commonmark.ext.gfm.tables + + + + + + + diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 772eda419..b4f7abed5 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -24,4 +24,20 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.commonmark.ext.heading.anchor + + + + + + + diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 8ecfb33f9..e46ff3a3c 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -24,4 +24,20 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.commonmark.ext.ins + + + + + + + diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index dab2e907d..875e81af1 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -24,4 +24,20 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.commonmark.ext.front.matter + + + + + + + diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 0604164b7..8162060df 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -18,4 +18,20 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.commonmark.testutil + + + + + + + diff --git a/commonmark/pom.xml b/commonmark/pom.xml index f2a446e1a..5c2d75172 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -29,6 +29,22 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.commonmark + + + + + + + benchmark diff --git a/pom.xml b/pom.xml index c5d324779..0f17ac7c2 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,11 @@ 7 + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + org.apache.maven.plugins maven-javadoc-plugin From 435ffae81ac756c4983a004d02ae501092db753b Mon Sep 17 00:00:00 2001 From: Antek Jaworski Date: Fri, 8 Dec 2017 20:40:50 +0100 Subject: [PATCH 270/815] Issue 80: TextContent extension for GFM tables Issue 80: TextContent extension for GFM tables Issue 80: TextContent extension for GFM tables --- README.md | 6 + commonmark-ext-gfm-tables-text/pom.xml | 31 ++++ .../gfm/tables/text/TextTablesExtension.java | 43 +++++ .../text/internal/TextTableNodeRenderer.java | 104 +++++++++++++ .../src/main/javadoc/overview.html | 6 + .../ext/gfm/tables/text/TextTablesTest.java | 147 ++++++++++++++++++ .../renderer/text/TextContentWriter.java | 6 + pom.xml | 6 + 8 files changed, 349 insertions(+) create mode 100644 commonmark-ext-gfm-tables-text/pom.xml create mode 100644 commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/TextTablesExtension.java create mode 100644 commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java create mode 100644 commonmark-ext-gfm-tables-text/src/main/javadoc/overview.html create mode 100644 commonmark-ext-gfm-tables-text/src/test/java/org/commonmark/ext/gfm/tables/text/TextTablesTest.java diff --git a/README.md b/README.md index aac78a543..03cffb68e 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,12 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. +### Tables as text + +Enables text rendering tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. + +Use class `TextTablesExtension` in artifact `commonmark-ext-gfm-tables-text`. + ### Heading anchor Enables adding auto generated "id" attributes to heading tags. The "id" diff --git a/commonmark-ext-gfm-tables-text/pom.xml b/commonmark-ext-gfm-tables-text/pom.xml new file mode 100644 index 000000000..23b2bb557 --- /dev/null +++ b/commonmark-ext-gfm-tables-text/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + com.atlassian.commonmark + commonmark-parent + 0.10.1-SNAPSHOT + + + commonmark-ext-gfm-tables-text + commonmark-java extension for GFM tables as text + commonmark-java extension to render GFM tables as text using "|" pipes (GitHub Flavored Markdown) + + + + com.atlassian.commonmark + commonmark + + + com.atlassian.commonmark + commonmark-ext-gfm-tables + + + + com.atlassian.commonmark + commonmark-test-util + test + + + + diff --git a/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/TextTablesExtension.java b/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/TextTablesExtension.java new file mode 100644 index 000000000..59b8adb1f --- /dev/null +++ b/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/TextTablesExtension.java @@ -0,0 +1,43 @@ +package org.commonmark.ext.gfm.tables.text; + +import org.commonmark.Extension; +import org.commonmark.ext.gfm.tables.internal.TableBlockParser; +import org.commonmark.ext.gfm.tables.text.internal.TextTableNodeRenderer; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentNodeRendererFactory; +import org.commonmark.renderer.text.TextContentRenderer; + +/** + * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). + *

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

    + */ +public class TextTablesExtension implements Parser.ParserExtension, TextContentRenderer.TextContentRendererExtension { + + private TextTablesExtension() { + } + + public static Extension create() { + return new TextTablesExtension(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customBlockParserFactory(new TableBlockParser.Factory()); + } + + @Override + public void extend(TextContentRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { + @Override + public NodeRenderer create(TextContentNodeRendererContext context) { + return new TextTableNodeRenderer(context); + } + }); + } +} diff --git a/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java b/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java new file mode 100644 index 000000000..d5fc92f49 --- /dev/null +++ b/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java @@ -0,0 +1,104 @@ +package org.commonmark.ext.gfm.tables.text.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.node.Node; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentWriter; + +/** + * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text. + */ +public class TextTableNodeRenderer implements NodeRenderer { + + private final TextContentWriter textContentWriter; + private final TextContentNodeRendererContext context; + + public TextTableNodeRenderer(TextContentNodeRendererContext context) { + this.textContentWriter = context.getWriter(); + this.context = context; + } + + @Override + public Set> getNodeTypes() { + return new HashSet<>(Arrays.asList( + TableBlock.class, + TableHead.class, + TableBody.class, + TableRow.class, + TableCell.class + )); + } + + @Override + public void render(Node node) { + // We don't render the table header (node instanceof TableHead) and its children for the text content. + + if (node instanceof TableBlock) { + renderBlock((TableBlock) node); + } else if (node instanceof TableHead) { + renderHead((TableHead) node); + } else if (node instanceof TableBody) { + renderBody((TableBody) node); + } else if (node instanceof TableRow) { + renderRow((TableRow) node); + } else if (node instanceof TableCell) { + renderCell((TableCell) node); + } + } + + private void renderBlock(TableBlock tableBlock) { + renderChildren(tableBlock); + if (tableBlock.getNext() != null) { + textContentWriter.write("\n"); + } + } + + private void renderHead(TableHead tableHead) { + renderChildren(tableHead); + } + + private void renderBody(TableBody tableBody) { + renderChildren(tableBody); + } + + private void renderRow(TableRow tableRow) { + textContentWriter.line(); + renderChildren(tableRow); + textContentWriter.line(); + } + + private void renderCell(TableCell tableCell) { + renderChildren(tableCell); + textContentWriter.pipe(); + textContentWriter.whitespace(); + } + + private void renderLastCell(TableCell tableCell) { + renderChildren(tableCell); + } + + 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); + } + + node = next; + } + } +} diff --git a/commonmark-ext-gfm-tables-text/src/main/javadoc/overview.html b/commonmark-ext-gfm-tables-text/src/main/javadoc/overview.html new file mode 100644 index 000000000..ce9020657 --- /dev/null +++ b/commonmark-ext-gfm-tables-text/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ + + +Extension for rendering GFM tables as text using "|" pipes (GitHub Flavored Markdown) +

    See {@link org.commonmark.ext.gfm.tables.text.TextTablesExtension}

    + + diff --git a/commonmark-ext-gfm-tables-text/src/test/java/org/commonmark/ext/gfm/tables/text/TextTablesTest.java b/commonmark-ext-gfm-tables-text/src/test/java/org/commonmark/ext/gfm/tables/text/TextTablesTest.java new file mode 100644 index 000000000..5a246ab42 --- /dev/null +++ b/commonmark-ext-gfm-tables-text/src/test/java/org/commonmark/ext/gfm/tables/text/TextTablesTest.java @@ -0,0 +1,147 @@ +package org.commonmark.ext.gfm.tables.text; + +import java.util.Collections; +import java.util.Set; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.text.TextContentRenderer; +import org.commonmark.test.RenderingTestCase; +import org.junit.Test; + +public class TextTablesTest extends RenderingTestCase { + + private static final Set EXTENSIONS = Collections.singleton(TextTablesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void oneHeadNoBody() { + assertRendering("Abc|Def\n---|---", "Abc| Def\n"); + } + + @Test + public void oneColumnOneHeadNoBody() { + String expected = "Abc\n"; + assertRendering("|Abc\n|---\n", expected); + assertRendering("|Abc|\n|---|\n", expected); + assertRendering("Abc|\n---|\n", expected); + + // Pipe required on separator + assertRendering("|Abc\n---\n", "|Abc"); + // Pipe required on head + assertRendering("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); + + // Pipe required on separator + assertRendering("|Abc\n---\n|1", "|Abc\n|1"); + + // Pipe required on body + assertRendering("|Abc\n|---\n1\n", "Abc\n\n1"); + } + + @Test + public void oneHeadOneBody() { + assertRendering("Abc|Def\n---|---\n1|2", "Abc| Def\n1| 2\n"); + } + + @Test + public void separatorMustNotHaveLessPartsThanHead() { + assertRendering("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"); + } + + @Test + public void paddingWithCodeBlockIndentation() { + assertRendering("Abc|Def\n---|---\n 1|2", "Abc| Def\n1| 2\n"); + } + + @Test + public void pipesOnOutside() { + assertRendering("|Abc|Def|\n|---|---|\n|1|2|", "Abc| Def\n1| 2\n"); + } + + @Test + public void inlineElements() { + assertRendering("*Abc*|Def\n---|---\n1|2", "Abc| Def\n1| 2\n"); + } + + @Test + public void escapedPipe() { + assertRendering("Abc|Def\n---|---\n1\\|2|20", "Abc| Def\n1|2| 20\n"); + } + + @Test + public void escapedBackslash() { + assertRendering("Abc|Def\n---|---\n1\\\\|2", "Abc| Def\n1\\| 2\n"); + } + + @Test + public void alignLeft() { + assertRendering("Abc|Def\n:---|---\n1|2", "Abc| Def\n1| 2\n"); + } + + @Test + public void alignRight() { + assertRendering("Abc|Def\n---:|---\n1|2", "Abc| Def\n1| 2\n"); + } + + @Test + public void alignCenter() { + assertRendering("Abc|Def\n:---:|---\n1|2", "Abc| Def\n1| 2\n"); + } + + @Test + public void alignCenterSecond() { + assertRendering("Abc|Def\n---|:---:\n1|2", "Abc| Def\n1| 2\n"); + } + + @Test + public void alignLeftWithSpaces() { + assertRendering("Abc|Def\n :--- |---\n1|2", "Abc| Def\n1| 2\n"); + } + + @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---|--- :"); + } + + @Test + public void bodyCanNotHaveMoreColumnsThanHead() { + assertRendering("Abc|Def\n---|---\n1|2|3", "Abc| Def\n1| 2\n"); + } + + @Test + public void bodyWithFewerColumnsThanHeadResultsInEmptyCells() { + assertRendering("Abc|Def|Ghi\n---|---|---\n1|2", "Abc| Def| Ghi\n1| 2| \n"); + } + + @Test + public void insideBlockQuote() { + assertRendering("> Abc|Def\n> ---|---\n> 1|2", "«\nAbc| Def\n1| 2\n»"); + } + + @Test + public void tableEndWithoutEmptyLine() { + assertRendering("Abc|Def\n---|---\n1|2\ntable, you are over", "Abc| Def\n1| 2\n\ntable, you are over"); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} 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..a99f76ff8 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java @@ -24,6 +24,12 @@ public void colon() { } } + public void pipe() { + if (lastChar != 0 && lastChar != '|') { + append('|'); + } + } + public void line() { if (lastChar != 0 && lastChar != '\n') { append('\n'); diff --git a/pom.xml b/pom.xml index c5d324779..5e7a56ed4 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ commonmark-ext-autolink commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables + commonmark-ext-gfm-tables-text commonmark-ext-heading-anchor commonmark-ext-ins commonmark-ext-yaml-front-matter @@ -99,6 +100,11 @@ commonmark-ext-gfm-tables 0.10.1-SNAPSHOT + + com.atlassian.commonmark + commonmark-ext-gfm-tables-text + 0.10.1-SNAPSHOT + com.atlassian.commonmark commonmark-ext-heading-anchor From 17040f84c30a53b2bcaa0e3d726876512f884d06 Mon Sep 17 00:00:00 2001 From: Antek Jaworski Date: Sat, 23 Dec 2017 08:22:13 +0100 Subject: [PATCH 271/815] Issue 80: TextContent extension for GFM tables, reverted earlier changes --- ...lesExtension.java => TablesExtension.java} | 10 +- .../tables/TextContentTablesExtension.java | 46 ----- ...deRenderer.java => TableNodeRenderer.java} | 4 +- .../TextContentTableNodeRenderer.java | 104 ---------- .../{HtmlTablesTest.java => TablesTest.java} | 4 +- .../ext/gfm/tables/TextContentTablesTest.java | 179 ------------------ .../integration/SpecIntegrationTest.java | 4 +- .../commonmark/test/RenderingTestCase.java | 8 +- 8 files changed, 15 insertions(+), 344 deletions(-) rename commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/{HtmlTablesExtension.java => TablesExtension.java} (80%) delete mode 100644 commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TextContentTablesExtension.java rename commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/{HtmlTableNodeRenderer.java => TableNodeRenderer.java} (96%) delete mode 100644 commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TextContentTableNodeRenderer.java rename commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/{HtmlTablesTest.java => TablesTest.java} (99%) delete mode 100644 commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TextContentTablesTest.java diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/HtmlTablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java similarity index 80% rename from commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/HtmlTablesExtension.java rename to commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java index 6073b5282..5f562fc63 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/HtmlTablesExtension.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TablesExtension.java @@ -2,7 +2,7 @@ import org.commonmark.Extension; import org.commonmark.ext.gfm.tables.internal.TableBlockParser; -import org.commonmark.ext.gfm.tables.internal.HtmlTableNodeRenderer; +import org.commonmark.ext.gfm.tables.internal.TableNodeRenderer; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlNodeRendererFactory; @@ -20,13 +20,13 @@ * The parsed tables are turned into {@link TableBlock} blocks. *

    */ -public class HtmlTablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { - private HtmlTablesExtension() { + private TablesExtension() { } public static Extension create() { - return new HtmlTablesExtension(); + return new TablesExtension(); } @Override @@ -39,7 +39,7 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { @Override public NodeRenderer create(HtmlNodeRendererContext context) { - return new HtmlTableNodeRenderer(context); + return new TableNodeRenderer(context); } }); } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TextContentTablesExtension.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TextContentTablesExtension.java deleted file mode 100644 index 9ca89ae93..000000000 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/TextContentTablesExtension.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.commonmark.ext.gfm.tables; - -import org.commonmark.Extension; -import org.commonmark.ext.gfm.tables.internal.TableBlockParser; -import org.commonmark.ext.gfm.tables.internal.TextContentTableNodeRenderer; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.text.TextContentNodeRendererContext; -import org.commonmark.renderer.text.TextContentNodeRendererFactory; -import org.commonmark.renderer.text.TextContentRenderer; - -/** - * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). - *

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

    - *

    - * The parsed tables are turned into {@link TableBlock} blocks. - *

    - */ -public class TextContentTablesExtension implements Parser.ParserExtension, TextContentRenderer.TextContentRendererExtension { - - private TextContentTablesExtension() { - } - - public static Extension create() { - return new TextContentTablesExtension(); - } - - @Override - public void extend(Parser.Builder parserBuilder) { - parserBuilder.customBlockParserFactory(new TableBlockParser.Factory()); - } - - @Override - public void extend(TextContentRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { - @Override - public NodeRenderer create(TextContentNodeRendererContext context) { - return new TextContentTableNodeRenderer(context); - } - }); - } -} diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/HtmlTableNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java similarity index 96% rename from commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/HtmlTableNodeRenderer.java rename to commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java index 2be284d66..1bff26e19 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/HtmlTableNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableNodeRenderer.java @@ -8,12 +8,12 @@ import java.util.*; -public class HtmlTableNodeRenderer implements NodeRenderer { +public class TableNodeRenderer implements NodeRenderer { private final HtmlWriter htmlWriter; private final HtmlNodeRendererContext context; - public HtmlTableNodeRenderer(HtmlNodeRendererContext context) { + public TableNodeRenderer(HtmlNodeRendererContext context) { this.htmlWriter = context.getWriter(); this.context = context; } diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TextContentTableNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TextContentTableNodeRenderer.java deleted file mode 100644 index f3088867d..000000000 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TextContentTableNodeRenderer.java +++ /dev/null @@ -1,104 +0,0 @@ -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.node.Node; -import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.text.TextContentNodeRendererContext; -import org.commonmark.renderer.text.TextContentWriter; - -/** - * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text. - */ -public class TextContentTableNodeRenderer implements NodeRenderer { - - private final TextContentWriter textContentWriter; - private final TextContentNodeRendererContext context; - - public TextContentTableNodeRenderer(TextContentNodeRendererContext context) { - this.textContentWriter = context.getWriter(); - this.context = context; - } - - @Override - public Set> getNodeTypes() { - return new HashSet<>(Arrays.asList( - TableBlock.class, - TableHead.class, - TableBody.class, - TableRow.class, - TableCell.class - )); - } - - @Override - public void render(Node node) { - // We don't render the table header (node instanceof TableHead) and its children for the text content. - - if (node instanceof TableBlock) { - renderBlock((TableBlock) node); - } else if (node instanceof TableHead) { - renderHead((TableHead) node); - } else if (node instanceof TableBody) { - renderBody((TableBody) node); - } else if (node instanceof TableRow) { - renderRow((TableRow) node); - } else if (node instanceof TableCell) { - renderCell((TableCell) node); - } - } - - private void renderBlock(TableBlock tableBlock) { - renderChildren(tableBlock); - if (tableBlock.getNext() != null) { - textContentWriter.write("\n"); - } - } - - private void renderHead(TableHead tableHead) { - renderChildren(tableHead); - } - - private void renderBody(TableBody tableBody) { - renderChildren(tableBody); - } - - private void renderRow(TableRow tableRow) { - textContentWriter.line(); - renderChildren(tableRow); - textContentWriter.line(); - } - - private void renderCell(TableCell tableCell) { - renderChildren(tableCell); - textContentWriter.colon(); - textContentWriter.whitespace(); - } - - private void renderLastCell(TableCell tableCell) { - renderChildren(tableCell); - } - - 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); - } - - node = next; - } - } -} diff --git a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/HtmlTablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java similarity index 99% rename from commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/HtmlTablesTest.java rename to commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java index eb2ed14fc..9925959f4 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/HtmlTablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -17,9 +17,9 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -public class HtmlTablesTest extends RenderingTestCase { +public class TablesTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(HtmlTablesExtension.create()); + private static final Set EXTENSIONS = Collections.singleton(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/TextContentTablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TextContentTablesTest.java deleted file mode 100644 index c3c239ce1..000000000 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TextContentTablesTest.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.commonmark.ext.gfm.tables; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import org.commonmark.Extension; -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.AttributeProvider; -import org.commonmark.renderer.html.AttributeProviderContext; -import org.commonmark.renderer.html.AttributeProviderFactory; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.text.TextContentNodeRendererFactory; -import org.commonmark.renderer.text.TextContentRenderer; -import org.commonmark.test.RenderingTestCase; -import org.junit.Test; - -public class TextContentTablesTest extends RenderingTestCase { - - private static final Set EXTENSIONS = Collections.singleton(TextContentTablesExtension.create()); - private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); - private static final TextContentRenderer RENDERER = TextContentRenderer.builder().extensions(EXTENSIONS).build(); - - @Test - public void mustHaveHeaderAndSeparator() { - assertRendering("Abc|Def", "Abc|Def"); - assertRendering("Abc | Def", "Abc | Def"); - } - - @Test - public void separatorMustBeThreeOrMore() { - assertRendering("Abc|Def\n-|-", "Abc|Def\n-|-"); - assertRendering("Abc|Def\n--|--", "Abc|Def\n--|--"); - } - - @Test - public void separatorCanNotHaveLeadingSpaceThenPipe() { - assertRendering("Abc|Def\n |---|---", "Abc|Def\n|---|---"); - } - - @Test - public void headerMustBeOneLine() { - assertRendering("No\nAbc|Def\n---|---", "No\nAbc|Def\n---|---"); - } - - @Test - public void oneHeadNoBody() { - assertRendering("Abc|Def\n---|---", "Abc: Def\n"); - } - - @Test - public void oneColumnOneHeadNoBody() { - String expected = "Abc\n"; - assertRendering("|Abc\n|---\n", expected); - assertRendering("|Abc|\n|---|\n", expected); - assertRendering("Abc|\n---|\n", expected); - - // Pipe required on separator - assertRendering("|Abc\n---\n", "|Abc"); - // Pipe required on head - assertRendering("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); - - // Pipe required on separator - assertRendering("|Abc\n---\n|1", "|Abc\n|1"); - - // Pipe required on body - assertRendering("|Abc\n|---\n1\n", "Abc\n\n1"); - } - - @Test - public void oneHeadOneBody() { - assertRendering("Abc|Def\n---|---\n1|2", "Abc: Def\n1: 2\n"); - } - - @Test - public void separatorMustNotHaveLessPartsThanHead() { - assertRendering("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"); - } - - @Test - public void paddingWithCodeBlockIndentation() { - assertRendering("Abc|Def\n---|---\n 1|2", "Abc: Def\n1: 2\n"); - } - - @Test - public void pipesOnOutside() { - assertRendering("|Abc|Def|\n|---|---|\n|1|2|", "Abc: Def\n1: 2\n"); - } - - @Test - public void inlineElements() { - assertRendering("*Abc*|Def\n---|---\n1|2", "Abc: Def\n1: 2\n"); - } - - @Test - public void escapedPipe() { - assertRendering("Abc|Def\n---|---\n1\\|2|20", "Abc: Def\n1|2: 20\n"); - } - - @Test - public void escapedBackslash() { - assertRendering("Abc|Def\n---|---\n1\\\\|2", "Abc: Def\n1\\: 2\n"); - } - - @Test - public void alignLeft() { - assertRendering("Abc|Def\n:---|---\n1|2", "Abc: Def\n1: 2\n"); - } - - @Test - public void alignRight() { - assertRendering("Abc|Def\n---:|---\n1|2", "Abc: Def\n1: 2\n"); - } - - @Test - public void alignCenter() { - assertRendering("Abc|Def\n:---:|---\n1|2", "Abc: Def\n1: 2\n"); - } - - @Test - public void alignCenterSecond() { - assertRendering("Abc|Def\n---|:---:\n1|2", "Abc: Def\n1: 2\n"); - } - - @Test - public void alignLeftWithSpaces() { - assertRendering("Abc|Def\n :--- |---\n1|2", "Abc: Def\n1: 2\n"); - } - - @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---|--- :"); - } - - @Test - public void bodyCanNotHaveMoreColumnsThanHead() { - assertRendering("Abc|Def\n---|---\n1|2|3", "Abc: Def\n1: 2\n"); - } - - @Test - public void bodyWithFewerColumnsThanHeadResultsInEmptyCells() { - assertRendering("Abc|Def|Ghi\n---|---|---\n1|2", "Abc: Def: Ghi\n1: 2: \n"); - } - - @Test - public void insideBlockQuote() { - assertRendering("> Abc|Def\n> ---|---\n> 1|2", "«\nAbc: Def\n1: 2\n»"); - } - - @Test - public void tableEndWithoutEmptyLine() { - assertRendering("Abc|Def\n---|---\n1|2\ntable, you are over", "Abc: Def\n1: 2\n\ntable, you are over"); - } - - @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 c3dd08d02..af4edf23d 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.ext.autolink.AutolinkExtension; import org.commonmark.ext.ins.InsExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; -import org.commonmark.ext.gfm.tables.HtmlTablesExtension; +import org.commonmark.ext.gfm.tables.TablesExtension; import org.commonmark.ext.front.matter.YamlFrontMatterExtension; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; @@ -23,7 +23,7 @@ public class SpecIntegrationTest extends SpecTestCase { AutolinkExtension.create(), InsExtension.create(), StrikethroughExtension.create(), - HtmlTablesExtension.create(), + TablesExtension.create(), YamlFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. diff --git a/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java index b182cc735..1ee19dbb2 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java @@ -6,12 +6,12 @@ public abstract class RenderingTestCase { protected abstract String render(String source); - protected void assertRendering(String source, String expectedResult) { - String renderedContent = render(source); + protected void assertRendering(String source, String expectedHtml) { + String html = render(source); // include source for better assertion errors - String expected = showTabs(expectedResult + "\n\n" + source); - String actual = showTabs(renderedContent + "\n\n" + source); + String expected = showTabs(expectedHtml + "\n\n" + source); + String actual = showTabs(html + "\n\n" + source); assertEquals(expected, actual); } From b4c2f51306d9f82b861c3d5028b2f3ae007e856b Mon Sep 17 00:00:00 2001 From: Antek Jaworski Date: Sat, 23 Dec 2017 08:23:05 +0100 Subject: [PATCH 272/815] Issue 80: TextContent extension for GFM tables, processed review comment --- .../ext/gfm/tables/text/internal/TextTableNodeRenderer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java b/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java index d5fc92f49..abc307068 100644 --- a/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java +++ b/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java @@ -40,8 +40,6 @@ public Set> getNodeTypes() { @Override public void render(Node node) { - // We don't render the table header (node instanceof TableHead) and its children for the text content. - if (node instanceof TableBlock) { renderBlock((TableBlock) node); } else if (node instanceof TableHead) { From d997b21159e1f3e9f0ae24f876c68efb6a969fd6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jan 2018 16:56:02 +1100 Subject: [PATCH 273/815] Bump autolink to version with Automatic-Module-Name --- 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 f1ff149de..54a8ffc92 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.7.0 + 0.8.0 From bf47c35dbad7a6113b10cd36bf8bc48d67bb5a39 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Jan 2018 14:16:56 +1100 Subject: [PATCH 274/815] Change Java 7 to "best effort" support With the new parent poms, the build requires Java 8, so it fails building on Java 7. So remove the openjdk7 build and change the Android one to openjdk8. --- .travis.yml | 4 +--- README.md | 11 +++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 42dba458e..9476c64d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,7 @@ matrix: include: - jdk: oraclejdk8 env: TEST=java - - jdk: openjdk7 - env: TEST=java - - jdk: openjdk7 + - jdk: openjdk8 env: TEST=android dist: precise android: diff --git a/README.md b/README.md index aac78a543..5836c5e91 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,15 @@ Provides classes for parsing input to an abstract syntax tree of nodes started out as a port of [commonmark.js], but has since evolved into a full library with a nice API and the following features: -* Small (minimal dependencies) +* Small (core has no dependencies, extensions in separate artifacts) * Fast (10-20 times faster than pegdown, see benchmarks in repo) * Flexible (manipulate the AST after parsing, customize HTML rendering) * Extensible (tables, strikethrough, autolinking and more, see below) -Requirements: - -* Java 7 or above -* Works on Android, minimum API level 15 (see [commonmark-android-test](commonmark-android-test) directory) -* The core has no dependencies; for extensions, see below +The library is supported on Java 8 or higher. 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 15, see the +[commonmark-android-test](commonmark-android-test) directory. Coordinates for core library (see all on [Maven Central]): From d7ba261223b31fa71c705acbbc2262a731137bcc Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Jan 2018 14:28:31 +1100 Subject: [PATCH 275/815] Use oraclejdk8 for Android build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9476c64d9..023c2cd1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ matrix: include: - jdk: oraclejdk8 env: TEST=java - - jdk: openjdk8 + - jdk: oraclejdk8 env: TEST=android dist: precise android: From c637dd37c467b858b4d39bfa2079b7fae7d43502 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Jan 2018 14:40:12 +1100 Subject: [PATCH 276/815] CHANGELOG: Add note about Java 7 support --- CHANGELOG.md | 2 ++ README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 221b32ad4..390ef92ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ with the exception that 0.x versions can break between minor versions. - Add `Automatic-Module-Name` manifest entries so that library can be used nicely in Java 9 modules. The module names correspond to the root package name: `org.commonmark`, `org.commonmark.ext.autolink`, etc. +- Java 7 is now only supported on a best-effort basis (but it has been + EOL for quite some time, so yeah) ## [0.10.0] - 2017-09-14 ### Added diff --git a/README.md b/README.md index 5836c5e91..fd05a69d2 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ full library with a nice API and the following features: * Extensible (tables, strikethrough, autolinking and more, see below) The library is supported on Java 8 or higher. It should work on Java 7 -and Android too, but that is on a "best effort" basis, please report +and Android too, but that is on a best-effort basis, please report problems. For Android the minimum API level is 15, see the [commonmark-android-test](commonmark-android-test) directory. From 66e2d3f9d93bd4a4cdb924ea20275df053fa3344 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Jan 2018 15:04:56 +1100 Subject: [PATCH 277/815] Add links to CLA in README and add COC --- CODE_OF_CONDUCT.md | 27 +++++++++++++++++++++++++++ README.md | 15 ++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..ce3a9d1d8 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,27 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic addresses, without explicit permission +* Submitting contributions or comments that you know to violate the intellectual property or privacy rights of others +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer. Complaints will result in a response and be reviewed and investigated in a way that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/3/0/ diff --git a/README.md b/README.md index fd05a69d2..b5f77b451 100644 --- a/README.md +++ b/README.md @@ -325,11 +325,24 @@ See the existing "help wanted" issues for things to start contributing. For bigger changes, make sure you start a discussion first by creating an issue and explaining the intended change. +Atlassian requires contributors to sign a Contributor License Agreement, +known as a CLA. This serves as a record stating that the contributor is +entitled to contribute the code/documentation/translation to the project +and is willing to have it used in distributions and derivative works +(or is willing to transfer ownership). + +Prior to accepting your first contribution we ask that you please follow the +appropriate link below to digitally sign the CLA. The Corporate CLA is for those +who are contributing as a member of an organization and the individual CLA is +for those contributing as an individual. + +* [CLA for corporate contributors](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=e1c17c66-ca4d-4aab-a953-2c231af4a20b) +* [CLA for individuals](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=3f94fbdc-2fbe-46ac-b14c-5d152700ae5d) License ------- -Copyright (c) 2015-2017 Atlassian and others. +Copyright (c) 2015-2018 Atlassian and others. BSD (2-clause) licensed, see LICENSE.txt file. From 2e37b5f9a8ed8a23a6687fd47bcee455c148524c Mon Sep 17 00:00:00 2001 From: Antek Jaworski Date: Sun, 14 Jan 2018 09:15:20 +0100 Subject: [PATCH 278/815] Issue 80: TextContent extension for GFM tables, processed review comments --- README.md | 6 -- commonmark-ext-gfm-tables-text/pom.xml | 31 ------ .../gfm/tables/text/TextTablesExtension.java | 43 --------- .../src/main/javadoc/overview.html | 6 -- .../ext/gfm/tables/TablesExtension.java | 21 +++- .../internal/TableHtmlNodeRenderer.java | 96 +++++++++++++++++++ .../tables/internal/TableNodeRenderer.java | 96 +++---------------- .../TableTextContentNodeRenderer.java | 49 ++-------- .../commonmark/ext/gfm/tables/TablesTest.java | 20 ++-- .../ext/gfm/tables/TablesTextContentTest.java | 6 +- .../commonmark/test/RenderingTestCase.java | 8 +- pom.xml | 6 -- 12 files changed, 155 insertions(+), 233 deletions(-) delete mode 100644 commonmark-ext-gfm-tables-text/pom.xml delete mode 100644 commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/TextTablesExtension.java delete mode 100644 commonmark-ext-gfm-tables-text/src/main/javadoc/overview.html create mode 100644 commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java rename commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java => commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java (54%) rename commonmark-ext-gfm-tables-text/src/test/java/org/commonmark/ext/gfm/tables/text/TextTablesTest.java => commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java (96%) diff --git a/README.md b/README.md index 03cffb68e..aac78a543 100644 --- a/README.md +++ b/README.md @@ -267,12 +267,6 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. -### Tables as text - -Enables text rendering tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. - -Use class `TextTablesExtension` in artifact `commonmark-ext-gfm-tables-text`. - ### Heading anchor Enables adding auto generated "id" attributes to heading tags. The "id" diff --git a/commonmark-ext-gfm-tables-text/pom.xml b/commonmark-ext-gfm-tables-text/pom.xml deleted file mode 100644 index 23b2bb557..000000000 --- a/commonmark-ext-gfm-tables-text/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - 4.0.0 - - com.atlassian.commonmark - commonmark-parent - 0.10.1-SNAPSHOT - - - commonmark-ext-gfm-tables-text - commonmark-java extension for GFM tables as text - commonmark-java extension to render GFM tables as text using "|" pipes (GitHub Flavored Markdown) - - - - com.atlassian.commonmark - commonmark - - - com.atlassian.commonmark - commonmark-ext-gfm-tables - - - - com.atlassian.commonmark - commonmark-test-util - test - - - - diff --git a/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/TextTablesExtension.java b/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/TextTablesExtension.java deleted file mode 100644 index 59b8adb1f..000000000 --- a/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/TextTablesExtension.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.commonmark.ext.gfm.tables.text; - -import org.commonmark.Extension; -import org.commonmark.ext.gfm.tables.internal.TableBlockParser; -import org.commonmark.ext.gfm.tables.text.internal.TextTableNodeRenderer; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.text.TextContentNodeRendererContext; -import org.commonmark.renderer.text.TextContentNodeRendererFactory; -import org.commonmark.renderer.text.TextContentRenderer; - -/** - * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). - *

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

    - */ -public class TextTablesExtension implements Parser.ParserExtension, TextContentRenderer.TextContentRendererExtension { - - private TextTablesExtension() { - } - - public static Extension create() { - return new TextTablesExtension(); - } - - @Override - public void extend(Parser.Builder parserBuilder) { - parserBuilder.customBlockParserFactory(new TableBlockParser.Factory()); - } - - @Override - public void extend(TextContentRenderer.Builder rendererBuilder) { - rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { - @Override - public NodeRenderer create(TextContentNodeRendererContext context) { - return new TextTableNodeRenderer(context); - } - }); - } -} diff --git a/commonmark-ext-gfm-tables-text/src/main/javadoc/overview.html b/commonmark-ext-gfm-tables-text/src/main/javadoc/overview.html deleted file mode 100644 index ce9020657..000000000 --- a/commonmark-ext-gfm-tables-text/src/main/javadoc/overview.html +++ /dev/null @@ -1,6 +0,0 @@ - - -Extension for rendering GFM tables as text using "|" pipes (GitHub Flavored Markdown) -

    See {@link org.commonmark.ext.gfm.tables.text.TextTablesExtension}

    - - 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 5f562fc63..6ce6614e9 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 @@ -2,12 +2,16 @@ import org.commonmark.Extension; import org.commonmark.ext.gfm.tables.internal.TableBlockParser; -import org.commonmark.ext.gfm.tables.internal.TableNodeRenderer; +import org.commonmark.ext.gfm.tables.internal.TableHtmlNodeRenderer; +import org.commonmark.ext.gfm.tables.internal.TableTextContentNodeRenderer; 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.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentNodeRendererFactory; +import org.commonmark.renderer.text.TextContentRenderer; /** * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). @@ -20,7 +24,8 @@ * The parsed tables are turned into {@link TableBlock} blocks. *

    */ -public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, + TextContentRenderer.TextContentRendererExtension { private TablesExtension() { } @@ -39,7 +44,17 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { @Override public NodeRenderer create(HtmlNodeRendererContext context) { - return new TableNodeRenderer(context); + return new TableHtmlNodeRenderer(context); + } + }); + } + + @Override + public void extend(TextContentRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { + @Override + public NodeRenderer create(TextContentNodeRendererContext context) { + return new TableTextContentNodeRenderer(context); } }); } 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 new file mode 100644 index 000000000..aa393ac15 --- /dev/null +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java @@ -0,0 +1,96 @@ +package org.commonmark.ext.gfm.tables.internal; + +import java.util.Collections; +import java.util.Map; + +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.node.Node; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; + +public class TableHtmlNodeRenderer extends TableNodeRenderer { + + private final HtmlWriter htmlWriter; + private final HtmlNodeRendererContext context; + + public TableHtmlNodeRenderer(HtmlNodeRendererContext context) { + this.htmlWriter = context.getWriter(); + this.context = context; + } + + protected void renderBlock(TableBlock tableBlock) { + htmlWriter.line(); + htmlWriter.tag("table", getAttributes(tableBlock, "table")); + renderChildren(tableBlock); + htmlWriter.tag("/table"); + htmlWriter.line(); + } + + protected void renderHead(TableHead tableHead) { + htmlWriter.line(); + htmlWriter.tag("thead", getAttributes(tableHead, "thead")); + renderChildren(tableHead); + htmlWriter.tag("/thead"); + htmlWriter.line(); + } + + protected void renderBody(TableBody tableBody) { + htmlWriter.line(); + htmlWriter.tag("tbody", getAttributes(tableBody, "tbody")); + renderChildren(tableBody); + htmlWriter.tag("/tbody"); + htmlWriter.line(); + } + + protected void renderRow(TableRow tableRow) { + htmlWriter.line(); + htmlWriter.tag("tr", getAttributes(tableRow, "tr")); + renderChildren(tableRow); + htmlWriter.tag("/tr"); + htmlWriter.line(); + } + + protected void renderCell(TableCell tableCell) { + String tagName = tableCell.isHeader() ? "th" : "td"; + htmlWriter.tag(tagName, getCellAttributes(tableCell, tagName)); + renderChildren(tableCell); + htmlWriter.tag("/" + tagName); + } + + private Map getAttributes(Node node, String tagName) { + return context.extendAttributes(node, tagName, Collections.emptyMap()); + } + + private Map getCellAttributes(TableCell tableCell, String tagName) { + if (tableCell.getAlignment() != null) { + return context.extendAttributes(tableCell, tagName, Collections.singletonMap("align", getAlignValue(tableCell.getAlignment()))); + } else { + return context.extendAttributes(tableCell, tagName, Collections.emptyMap()); + } + } + + private static String getAlignValue(TableCell.Alignment alignment) { + switch (alignment) { + case LEFT: + return "left"; + case CENTER: + return "center"; + case RIGHT: + return "right"; + } + throw new IllegalStateException("Unknown alignment: " + alignment); + } + + 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/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 1bff26e19..93478a30b 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,22 +1,18 @@ package org.commonmark.ext.gfm.tables.internal; -import org.commonmark.ext.gfm.tables.*; +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.node.Node; import org.commonmark.renderer.NodeRenderer; -import org.commonmark.renderer.html.HtmlNodeRendererContext; -import org.commonmark.renderer.html.HtmlWriter; -import java.util.*; - -public class TableNodeRenderer implements NodeRenderer { - - private final HtmlWriter htmlWriter; - private final HtmlNodeRendererContext context; - - public TableNodeRenderer(HtmlNodeRendererContext context) { - this.htmlWriter = context.getWriter(); - this.context = context; - } +abstract class TableNodeRenderer implements NodeRenderer { @Override public Set> getNodeTypes() { @@ -44,75 +40,13 @@ public void render(Node node) { } } - private void renderBlock(TableBlock tableBlock) { - htmlWriter.line(); - htmlWriter.tag("table", getAttributes(tableBlock, "table")); - renderChildren(tableBlock); - htmlWriter.tag("/table"); - htmlWriter.line(); - } - - private void renderHead(TableHead tableHead) { - htmlWriter.line(); - htmlWriter.tag("thead", getAttributes(tableHead, "thead")); - renderChildren(tableHead); - htmlWriter.tag("/thead"); - htmlWriter.line(); - } - - private void renderBody(TableBody tableBody) { - htmlWriter.line(); - htmlWriter.tag("tbody", getAttributes(tableBody, "tbody")); - renderChildren(tableBody); - htmlWriter.tag("/tbody"); - htmlWriter.line(); - } - - private void renderRow(TableRow tableRow) { - htmlWriter.line(); - htmlWriter.tag("tr", getAttributes(tableRow, "tr")); - renderChildren(tableRow); - htmlWriter.tag("/tr"); - htmlWriter.line(); - } + protected abstract void renderBlock(TableBlock node); - private void renderCell(TableCell tableCell) { - String tagName = tableCell.isHeader() ? "th" : "td"; - htmlWriter.tag(tagName, getCellAttributes(tableCell, tagName)); - renderChildren(tableCell); - htmlWriter.tag("/" + tagName); - } + protected abstract void renderHead(TableHead node); - private Map getAttributes(Node node, String tagName) { - return context.extendAttributes(node, tagName, Collections.emptyMap()); - } - - private Map getCellAttributes(TableCell tableCell, String tagName) { - if (tableCell.getAlignment() != null) { - return context.extendAttributes(tableCell, tagName, Collections.singletonMap("align", getAlignValue(tableCell.getAlignment()))); - } else { - return context.extendAttributes(tableCell, tagName, Collections.emptyMap()); - } - } + protected abstract void renderBody(TableBody node); - private static String getAlignValue(TableCell.Alignment alignment) { - switch (alignment) { - case LEFT: - return "left"; - case CENTER: - return "center"; - case RIGHT: - return "right"; - } - throw new IllegalStateException("Unknown alignment: " + alignment); - } + protected abstract void renderRow(TableRow node); - private void renderChildren(Node parent) { - Node node = parent.getFirstChild(); - while (node != null) { - Node next = node.getNext(); - context.render(node); - node = next; - } - } + protected abstract void renderCell(TableCell node); } diff --git a/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java similarity index 54% rename from commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java rename to commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java index abc307068..b9073e53e 100644 --- a/commonmark-ext-gfm-tables-text/src/main/java/org/commonmark/ext/gfm/tables/text/internal/TextTableNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableTextContentNodeRenderer.java @@ -1,8 +1,4 @@ -package org.commonmark.ext.gfm.tables.text.internal; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +package org.commonmark.ext.gfm.tables.internal; import org.commonmark.ext.gfm.tables.TableBlock; import org.commonmark.ext.gfm.tables.TableBody; @@ -10,71 +6,44 @@ import org.commonmark.ext.gfm.tables.TableHead; import org.commonmark.ext.gfm.tables.TableRow; import org.commonmark.node.Node; -import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.text.TextContentNodeRendererContext; import org.commonmark.renderer.text.TextContentWriter; /** - * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text. + * The Table node renderer that is needed for rendering GFM tables (GitHub Flavored Markdown) to text content. */ -public class TextTableNodeRenderer implements NodeRenderer { +public class TableTextContentNodeRenderer extends TableNodeRenderer { private final TextContentWriter textContentWriter; private final TextContentNodeRendererContext context; - public TextTableNodeRenderer(TextContentNodeRendererContext context) { + public TableTextContentNodeRenderer(TextContentNodeRendererContext context) { this.textContentWriter = context.getWriter(); this.context = context; } - @Override - public Set> getNodeTypes() { - return new HashSet<>(Arrays.asList( - TableBlock.class, - TableHead.class, - TableBody.class, - TableRow.class, - TableCell.class - )); - } - - @Override - public void render(Node node) { - if (node instanceof TableBlock) { - renderBlock((TableBlock) node); - } else if (node instanceof TableHead) { - renderHead((TableHead) node); - } else if (node instanceof TableBody) { - renderBody((TableBody) node); - } else if (node instanceof TableRow) { - renderRow((TableRow) node); - } else if (node instanceof TableCell) { - renderCell((TableCell) node); - } - } - - private void renderBlock(TableBlock tableBlock) { + protected void renderBlock(TableBlock tableBlock) { renderChildren(tableBlock); if (tableBlock.getNext() != null) { textContentWriter.write("\n"); } } - private void renderHead(TableHead tableHead) { + protected void renderHead(TableHead tableHead) { renderChildren(tableHead); } - private void renderBody(TableBody tableBody) { + protected void renderBody(TableBody tableBody) { renderChildren(tableBody); } - private void renderRow(TableRow tableRow) { + protected void renderRow(TableRow tableRow) { textContentWriter.line(); renderChildren(tableRow); textContentWriter.line(); } - private void renderCell(TableCell tableCell) { + protected void renderCell(TableCell tableCell) { renderChildren(tableCell); textContentWriter.pipe(); textContentWriter.whitespace(); 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 9925959f4..1e701eff7 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,22 +1,22 @@ package org.commonmark.ext.gfm.tables; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + import org.commonmark.Extension; -import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProviderContext; import org.commonmark.renderer.html.AttributeProviderFactory; -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.test.RenderingTestCase; import org.junit.Test; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - public class TablesTest extends RenderingTestCase { private static final Set EXTENSIONS = Collections.singleton(TablesExtension.create()); diff --git a/commonmark-ext-gfm-tables-text/src/test/java/org/commonmark/ext/gfm/tables/text/TextTablesTest.java b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java similarity index 96% rename from commonmark-ext-gfm-tables-text/src/test/java/org/commonmark/ext/gfm/tables/text/TextTablesTest.java rename to commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java index 5a246ab42..a5344a5f9 100644 --- a/commonmark-ext-gfm-tables-text/src/test/java/org/commonmark/ext/gfm/tables/text/TextTablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java @@ -1,4 +1,4 @@ -package org.commonmark.ext.gfm.tables.text; +package org.commonmark.ext.gfm.tables; import java.util.Collections; import java.util.Set; @@ -9,9 +9,9 @@ import org.commonmark.test.RenderingTestCase; import org.junit.Test; -public class TextTablesTest extends RenderingTestCase { +public class TablesTextContentTest extends RenderingTestCase { - private static final Set EXTENSIONS = Collections.singleton(TextTablesExtension.create()); + private static final Set EXTENSIONS = Collections.singleton(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-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java b/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java index 1ee19dbb2..b182cc735 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/test/RenderingTestCase.java @@ -6,12 +6,12 @@ public abstract class RenderingTestCase { protected abstract String render(String source); - protected void assertRendering(String source, String expectedHtml) { - String html = render(source); + protected void assertRendering(String source, String expectedResult) { + String renderedContent = render(source); // include source for better assertion errors - String expected = showTabs(expectedHtml + "\n\n" + source); - String actual = showTabs(html + "\n\n" + source); + String expected = showTabs(expectedResult + "\n\n" + source); + String actual = showTabs(renderedContent + "\n\n" + source); assertEquals(expected, actual); } diff --git a/pom.xml b/pom.xml index 5e7a56ed4..c5d324779 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,6 @@ commonmark-ext-autolink commonmark-ext-gfm-strikethrough commonmark-ext-gfm-tables - commonmark-ext-gfm-tables-text commonmark-ext-heading-anchor commonmark-ext-ins commonmark-ext-yaml-front-matter @@ -100,11 +99,6 @@ commonmark-ext-gfm-tables 0.10.1-SNAPSHOT
    - - com.atlassian.commonmark - commonmark-ext-gfm-tables-text - 0.10.1-SNAPSHOT - com.atlassian.commonmark commonmark-ext-heading-anchor From 006d43ab2dea8602fb3df45e00e025a29f3c1cc2 Mon Sep 17 00:00:00 2001 From: Antek Jaworski Date: Sun, 14 Jan 2018 09:36:21 +0100 Subject: [PATCH 279/815] Merge remote-tracking branch 'remotes/upstream/master' into issue-80-textcontent-extension-for-gfm-tables # Conflicts: # commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java --- .../org/commonmark/ext/gfm/tables/TablesTextContentTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a5344a5f9..c52bb348b 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,7 +6,7 @@ import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.text.TextContentRenderer; -import org.commonmark.test.RenderingTestCase; +import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; public class TablesTextContentTest extends RenderingTestCase { From effebfa91dfd27453ec30e07765b5c6e8686cc07 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 16 Jan 2018 12:19:52 +1100 Subject: [PATCH 280/815] Don't add `pipe` method to `TextContentWriter` I'm not sure how generally useful it would be. --- .../gfm/tables/internal/TableTextContentNodeRenderer.java | 2 +- .../org/commonmark/renderer/text/TextContentWriter.java | 6 ------ 2 files changed, 1 insertion(+), 7 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 b9073e53e..94b0e8665 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 @@ -45,7 +45,7 @@ protected void renderRow(TableRow tableRow) { protected void renderCell(TableCell tableCell) { renderChildren(tableCell); - textContentWriter.pipe(); + textContentWriter.write('|'); textContentWriter.whitespace(); } 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 a99f76ff8..0ea56e621 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentWriter.java @@ -24,12 +24,6 @@ public void colon() { } } - public void pipe() { - if (lastChar != 0 && lastChar != '|') { - append('|'); - } - } - public void line() { if (lastChar != 0 && lastChar != '\n') { append('\n'); From 4167c0cb49d4bf4b63dd9fb271e5e260c2fd9de2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 16 Jan 2018 12:26:24 +1100 Subject: [PATCH 281/815] Prepare for 0.11.0 --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 390ef92ef..4d1f0001f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## Unreleased +## [0.11.0] - 2018-01-16 +### Added +- The extension for tables now also renders to plain text + (when using a `TextContentRenderer`), thanks @ahjaworski ### Changed - Add `Automatic-Module-Name` manifest entries so that library can be used nicely in Java 9 modules. The module names correspond to the root @@ -210,6 +213,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.11.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.10.0...commonmark-parent-0.11.0 [0.10.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.9.0...commonmark-parent-0.10.0 [0.9.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.8.0...commonmark-parent-0.9.0 [0.8.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.7.1...commonmark-parent-0.8.0 From 11722cc2053b09161e41d748facf34e4d42b2d10 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 17 Jan 2018 10:43:59 +1100 Subject: [PATCH 282/815] CHANGELOG: Change date Maybe it would be a better idea to only do that after the release, otherwise people might think it's already released, hmm. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d1f0001f..f3b70b9eb 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. -## [0.11.0] - 2018-01-16 +## [0.11.0] - 2018-01-17 ### Added - The extension for tables now also renders to plain text (when using a `TextContentRenderer`), thanks @ahjaworski From c8ba11ae9c86e3caaf36121c544e1474c577b257 Mon Sep 17 00:00:00 2001 From: Bamboo Build User Date: Wed, 17 Jan 2018 00:09:42 +0000 Subject: [PATCH 283/815] [maven-release-plugin] prepare release commonmark-parent-0.11.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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 54a8ffc92..ef0367ac2 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 99a1b89c6..ba409335e 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index f05ff6f92..419984900 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index b4f7abed5..12c837fef 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index e46ff3a3c..07d9b32b9 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 875e81af1..963288ff3 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.10.1-SNAPSHOT + 0.11.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index ed1026538..920ea0f66 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 8162060df..dee9c988f 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 5c2d75172..f1025e7e8 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark diff --git a/pom.xml b/pom.xml index cba60af09..655f87c49 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.10.1-SNAPSHOT + 0.11.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -82,42 +82,42 @@ com.atlassian.commonmark commonmark - 0.10.1-SNAPSHOT + 0.11.0 com.atlassian.commonmark commonmark-ext-autolink - 0.10.1-SNAPSHOT + 0.11.0 com.atlassian.commonmark commonmark-ext-ins - 0.10.1-SNAPSHOT + 0.11.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.10.1-SNAPSHOT + 0.11.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.10.1-SNAPSHOT + 0.11.0 com.atlassian.commonmark commonmark-ext-heading-anchor - 0.10.1-SNAPSHOT + 0.11.0 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.10.1-SNAPSHOT + 0.11.0 com.atlassian.commonmark commonmark-test-util - 0.10.1-SNAPSHOT + 0.11.0 @@ -190,7 +190,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.11.0 From 43aa0e2903d085086c3f70481d12d6eb0e3debbc Mon Sep 17 00:00:00 2001 From: Bamboo Build User Date: Wed, 17 Jan 2018 00:09:45 +0000 Subject: [PATCH 284/815] [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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index ef0367ac2..c590ab7b7 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index ba409335e..bde6bae15 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 419984900..2e8e687fe 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 12c837fef..954256dfb 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 07d9b32b9..b40673e38 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 963288ff3..c5a82c5d5 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.11.0 + 0.11.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 920ea0f66..57dfcb8c8 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index dee9c988f..a3bc7cb2b 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index f1025e7e8..528bfc820 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 655f87c49..4ade086aa 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.0 + 0.11.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -82,42 +82,42 @@ com.atlassian.commonmark commonmark - 0.11.0 + 0.11.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.11.0 + 0.11.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-ins - 0.11.0 + 0.11.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.11.0 + 0.11.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.11.0 + 0.11.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-heading-anchor - 0.11.0 + 0.11.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.11.0 + 0.11.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.11.0 + 0.11.1-SNAPSHOT @@ -190,7 +190,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.11.0 + HEAD From 0dd689f8378fd21d3e4b2fab5a311d24668776ee Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 18 Jan 2018 10:34:11 +1100 Subject: [PATCH 285/815] Bump version in README and explicitly mention Java 9 support --- README.md | 4 ++-- commonmark-android-test/README.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b5f77b451..0e7a83b75 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ 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 higher. It should work on Java 7 +The library is supported on Java 8 and Java 9. 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 15, see the [commonmark-android-test](commonmark-android-test) directory. @@ -33,7 +33,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.10.0 + 0.11.0 ``` diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 6d498af5a..84dcffb92 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,14 +28,14 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.10.0 +version.maven=0.11.0 # Version number of autolink in maven central (not bundled with extension jar). -version.maven_autolink=0.7.0 +version.maven_autolink=0.8.0 # Version number of commonmark and extensions in project. -version.snapshot=0.10.0-SNAPSHOT +version.snapshot=0.11.1-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). -version.snapshot_autolink=0.7.0 +version.snapshot_autolink=0.8.0 ``` If you're going to test on device with Android 15 then you can skip downloading emulator. From 9e8d627719807ea6e32ce2609ddcdee0c9beca23 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 18 Jan 2018 10:43:57 +1100 Subject: [PATCH 286/815] README: Add note about Java 9 module names --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 0e7a83b75..5c079437e 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,9 @@ Coordinates for core library (see all on [Maven Central]): ``` +The module names to use in Java 9 are `org.commonmark`, +`org.commonmark.ext.autolink`, etc, corresponding to package names. + 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. From 6c6228bd56d061a1312d9858b72b0ef12a9b7f4a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 8 Feb 2018 15:01:21 +1100 Subject: [PATCH 287/815] Add example for parseReader and mention BOM (#121) --- .../java/org/commonmark/parser/Parser.java | 25 +++++++++++++------ .../org/commonmark/test/UsageExampleTest.java | 15 +++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index b51f10238..349bdbec0 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -1,11 +1,5 @@ package org.commonmark.parser; -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - import org.commonmark.Extension; import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserImpl; @@ -13,6 +7,12 @@ 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.Set; + /** * Parses input text to a tree of nodes. @@ -52,7 +52,7 @@ public static Builder builder() { /** * Parse the specified input text into a tree of nodes. *

    - * Note that this method is thread-safe (a new parser state is used for each invocation). + * This method is thread-safe (a new parser state is used for each invocation). * * @param input the text to parse * @return the root node @@ -66,8 +66,17 @@ public Node parse(String input) { /** * Parse the specified reader into a tree of nodes. The caller is responsible for closing the reader. + *

    
    +     * Parser parser = Parser.builder().build();
    +     * try (InputStreamReader reader = new InputStreamReader(new FileInputStream("file.md"), StandardCharsets.UTF_8)) {
    +     *     Node document = parser.parseReader(reader);
    +     *     // ...
    +     * }
    +     * 
    + * Note that if you have a file with a byte order mark (BOM), you need to skip it before handing the reader to this + * library. There's existing classes that do that, e.g. see {@code BOMInputStream} in Commons IO. *

    - * Note that this method is thread-safe (a new parser state is used for each invocation). + * This method is thread-safe (a new parser state is used for each invocation). * * @param input the reader to parse * @return the root node diff --git a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java index 6109c31cf..9ff646630 100644 --- a/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java +++ b/commonmark/src/test/java/org/commonmark/test/UsageExampleTest.java @@ -4,8 +4,13 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; +import org.junit.Ignore; import org.junit.Test; +import java.io.FileInputStream; +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; @@ -22,6 +27,16 @@ public void parseAndRender() { assertEquals("

    This is Sparta

    \n", renderer.render(document)); } + @Test + @Ignore + public void parseReaderRender() throws IOException { + Parser parser = Parser.builder().build(); + try (InputStreamReader reader = new InputStreamReader(new FileInputStream("file.md"), StandardCharsets.UTF_8)) { + Node document = parser.parseReader(reader); + // ... + } + } + @Test public void visitor() { Parser parser = Parser.builder().build(); From b577212c7ef8380193e2c4d772125e731ad99f72 Mon Sep 17 00:00:00 2001 From: Justin Leider Date: Wed, 21 Feb 2018 17:37:38 -0500 Subject: [PATCH 288/815] Update delimiter row to GFM spec The GFM spec states that any delimiter row is valid if it contains at least one `-` and no other characters except for whitespace and `:`. See: https://github.github.com/gfm/#tables-extension- --- .../gfm/tables/internal/TableBlockParser.java | 2 +- .../commonmark/ext/gfm/tables/TablesTest.java | 71 ++++++++++++++++++- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index a5df15456..70ed0fa7c 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 @@ -12,7 +12,7 @@ public class TableBlockParser extends AbstractBlockParser { - private static String COL = "\\s*:?-{3,}:?\\s*"; + private static String COL = "\\s*:?-{1,}:?\\s*"; private static Pattern TABLE_HEADER_SEPARATOR = Pattern.compile( // For single column, require at least one pipe, otherwise it's ambiguous with setext headers "\\|" + COL + "\\|?\\s*" + "|" + 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 3d6434fdd..1e47300cd 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -30,9 +30,26 @@ public void mustHaveHeaderAndSeparator() { } @Test - public void separatorMustBeThreeOrMore() { - assertRendering("Abc|Def\n-|-", "

    Abc|Def\n-|-

    \n"); - assertRendering("Abc|Def\n--|--", "

    Abc|Def\n--|--

    \n"); + public void separatorMustBeOneOrMore() { + assertRendering("Abc|Def\n-|-", "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    \n"); + assertRendering("Abc|Def\n--|--", "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    \n"); + } + + @Test + public void separatorMustNotContainInvalidChars() { + assertRendering("Abc|Def\n |-a-|---", "

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

    \n"); + assertRendering("Abc|Def\n |:--a|---", "

    Abc|Def\n|:--a|---

    \n"); + assertRendering("Abc|Def\n |:--a--:|---", "

    Abc|Def\n|:--a--:|---

    \n"); } @Test @@ -191,6 +208,22 @@ public void escapedBackslash() { @Test public void alignLeft() { + assertRendering("Abc|Def\n:-|-\n1|2", "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering("Abc|Def\n:-|-\n1|2", "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); assertRendering("Abc|Def\n:---|---\n1|2", "\n" + "\n" + "\n" + @@ -203,6 +236,22 @@ public void alignLeft() { @Test public void alignRight() { + assertRendering("Abc|Def\n-:|-\n1|2", "
    AbcDef
    \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering("Abc|Def\n--:|--\n1|2", "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); assertRendering("Abc|Def\n---:|---\n1|2", "\n" + "\n" + "\n" + @@ -215,6 +264,22 @@ public void alignRight() { @Test public void alignCenter() { + assertRendering("Abc|Def\n:-:|-\n1|2", "
    AbcDef
    \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); + assertRendering("Abc|Def\n:--:|--\n1|2", "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AbcDef
    12
    \n"); assertRendering("Abc|Def\n:---:|---\n1|2", "\n" + "\n" + "\n" + From 01d8cc6d0dc1a85d3daca5de210e3fd179452052 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 26 Mar 2018 14:43:41 +1100 Subject: [PATCH 289/815] Add regression tests from cmark and commonmark.js There were two failing tests, related to: * Backslash in link destinations (see commonmark/CommonMark#493) * Tabs in ATX/Setext headers Change the implementation to match the reference implementation. --- CHANGELOG.md | 7 ++ .../android/test/AndroidSupportTest.java | 4 +- .../integration/BoundsIntegrationTest.java | 9 +- .../integration/PegDownBenchmark.java | 7 +- .../integration/SpecIntegrationTest.java | 4 +- .../org/commonmark/testutil/SpecTestCase.java | 12 +- .../commonmark/testutil/TestResources.java | 37 +++++++ .../SpecExample.java => example/Example.java} | 10 +- .../ExampleReader.java} | 57 ++++------ .../src/main/resources/README.md | 7 ++ .../src/main/resources/cmark-regression.txt | 95 ++++++++++++++++ .../resources/commonmark.js-regression.txt | 104 ++++++++++++++++++ .../commonmark/internal/HeadingParser.java | 7 +- .../commonmark/internal/InlineParserImpl.java | 12 +- .../org/commonmark/test/HtmlRendererTest.java | 4 +- .../java/org/commonmark/test/ParserTest.java | 8 +- .../org/commonmark/test/RegressionTest.java | 52 +++++++++ .../org/commonmark/test/SpecBenchmark.java | 9 +- .../org/commonmark/test/SpecCoreTest.java | 4 +- .../org/commonmark/test/SpecialInputTest.java | 5 +- etc/update-spec.sh | 6 +- 21 files changed, 377 insertions(+), 83 deletions(-) create mode 100644 commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java rename commonmark-test-util/src/main/java/org/commonmark/testutil/{spec/SpecExample.java => example/Example.java} (57%) rename commonmark-test-util/src/main/java/org/commonmark/testutil/{spec/SpecReader.java => example/ExampleReader.java} (61%) create mode 100644 commonmark-test-util/src/main/resources/README.md create mode 100644 commonmark-test-util/src/main/resources/cmark-regression.txt create mode 100644 commonmark-test-util/src/main/resources/commonmark.js-regression.txt create mode 100644 commonmark/src/test/java/org/commonmark/test/RegressionTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f3b70b9eb..92ddbac52 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 +### Changed +- Parse backslash followed by unescapable character the same way as + the reference implementations. +### Fixed +- Fix tab handling in ATX and Setext headings. + ## [0.11.0] - 2018-01-17 ### Added - The extension for tables now also renders to plain text diff --git a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java index ecb9fcd86..26fd2c360 100644 --- a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java +++ b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java @@ -10,7 +10,7 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.testutil.spec.SpecReader; +import org.commonmark.testutil.TestResources; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,7 +30,7 @@ public class AndroidSupportTest { @Before public void setUp() throws Exception { - spec = SpecReader.readSpec(); + spec = TestResources.readAsString(TestResources.getSpec()); } @Test 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 981da1540..8ee15164a 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 @@ -2,8 +2,9 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; -import org.commonmark.testutil.spec.SpecExample; -import org.commonmark.testutil.spec.SpecReader; +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; @@ -29,9 +30,9 @@ public BoundsIntegrationTest(String input) { @Parameterized.Parameters(name = "{0}") public static List data() { - List examples = SpecReader.readExamples(); + List examples = ExampleReader.readExamples(TestResources.getSpec()); List data = new ArrayList<>(); - for (SpecExample example : examples) { + for (Example example : examples) { data.add(new Object[]{example.getSource()}); } return data; 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 2056868ec..7b61242f4 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/PegDownBenchmark.java @@ -1,6 +1,7 @@ package org.commonmark.integration; -import org.commonmark.testutil.spec.SpecReader; +import org.commonmark.testutil.TestResources; +import org.commonmark.testutil.example.ExampleReader; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; @@ -17,8 +18,8 @@ @State(Scope.Benchmark) public class PegDownBenchmark { - private static final String SPEC = SpecReader.readSpec(); - private static final List SPEC_EXAMPLES = SpecReader.readExamplesAsString(); + private static final String SPEC = TestResources.readAsString(TestResources.getSpec()); + private static final List SPEC_EXAMPLES = ExampleReader.readExampleSources(TestResources.getSpec()); private static final PegDownProcessor PROCESSOR = new PegDownProcessor(Extensions.FENCED_CODE_BLOCKS); public static void main(String[] args) throws Exception { 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 77dd26608..6462f4094 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 @@ -8,7 +8,7 @@ import org.commonmark.ext.front.matter.YamlFrontMatterExtension; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; -import org.commonmark.testutil.spec.SpecExample; +import org.commonmark.testutil.example.Example; import org.commonmark.testutil.SpecTestCase; import org.junit.Test; @@ -30,7 +30,7 @@ public class SpecIntegrationTest extends SpecTestCase { private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).percentEncodeUrls(true).build(); private static final Map OVERRIDDEN_EXAMPLES = getOverriddenExamples(); - public SpecIntegrationTest(SpecExample example) { + public SpecIntegrationTest(Example example) { super(example); } 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 a87d4dad4..1c35b7c28 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/SpecTestCase.java @@ -1,7 +1,7 @@ package org.commonmark.testutil; -import org.commonmark.testutil.spec.SpecExample; -import org.commonmark.testutil.spec.SpecReader; +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; @@ -13,17 +13,17 @@ @RunWith(Parameterized.class) public abstract class SpecTestCase extends RenderingTestCase { - protected final SpecExample example; + protected final Example example; - public SpecTestCase(SpecExample example) { + public SpecTestCase(Example example) { this.example = example; } @Parameters(name = "{0}") public static List data() { - List examples = SpecReader.readExamples(); + List examples = ExampleReader.readExamples(TestResources.getSpec()); List data = new ArrayList<>(); - for (SpecExample example : examples) { + for (Example example : examples) { data.add(new Object[]{example}); } return data; 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 new file mode 100644 index 000000000..8f6f5c071 --- /dev/null +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/TestResources.java @@ -0,0 +1,37 @@ +package org.commonmark.testutil; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; + +public class TestResources { + + public static URL getSpec() { + return TestResources.class.getResource("/spec.txt"); + } + + public static List getRegressions() { + return Arrays.asList( + TestResources.class.getResource("/cmark-regression.txt"), + TestResources.class.getResource("/commonmark.js-regression.txt") + ); + } + + public static String readAsString(URL url) { + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), Charset.forName("UTF-8")))) { + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + sb.append("\n"); + } + return sb.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecExample.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java similarity index 57% rename from commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecExample.java rename to commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java index decade455..8dc2c1e25 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecExample.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/Example.java @@ -1,13 +1,15 @@ -package org.commonmark.testutil.spec; +package org.commonmark.testutil.example; -public class SpecExample { +public class Example { + private final String filename; private final String section; private final int exampleNumber; private final String source; private final String html; - public SpecExample(String section, int exampleNumber, String source, String html) { + public Example(String filename, String section, int exampleNumber, String source, String html) { + this.filename = filename; this.section = section; this.exampleNumber = exampleNumber; this.source = source; @@ -24,6 +26,6 @@ public String getHtml() { @Override public String toString() { - return "Section \"" + section + "\" example " + exampleNumber; + return "File \"" + filename + "\" section \"" + section + "\" example " + exampleNumber; } } diff --git a/commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecReader.java b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java similarity index 61% rename from commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecReader.java rename to commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java index 4db1aa491..ad288f2fb 100644 --- a/commonmark-test-util/src/main/java/org/commonmark/testutil/spec/SpecReader.java +++ b/commonmark-test-util/src/main/java/org/commonmark/testutil/example/ExampleReader.java @@ -1,20 +1,22 @@ -package org.commonmark.testutil.spec; +package org.commonmark.testutil.example; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; +import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class SpecReader { +/** + * Reader for files containing examples of CommonMark source and the expected HTML rendering (e.g. spec.txt). + */ +public class ExampleReader { private static final Pattern SECTION_PATTERN = Pattern.compile("#{1,6} *(.*)"); private final InputStream inputStream; + private final String filename; private State state = State.BEFORE; private String section; @@ -22,52 +24,31 @@ public class SpecReader { private StringBuilder html; private int exampleNumber = 0; - private List examples = new ArrayList<>(); + private List examples = new ArrayList<>(); - private SpecReader(InputStream stream) { + private ExampleReader(InputStream stream, String filename) { this.inputStream = stream; + this.filename = filename; } - public static List readExamples() { - try (InputStream stream = getSpecInputStream()) { - return new SpecReader(stream).read(); + public static List readExamples(URL url) { + try (InputStream stream = url.openStream()) { + return new ExampleReader(stream, new File(url.getPath()).getName()).read(); } catch (IOException e) { throw new RuntimeException(e); } } - public static List readExamplesAsString() { - List examples = SpecReader.readExamples(); + public static List readExampleSources(URL url) { + List examples = ExampleReader.readExamples(url); List result = new ArrayList<>(); - for (SpecExample example : examples) { + for (Example example : examples) { result.add(example.getSource()); } return result; } - public static String readSpec() { - StringBuilder sb = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(getSpecInputStream(), Charset.forName("UTF-8")))) { - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - sb.append("\n"); - } - return sb.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static InputStream getSpecInputStream() { - InputStream stream = SpecReader.class.getResourceAsStream("/spec.txt"); - if (stream == null) { - throw new IllegalStateException("Could not load spec.txt classpath resource"); - } - return stream; - } - - private List read() throws IOException { + private List read() throws IOException { resetContents(); try (BufferedReader reader = new BufferedReader( @@ -106,7 +87,7 @@ private void processLine(String line) { case HTML: if (line.equals("````````````````````````````````")) { state = State.BEFORE; - examples.add(new SpecExample(section, exampleNumber, + examples.add(new Example(filename, section, exampleNumber, source.toString(), html.toString())); resetContents(); } else { diff --git a/commonmark-test-util/src/main/resources/README.md b/commonmark-test-util/src/main/resources/README.md new file mode 100644 index 000000000..a749c59b5 --- /dev/null +++ b/commonmark-test-util/src/main/resources/README.md @@ -0,0 +1,7 @@ +These files are copied from the CommonMark repositories, namely: + +https://github.com/commonmark/CommonMark/blob/master/spec.txt +https://github.com/commonmark/cmark/blob/master/test/regression.txt +https://github.com/commonmark/commonmark.js/blob/master/test/regression.txt + +They are licensed as stated in those repositories. diff --git a/commonmark-test-util/src/main/resources/cmark-regression.txt b/commonmark-test-util/src/main/resources/cmark-regression.txt new file mode 100644 index 000000000..2984a3bef --- /dev/null +++ b/commonmark-test-util/src/main/resources/cmark-regression.txt @@ -0,0 +1,95 @@ +### Regression tests + +Issue #113: EOL character weirdness on Windows +(Important: first line ends with CR + CR + LF) + +```````````````````````````````` example +line1 + +line2 +. +

    line1

    +

    line2

    +```````````````````````````````` + +Issue #114: cmark skipping first character in line +(Important: the blank lines around "Repeatedly" contain a tab.) + +```````````````````````````````` example +By taking it apart + +- alternative solutions +→ +Repeatedly solving +→ +- how techniques +. +

    By taking it apart

    +
      +
    • alternative solutions
    • +
    +

    Repeatedly solving

    +
      +
    • how techniques
    • +
    +```````````````````````````````` + +Issue jgm/CommonMark#430: h2..h6 not recognized as block tags. + +```````````````````````````````` example +

    lorem

    + +

    lorem

    + +

    lorem

    + +

    lorem

    + +
    lorem
    + +
    lorem
    +. +

    lorem

    +

    lorem

    +

    lorem

    +

    lorem

    +
    lorem
    +
    lorem
    +```````````````````````````````` + +Issue jgm/commonmark.js#109 - tabs after setext header line + + +```````````````````````````````` example +hi +--→ +. +

    hi

    +```````````````````````````````` + +Issue #177 - incorrect emphasis parsing + +```````````````````````````````` example +a***b* c* +. +

    a*b c

    +```````````````````````````````` + +Issue #193 - unescaped left angle brackets in link destination + +```````````````````````````````` example +[a] + +[a]: +. +

    a

    +```````````````````````````````` + +Issue #192 - escaped spaces in link destination + + +```````````````````````````````` example +[a](te\ st) +. +

    [a](te\ st)

    +```````````````````````````````` diff --git a/commonmark-test-util/src/main/resources/commonmark.js-regression.txt b/commonmark-test-util/src/main/resources/commonmark.js-regression.txt new file mode 100644 index 000000000..7300952fe --- /dev/null +++ b/commonmark-test-util/src/main/resources/commonmark.js-regression.txt @@ -0,0 +1,104 @@ +# Regression tests + +Eating a character after a partially consumed tab. + +```````````````````````````````` example +* foo +→bar +. +
      +
    • foo +bar
    • +
    +```````````````````````````````` + +Type 7 HTML block followed by whitespace (#98). + +```````````````````````````````` example + +x +. + +x +```````````````````````````````` + +h2..h6 raw HTML blocks (jgm/CommonMark#430). + +```````````````````````````````` example +

    lorem

    + +

    lorem

    + +

    lorem

    + +

    lorem

    + +
    lorem
    + +
    lorem
    +. +

    lorem

    +

    lorem

    +

    lorem

    +

    lorem

    +
    lorem
    +
    lorem
    +```````````````````````````````` + +Issue #109 - tabs after setext header line + + +```````````````````````````````` example +hi +--→ +. +

    hi

    +```````````````````````````````` + +Issue #108 - Chinese punctuation not recognized + +```````````````````````````````` example +**。**话 +. +

    **。**话

    +```````````````````````````````` + +Issue jgm/cmark#177 - incorrect emphasis parsing + +```````````````````````````````` example +a***b* c* +. +

    a*b c

    +```````````````````````````````` + +Issue jgm/CommonMark#468 - backslash at end of link definition + + +```````````````````````````````` example +[\]: test +. +

    []: test

    +```````````````````````````````` + +Issue jgm/commonmark.js#121 - punctuation set different + +```````````````````````````````` example +^_test_ +. +

    ^test

    +```````````````````````````````` + +Issue #116 - tabs before and after ATX closing heading +```````````````````````````````` example +# foo→#→ +. +

    foo

    +```````````````````````````````` + +commonmark/CommonMark#493 - escaped space not allowed in link +destination. +```````````````````````````````` example +[link](a\ b) +. +

    [link](a\ b)

    +```````````````````````````````` diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 3c26b1cd4..7e4f82c05 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -11,8 +11,8 @@ public class HeadingParser extends AbstractBlockParser { private static Pattern ATX_HEADING = Pattern.compile("^#{1,6}(?:[ \t]+|$)"); - private static Pattern ATX_TRAILING = Pattern.compile("(^| ) *#+ *$"); - private static Pattern SETEXT_HEADING = Pattern.compile("^(?:=+|-+) *$"); + private static Pattern ATX_TRAILING = Pattern.compile("(^|[ \t]+)#+[ \t]*$"); + private static Pattern SETEXT_HEADING = Pattern.compile("^(?:=+|-+)[ \t]*$"); private final Heading block = new Heading(); private final String content; @@ -54,7 +54,8 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar int newOffset = nextNonSpace + matcher.group(0).length(); int level = matcher.group(0).trim().length(); // number of #s // remove trailing ###s: - String content = ATX_TRAILING.matcher(line.subSequence(newOffset, line.length())).replaceAll(""); + CharSequence afterLeading = line.subSequence(newOffset, line.length()); + String content = ATX_TRAILING.matcher(afterLeading).replaceAll(""); return BlockStart.of(new HeadingParser(level, content)) .atIndex(line.length()); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 45da99d90..78cbacd82 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -648,13 +648,13 @@ private void parseLinkDestinationWithBalancedParens() { case '\0': return; case '\\': - // go to character after backslash - index++; - // stop if that took us to the end of input - if (peek() == '\0') { - return; + // check if we have an escapable character + if (index + 1 < input.length() && ESCAPABLE.matcher(input.substring(index + 1, index + 2)).matches()) { + // skip over the escaped character (after switch) + index++; + break; } - // otherwise, we'll skip over character after backslash + // otherwise, we treat this as a literal backslash break; case '(': parens++; diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index d393ee12a..50f321efe 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -7,7 +7,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; -import org.commonmark.testutil.spec.SpecReader; +import org.commonmark.testutil.TestResources; import org.junit.Test; import java.util.*; @@ -210,7 +210,7 @@ public void imageAltTextWithEntities() { @Test public void threading() throws Exception { Parser parser = Parser.builder().build(); - String spec = SpecReader.readSpec(); + String spec = TestResources.readAsString(TestResources.getSpec()); final Node document = parser.parse(spec); final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build(); diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index b5f487312..27b85f9a9 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -7,7 +7,7 @@ import org.commonmark.parser.Parser; import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.testutil.spec.SpecReader; +import org.commonmark.testutil.TestResources; import org.junit.Test; import java.io.IOException; @@ -32,13 +32,13 @@ public class ParserTest { public void ioReaderTest() throws IOException { Parser parser = Parser.builder().build(); - InputStream input1 = SpecReader.getSpecInputStream(); + InputStream input1 = TestResources.getSpec().openStream(); Node document1; try (InputStreamReader reader = new InputStreamReader(input1)) { document1 = parser.parseReader(reader); } - String spec = SpecReader.readSpec(); + String spec = TestResources.readAsString(TestResources.getSpec()); Node document2 = parser.parse(spec); HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build(); @@ -124,7 +124,7 @@ public InlineParser create(InlineParserContext inlineParserContext) { @Test public void threading() throws Exception { final Parser parser = Parser.builder().build(); - final String spec = SpecReader.readSpec(); + final String spec = TestResources.readAsString(TestResources.getSpec()); HtmlRenderer renderer = HtmlRenderer.builder().build(); String expectedRendering = renderer.render(parser.parse(spec)); diff --git a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java new file mode 100644 index 000000000..5d49c2abd --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java @@ -0,0 +1,52 @@ +package org.commonmark.test; + +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; +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 java.net.URL; +import java.util.ArrayList; +import java.util.List; + +@RunWith(Parameterized.class) +public class RegressionTest extends RenderingTestCase { + + private static final Parser PARSER = Parser.builder().build(); + // The spec says URL-escaping is optional, but the examples assume that it's enabled. + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); + + private final 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}); + } + } + return data; + } + + @Test + public void testHtmlRendering() { + assertRendering(example.getSource(), example.getHtml()); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index 9b10b0f34..75ce7ffcd 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -1,8 +1,9 @@ package org.commonmark.test; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.parser.Parser; -import org.commonmark.testutil.spec.SpecReader; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.TestResources; +import org.commonmark.testutil.example.ExampleReader; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; @@ -17,8 +18,8 @@ @State(Scope.Benchmark) public class SpecBenchmark { - private static final String SPEC = SpecReader.readSpec(); - private static final List SPEC_EXAMPLES = SpecReader.readExamplesAsString(); + private static final String SPEC = TestResources.readAsString(TestResources.getSpec()); + private static final List SPEC_EXAMPLES = ExampleReader.readExampleSources(TestResources.getSpec()); private static final Parser PARSER = Parser.builder().build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index 2a5ffe510..4e416264f 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -6,7 +6,7 @@ import org.commonmark.node.Text; import org.commonmark.parser.Parser; import org.commonmark.testutil.SpecTestCase; -import org.commonmark.testutil.spec.SpecExample; +import org.commonmark.testutil.example.Example; import org.junit.Test; import static org.junit.Assert.fail; @@ -17,7 +17,7 @@ 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(SpecExample example) { + public SpecCoreTest(Example example) { super(example); } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 930ecb023..1550d3197 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -110,9 +110,10 @@ public void linkLabelLength() { @Test public void linkDestinationEscaping() { + // Backslash escapes `)` assertRendering("[foo](\\))", "

    foo

    \n"); - assertRendering("[foo](\\ )", "

    foo

    \n"); - + // ` ` is not escapable, so the backslash is a literal backslash and there's an optional space at the end + assertRendering("[foo](\\ )", "

    foo

    \n"); // Backslash escapes `>`, so it's not a `(<...>)` link, but a `(...)` link instead assertRendering("[foo](<\\>)", "

    foo

    \n"); // Backslash is a literal, so valid diff --git a/etc/update-spec.sh b/etc/update-spec.sh index 8c9021a86..e5276f4b9 100755 --- a/etc/update-spec.sh +++ b/etc/update-spec.sh @@ -6,4 +6,8 @@ if [ "$#" -ne 1 ]; then fi version=$1 -curl -L "https://raw.githubusercontent.com/jgm/CommonMark/$version/spec.txt" -o commonmark-test-util/src/main/resources/spec.txt +curl -L "https://raw.githubusercontent.com/commonmark/CommonMark/$version/spec.txt" -o commonmark-test-util/src/main/resources/spec.txt + +echo "Check cmark and commonmark.js regression.txt:" +echo "https://github.com/commonmark/cmark/blob/master/test/regression.txt" +echo "https://github.com/commonmark/commonmark.js/blob/master/test/regression.txt" From 6101e8a6cf43bcb6dde29ca3ee7be1e07e2c5ed2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 26 Apr 2018 15:57:21 +1000 Subject: [PATCH 290/815] Don't use default charset in ParserTest.ioReaderTest Otherwise the test fails depending on what the default charset is. Fixes #127. --- commonmark/src/test/java/org/commonmark/test/ParserTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 27b85f9a9..e058de378 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -34,7 +35,7 @@ public void ioReaderTest() throws IOException { InputStream input1 = TestResources.getSpec().openStream(); Node document1; - try (InputStreamReader reader = new InputStreamReader(input1)) { + try (InputStreamReader reader = new InputStreamReader(input1, Charset.forName("UTF-8"))) { document1 = parser.parseReader(reader); } From f50554b7f291a9331991b9accfbdee0b369f5102 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 17 Jul 2018 10:40:50 +1000 Subject: [PATCH 291/815] Add "see also" section --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5c079437e..5b6309dff 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,12 @@ document start here Use class `YamlFrontMatterExtension` in artifact `commonmark-ext-yaml-front-matter`. To fetch metadata, use `YamlFrontMatterVisitor`. +See also +-------- + +* [Markwon](https://github.com/noties/Markwon): Android library for rendering markdown as system-native Spannables +* [flexmark-java](https://github.com/vsch/flexmark-java): Fork that added support for a lot more syntax and flexibility + Contributing ------------ From 4b5c3427e60b04026d0ec0eb0ebb7e32356ad55d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 13 Aug 2018 12:41:17 +1000 Subject: [PATCH 292/815] Replace regex matching in FencedCodeBlockParser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The use of Matcher in there showed up showed up in a profiling session on Android. Using Matcher also means doing more short-lived allocations. Replace it with some simple looping code. This gives a nice speedup for the whole spec benchmark (which is a long document with quite a few fenced code blocks). Before: Benchmark Mode Cnt Score Error Units SpecBenchmark.wholeSpec thrpt 200 104.297 ± 1.124 ops/s After: SpecBenchmark.wholeSpec thrpt 200 124.558 ± 0.485 ops/s --- .../internal/FencedCodeBlockParser.java | 98 +++++++++++++++---- .../commonmark/test/FencedCodeBlockTest.java | 55 +++++++++++ 2 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/FencedCodeBlockTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index aa9f73daa..76fe9c02c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -1,19 +1,14 @@ package org.commonmark.internal; +import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.FencedCodeBlock; import org.commonmark.parser.block.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import static org.commonmark.internal.util.Escaping.unescapeString; public class FencedCodeBlockParser extends AbstractBlockParser { - private static final Pattern OPENING_FENCE = Pattern.compile("^`{3,}(?!.*`)|^~{3,}(?!.*~)"); - private static final Pattern CLOSING_FENCE = Pattern.compile("^(?:`{3,}|~{3,})(?= *$)"); - private final FencedCodeBlock block = new FencedCodeBlock(); private String firstLine; @@ -35,13 +30,8 @@ public BlockContinue tryContinue(ParserState state) { int nextNonSpace = state.getNextNonSpaceIndex(); int newIndex = state.getIndex(); CharSequence line = state.getLine(); - Matcher matcher = null; - boolean matches = (state.getIndent() <= 3 && - nextNonSpace < line.length() && - line.charAt(nextNonSpace) == block.getFenceChar() && - (matcher = CLOSING_FENCE.matcher(line.subSequence(nextNonSpace, line.length()))) - .find()); - if (matches && matcher.group(0).length() >= block.getFenceLength()) { + boolean closing = state.getIndent() < Parsing.CODE_BLOCK_INDENT && isClosing(line, nextNonSpace); + if (closing) { // closing fence - we're at end of line, so we can finalize now return BlockContinue.finished(); } else { @@ -76,18 +66,84 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { + int indent = state.getIndent(); + if (indent >= Parsing.CODE_BLOCK_INDENT) { + return BlockStart.none(); + } + int nextNonSpace = state.getNextNonSpaceIndex(); - CharSequence line = state.getLine(); - Matcher matcher; - if (state.getIndent() < 4 && (matcher = OPENING_FENCE.matcher(line.subSequence(nextNonSpace, line.length()))).find()) { - int fenceLength = matcher.group(0).length(); - char fenceChar = matcher.group(0).charAt(0); - FencedCodeBlockParser blockParser = new FencedCodeBlockParser(fenceChar, fenceLength, state.getIndent()); - return BlockStart.of(blockParser).atIndex(nextNonSpace + fenceLength); + FencedCodeBlockParser blockParser = checkOpener(state.getLine(), nextNonSpace, indent); + if (blockParser != null) { + return BlockStart.of(blockParser).atIndex(nextNonSpace + blockParser.block.getFenceLength()); } else { return BlockStart.none(); } } } -} + // spec: A code fence is a sequence of at least three consecutive backtick characters (`) or tildes (~). (Tildes and + // backticks cannot be mixed.) + private static FencedCodeBlockParser checkOpener(CharSequence line, int index, int indent) { + int backticks = 0; + int tildes = 0; + loop: + for (int i = index; i < line.length(); i++) { + switch (line.charAt(i)) { + case '`': + backticks++; + break; + case '~': + tildes++; + break; + default: + break loop; + } + } + if (backticks >= 3 && tildes == 0) { + for (int i = index + backticks; i < line.length(); i++) { + // spec: The info string may not contain any backtick characters. + if (line.charAt(i) == '`') { + return null; + } + } + return new FencedCodeBlockParser('`', backticks, indent); + } else if (tildes >= 3 && backticks == 0) { + for (int i = index + tildes; i < line.length(); i++) { + // This follows commonmark.js but the spec is unclear about this: + // https://github.com/commonmark/CommonMark/issues/119 + if (line.charAt(i) == '~') { + return null; + } + } + return new FencedCodeBlockParser('~', tildes, indent); + } else { + return null; + } + } + + // 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(); + int fences = 0; + for (int i = index; i < line.length(); i++) { + if (line.charAt(i) == fenceChar) { + fences++; + } else { + break; + } + } + if (fences < fenceLength) { + return false; + } + // spec: The closing code fence [...] may be followed only by spaces, which are ignored. + for (int i = index + fences; i < line.length(); i++) { + if (line.charAt(i) != ' ') { + return false; + } + } + return true; + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockTest.java b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockTest.java new file mode 100644 index 000000000..145c4a158 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockTest.java @@ -0,0 +1,55 @@ +package org.commonmark.test; + +import org.commonmark.node.FencedCodeBlock; +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class FencedCodeBlockTest extends RenderingTestCase { + + private static final Parser PARSER = Parser.builder().build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); + + @Test + 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()); + } + + @Test + public void backtickInfoDoesntAllowBacktick() { + assertRendering("```info ` test\ncode\n```", + "

    ```info ` test\ncode

    \n
    \n"); + // Note, it's unclear in the spec whether a ~~~ code block can contain ` in info or not, see: + // https://github.com/commonmark/CommonMark/issues/119 + } + + @Test + public void backtickAndTildeCantBeMixed() { + assertRendering("``~`\ncode\n``~`", + "

    ~` code~`

    \n"); + } + + @Test + public void closingCanHaveSpacesAfter() { + assertRendering("```\ncode\n``` ", + "
    code\n
    \n"); + } + + @Test + public void closingCanNotHaveNonSpaces() { + assertRendering("```\ncode\n``` a", + "
    code\n``` a\n
    \n"); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} From da3bde87137a1c4401515ffc470a540584ec5546 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 15 Aug 2018 12:25:09 +1000 Subject: [PATCH 293/815] Optimize heading block parsing by replacing regex matching The speedup should be mostly in the common case where there's no match, in that case the new code just checks the first character and then aborts whereas the old code would try a full regex match. --- .../commonmark/internal/HeadingParser.java | 104 ++++++++++++------ .../org/commonmark/internal/util/Parsing.java | 44 ++++++++ .../commonmark/test/HeadingParserTest.java | 53 +++++++++ 3 files changed, 170 insertions(+), 31 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/HeadingParserTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 7e4f82c05..2b72ba236 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -1,19 +1,13 @@ package org.commonmark.internal; +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.block.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class HeadingParser extends AbstractBlockParser { - private static Pattern ATX_HEADING = Pattern.compile("^#{1,6}(?:[ \t]+|$)"); - private static Pattern ATX_TRAILING = Pattern.compile("(^|[ \t]+)#+[ \t]*$"); - private static Pattern SETEXT_HEADING = Pattern.compile("^(?:=+|-+)[ \t]*$"); - private final Heading block = new Heading(); private final String content; @@ -42,35 +36,83 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - if (state.getIndent() >= 4) { + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT) { return BlockStart.none(); } + CharSequence line = state.getLine(); int nextNonSpace = state.getNextNonSpaceIndex(); - CharSequence paragraph = matchedBlockParser.getParagraphContent(); - Matcher matcher; - if ((matcher = ATX_HEADING.matcher(line.subSequence(nextNonSpace, line.length()))).find()) { - // ATX heading - int newOffset = nextNonSpace + matcher.group(0).length(); - int level = matcher.group(0).trim().length(); // number of #s - // remove trailing ###s: - CharSequence afterLeading = line.subSequence(newOffset, line.length()); - String content = ATX_TRAILING.matcher(afterLeading).replaceAll(""); - return BlockStart.of(new HeadingParser(level, content)) - .atIndex(line.length()); - - } else if (paragraph != null && - ((matcher = SETEXT_HEADING.matcher(line.subSequence(nextNonSpace, line.length()))).find())) { - // setext heading line - - int level = matcher.group(0).charAt(0) == '=' ? 1 : 2; - String content = paragraph.toString(); - return BlockStart.of(new HeadingParser(level, content)) - .atIndex(line.length()) - .replaceActiveBlockParser(); - } else { - return BlockStart.none(); + HeadingParser atxHeading = getAtxHeading(line, nextNonSpace); + if (atxHeading != null) { + return BlockStart.of(atxHeading).atIndex(line.length()); + } + + int setextHeadingLevel = getSetextHeadingLevel(line, nextNonSpace); + if (setextHeadingLevel > 0) { + CharSequence paragraph = matchedBlockParser.getParagraphContent(); + if (paragraph != null) { + String content = paragraph.toString(); + return BlockStart.of(new HeadingParser(setextHeadingLevel, content)) + .atIndex(line.length()) + .replaceActiveBlockParser(); + } } + + return BlockStart.none(); + } + } + + // spec: An ATX heading consists of a string of characters, parsed as inline content, between an opening sequence of + // 1–6 unescaped # characters and an optional closing sequence of any number of unescaped # characters. The opening + // sequence of # characters must be followed by a space or by the end of line. The optional closing sequence of #s + // must be preceded by a space and may be followed by spaces only. + private static HeadingParser getAtxHeading(CharSequence line, int index) { + int level = Parsing.skip('#', line, index, line.length()) - index; + + if (level == 0 || level > 6) { + return null; } + + int start = index + level; + if (start >= line.length()) { + // End of line after markers is an empty heading + return new HeadingParser(level, ""); + } + + char next = line.charAt(start); + if (!(next == ' ' || next == '\t')) { + return null; + } + + int beforeSpace = Parsing.skipSpaceTabBackwards(line, line.length() - 1, start); + int beforeHash = Parsing.skipBackwards('#', line, beforeSpace, start); + int beforeTrailer = Parsing.skipSpaceTabBackwards(line, beforeHash, start); + if (beforeTrailer != beforeHash) { + return new HeadingParser(level, line.subSequence(start, beforeTrailer + 1).toString()); + } else { + return new HeadingParser(level, line.subSequence(start, beforeSpace + 1).toString()); + } + } + + // spec: A setext heading underline is a sequence of = characters or a sequence of - characters, with no more than + // 3 spaces indentation and any number of trailing spaces. + private static int getSetextHeadingLevel(CharSequence line, int index) { + switch (line.charAt(index)) { + case '=': + if (isSetextHeadingRest(line, index + 1, '=')) { + return 1; + } + case '-': + if (isSetextHeadingRest(line, index + 1, '-')) { + return 2; + } + } + return 0; + } + + 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()); + return afterSpace >= line.length(); } } 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 046ced1e1..b8ec87d79 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -85,6 +85,50 @@ public static CharSequence prepareLine(CharSequence 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) { for (int i = startIndex; i < s.length(); i++) { switch (s.charAt(i)) { diff --git a/commonmark/src/test/java/org/commonmark/test/HeadingParserTest.java b/commonmark/src/test/java/org/commonmark/test/HeadingParserTest.java new file mode 100644 index 000000000..a5b179a81 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/HeadingParserTest.java @@ -0,0 +1,53 @@ +package org.commonmark.test; + +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; +import org.junit.Test; + +public class HeadingParserTest extends RenderingTestCase { + + private static final Parser PARSER = Parser.builder().build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); + + @Test + public void atxHeadingStart() { + assertRendering("# test", "

    test

    \n"); + assertRendering("###### test", "
    test
    \n"); + assertRendering("####### test", "

    ####### test

    \n"); + assertRendering("#test", "

    #test

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

    \n"); + } + + @Test + public void atxHeadingTrailing() { + assertRendering("# test #", "

    test

    \n"); + assertRendering("# test ###", "

    test

    \n"); + assertRendering("# test # ", "

    test

    \n"); + assertRendering("# test ### ", "

    test

    \n"); + assertRendering("# test # #", "

    test #

    \n"); + assertRendering("# test#", "

    test#

    \n"); + } + + @Test + public void atxHeadingSurrogates() { + assertRendering("# \uD83D\uDE0A #", "

    \uD83D\uDE0A

    \n"); + } + + @Test + public void setextHeadingMarkers() { + assertRendering("test\n=", "

    test

    \n"); + assertRendering("test\n-", "

    test

    \n"); + assertRendering("test\n====", "

    test

    \n"); + assertRendering("test\n----", "

    test

    \n"); + assertRendering("test\n==== ", "

    test

    \n"); + assertRendering("test\n==== =", "

    test\n==== =

    \n"); + assertRendering("test\n=-=", "

    test\n=-=

    \n"); + assertRendering("test\n=a", "

    test\n=a

    \n"); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} From 7abda68d15d829104e969ebeed36b6d05cd94525 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 15 Aug 2018 12:36:21 +1000 Subject: [PATCH 294/815] Use new Parsing utils for FencedCodeBlockParser --- .../internal/FencedCodeBlockParser.java | 35 ++++++------------- .../org/commonmark/internal/util/Parsing.java | 9 +++++ ...st.java => FencedCodeBlockParserTest.java} | 2 +- 3 files changed, 20 insertions(+), 26 deletions(-) rename commonmark/src/test/java/org/commonmark/test/{FencedCodeBlockTest.java => FencedCodeBlockParserTest.java} (96%) diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index 76fe9c02c..80c2e0ab6 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -100,20 +100,16 @@ private static FencedCodeBlockParser checkOpener(CharSequence line, int index, i } } if (backticks >= 3 && tildes == 0) { - for (int i = index + backticks; i < line.length(); i++) { - // spec: The info string may not contain any backtick characters. - if (line.charAt(i) == '`') { - return null; - } + // spec: The info string may not contain any backtick characters. + if (Parsing.find('`', line, index + backticks) != -1) { + return null; } return new FencedCodeBlockParser('`', backticks, indent); } else if (tildes >= 3 && backticks == 0) { - for (int i = index + tildes; i < line.length(); i++) { - // This follows commonmark.js but the spec is unclear about this: - // https://github.com/commonmark/CommonMark/issues/119 - if (line.charAt(i) == '~') { - return null; - } + // This follows commonmark.js but the spec is unclear about this: + // https://github.com/commonmark/CommonMark/issues/119 + if (Parsing.find('~', line, index + tildes) != -1) { + return null; } return new FencedCodeBlockParser('~', tildes, indent); } else { @@ -127,23 +123,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 = 0; - for (int i = index; i < line.length(); i++) { - if (line.charAt(i) == fenceChar) { - fences++; - } else { - break; - } - } + int fences = Parsing.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. - for (int i = index + fences; i < line.length(); i++) { - if (line.charAt(i) != ' ') { - return false; - } - } - return true; + int after = Parsing.skipSpaceTab(line, index + fences, line.length()); + return after == line.length(); } } 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 b8ec87d79..f298e9158 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -24,6 +24,15 @@ public static int columnsToNextTabStop(int column) { return 4 - (column % 4); } + public static int find(char c, CharSequence s, int startIndex) { + for (int i = startIndex; i < s.length(); i++) { + if (s.charAt(i) == c) { + return i; + } + } + return -1; + } + public static int findLineBreak(CharSequence s, int startIndex) { for (int i = startIndex; i < s.length(); i++) { switch (s.charAt(i)) { diff --git a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockTest.java b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java similarity index 96% rename from commonmark/src/test/java/org/commonmark/test/FencedCodeBlockTest.java rename to commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java index 145c4a158..036af3d92 100644 --- a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockTest.java +++ b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java @@ -9,7 +9,7 @@ import static org.junit.Assert.assertEquals; -public class FencedCodeBlockTest extends RenderingTestCase { +public class FencedCodeBlockParserTest extends RenderingTestCase { private static final Parser PARSER = Parser.builder().build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); From ad397cb7941f8dba518e6b59a4ef58317d791feb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 15 Aug 2018 13:37:27 +1000 Subject: [PATCH 295/815] Replace regex matching in ThematicBreakParser --- .../internal/ThematicBreakParser.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java index 879d583c7..0758cc828 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java @@ -4,12 +4,8 @@ import org.commonmark.node.ThematicBreak; import org.commonmark.parser.block.*; -import java.util.regex.Pattern; - public class ThematicBreakParser extends AbstractBlockParser { - private static Pattern PATTERN = Pattern.compile("^(?:(?:\\*[ \t]*){3,}|(?:_[ \t]*){3,}|(?:-[ \t]*){3,})[ \t]*$"); - private final ThematicBreak block = new ThematicBreak(); @Override @@ -32,11 +28,42 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } int nextNonSpace = state.getNextNonSpaceIndex(); CharSequence line = state.getLine(); - if (PATTERN.matcher(line.subSequence(nextNonSpace, line.length())).matches()) { + if (isThematicBreak(line, nextNonSpace)) { return BlockStart.of(new ThematicBreakParser()).atIndex(line.length()); } else { return BlockStart.none(); } } } + + // spec: A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching -, _, or * + // characters, each followed optionally by any number of spaces, forms a thematic break. + private static boolean isThematicBreak(CharSequence line, int index) { + int dashes = 0; + int underscores = 0; + int asterisks = 0; + for (int i = index; i < line.length(); i++) { + switch (line.charAt(i)) { + case '-': + dashes++; + break; + case '_': + underscores++; + break; + case '*': + asterisks++; + break; + case ' ': + case '\t': + // Allowed, even between markers + break; + default: + return false; + } + } + + return ((dashes >= 3 && underscores == 0 && asterisks == 0) || + (underscores >= 3 && dashes == 0 && asterisks == 0) || + (asterisks >= 3 && dashes == 0 && underscores == 0)); + } } From 69af6839ce90d18cccac3fd295e61c1cb801f8ed Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 9 Sep 2018 14:06:52 +1000 Subject: [PATCH 296/815] Replace regex matching in ListBlockParser --- .../commonmark/internal/ListBlockParser.java | 116 +++++++++++++----- 1 file changed, 87 insertions(+), 29 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index a129b2abe..53df27caa 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -4,15 +4,8 @@ import org.commonmark.node.*; import org.commonmark.parser.block.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class ListBlockParser extends AbstractBlockParser { - private static Pattern MARKER = Pattern.compile( - "^([*+-])(?= |\t|$)" + - "|^(\\d{1,9})([.)])(?= |\t|$)"); - private final ListBlock block; public ListBlockParser(ListBlock block) { @@ -48,18 +41,16 @@ public void setTight(boolean tight) { /** * Parse a list marker and return data on the marker or null. */ - private static ListData parseListMarker(CharSequence line, final int markerIndex, final int markerColumn, - final boolean inParagraph) { - CharSequence rest = line.subSequence(markerIndex, line.length()); - Matcher matcher = MARKER.matcher(rest); - if (!matcher.find()) { + private static ListData parseList(CharSequence line, final int markerIndex, final int markerColumn, + final boolean inParagraph) { + ListMarkerData listMarker = parseListMarker(line, markerIndex); + if (listMarker == null) { return null; } + ListBlock listBlock = listMarker.listBlock; - ListBlock listBlock = createListBlock(matcher); - - int markerLength = matcher.end() - matcher.start(); - int indexAfterMarker = markerIndex + markerLength; + int indexAfterMarker = listMarker.indexAfterMarker; + int markerLength = indexAfterMarker - markerIndex; // marker doesn't include tabs, so counting them as columns directly is ok int columnAfterMarker = markerColumn + markerLength; // the column within the line where the content starts @@ -98,19 +89,76 @@ private static ListData parseListMarker(CharSequence line, final int markerIndex return new ListData(listBlock, contentColumn); } - private static ListBlock createListBlock(Matcher matcher) { - String bullet = matcher.group(1); - if (bullet != null) { - BulletList bulletList = new BulletList(); - bulletList.setBulletMarker(bullet.charAt(0)); - return bulletList; + private static ListMarkerData parseListMarker(CharSequence line, int index) { + char c = line.charAt(index); + switch (c) { + // spec: A bullet list marker is a -, +, or * character. + case '-': + case '+': + case '*': + if (isSpaceTabOrEnd(line, index + 1)) { + BulletList bulletList = new BulletList(); + bulletList.setBulletMarker(c); + return new ListMarkerData(bulletList, index + 1); + } else { + return null; + } + default: + return parseOrderedList(line, index); + } + } + + // 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; + for (int i = index; i < line.length(); i++) { + char c = line.charAt(i); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + digits++; + if (digits > 9) { + return null; + } + break; + case '.': + case ')': + 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); + return new ListMarkerData(orderedList, i + 1); + } else { + return null; + } + default: + return null; + } + } + return null; + } + + private static boolean isSpaceTabOrEnd(CharSequence line, int index) { + if (index < line.length()) { + switch (line.charAt(index)) { + case ' ': + case '\t': + return true; + default: + return false; + } } else { - String digit = matcher.group(2); - String delim = matcher.group(3); - OrderedList orderedList = new OrderedList(); - orderedList.setStartNumber(Integer.parseInt(digit)); - orderedList.setDelimiter(delim.charAt(0)); - return orderedList; + return true; } } @@ -144,7 +192,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar int markerIndex = state.getNextNonSpaceIndex(); int markerColumn = state.getColumn() + state.getIndent(); boolean inParagraph = matchedBlockParser.getParagraphContent() != null; - ListData listData = parseListMarker(state.getLine(), markerIndex, markerColumn, inParagraph); + ListData listData = parseList(state.getLine(), markerIndex, markerColumn, inParagraph); if (listData == null) { return BlockStart.none(); } @@ -175,4 +223,14 @@ private static class ListData { this.contentColumn = contentColumn; } } + + private static class ListMarkerData { + final ListBlock listBlock; + final int indexAfterMarker; + + ListMarkerData(ListBlock listBlock, int indexAfterMarker) { + this.listBlock = listBlock; + this.indexAfterMarker = indexAfterMarker; + } + } } From 0facee19b45c7c717e1ea8c0afd955c0367f91eb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sun, 9 Sep 2018 14:31:04 +1000 Subject: [PATCH 297/815] Replace regex matching and simplify IndentedCodeBlockParser --- .../internal/IndentedCodeBlockParser.java | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java index 831ff2c36..e90fdf6a9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java @@ -1,17 +1,18 @@ package org.commonmark.internal; import org.commonmark.internal.util.Parsing; -import org.commonmark.node.*; +import org.commonmark.node.Block; +import org.commonmark.node.IndentedCodeBlock; +import org.commonmark.node.Paragraph; import org.commonmark.parser.block.*; -import java.util.regex.Pattern; +import java.util.ArrayList; +import java.util.List; public class IndentedCodeBlockParser extends AbstractBlockParser { - private static final Pattern TRAILING_BLANK_LINES = Pattern.compile("(?:\n[ \t]*)+$"); - private final IndentedCodeBlock block = new IndentedCodeBlock(); - private BlockContent content = new BlockContent(); + private final List lines = new ArrayList<>(); @Override public Block getBlock() { @@ -31,17 +32,26 @@ public BlockContinue tryContinue(ParserState state) { @Override public void addLine(CharSequence line) { - content.add(line); + lines.add(line); } @Override public void closeBlock() { - // add trailing newline - content.add(""); - String contentString = content.getString(); - content = null; + int lastNonBlank = lines.size() - 1; + while (lastNonBlank >= 0) { + if (!Parsing.isBlank(lines.get(lastNonBlank))) { + break; + } + lastNonBlank--; + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < lastNonBlank + 1; i++) { + sb.append(lines.get(i)); + sb.append('\n'); + } - String literal = TRAILING_BLANK_LINES.matcher(contentString).replaceFirst("\n"); + String literal = sb.toString(); block.setLiteral(literal); } From f114c3d000efd31faf4588160b4fae71dd2c997a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Sep 2018 12:11:02 +1000 Subject: [PATCH 298/815] CHANGELOG: Mention performance improvements for block parsing --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92ddbac52..199d2c825 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ with the exception that 0.x versions can break between minor versions. ## Unreleased ### Changed +- Speed up block parsing significantly. If your document uses a lot of + blocks (paragraphs, code blocks, lists, headings), there's performance + improvements of 15% to 50% (see #137). - Parse backslash followed by unescapable character the same way as the reference implementations. ### Fixed From bae3ad665c3f7e322b989cb5de80896f42b8b66e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Sep 2018 16:24:15 +1000 Subject: [PATCH 299/815] Move constant lengths to outside of loop condition Not sure optimization would take care of that, but doing it doesn't really hurt readability either. --- .../org/commonmark/internal/DocumentParser.java | 9 ++++++--- .../internal/FencedCodeBlockParser.java | 6 ++++-- .../org/commonmark/internal/ListBlockParser.java | 6 ++++-- .../commonmark/internal/ThematicBreakParser.java | 3 ++- .../org/commonmark/internal/util/Parsing.java | 16 ++++++++++------ 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 4d705b6cd..d090ee5bb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -282,7 +282,8 @@ private void findNextNonSpace() { int cols = column; blank = true; - while (i < line.length()) { + int length = line.length(); + while (i < length) { char c = line.charAt(i); switch (c) { case ' ': @@ -309,7 +310,8 @@ private void setNewIndex(int newIndex) { index = nextNonSpace; column = nextNonSpaceColumn; } - while (index < newIndex && index != line.length()) { + int length = line.length(); + while (index < newIndex && index != length) { advance(); } // If we're going to an index as opposed to a column, we're never within a tab @@ -322,7 +324,8 @@ private void setNewColumn(int newColumn) { index = nextNonSpace; column = nextNonSpaceColumn; } - while (column < newColumn && index != line.length()) { + int length = line.length(); + while (column < newColumn && index != length) { advance(); } if (column > newColumn) { diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index 80c2e0ab6..6352892cf 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -37,7 +37,8 @@ public BlockContinue tryContinue(ParserState state) { } else { // skip optional spaces of fence indent int i = block.getFenceIndent(); - while (i > 0 && newIndex < line.length() && line.charAt(newIndex) == ' ') { + int length = line.length(); + while (i > 0 && newIndex < length && line.charAt(newIndex) == ' ') { newIndex++; i--; } @@ -86,8 +87,9 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar private static FencedCodeBlockParser checkOpener(CharSequence line, int index, int indent) { int backticks = 0; int tildes = 0; + int length = line.length(); loop: - for (int i = index; i < line.length(); i++) { + for (int i = index; i < length; i++) { switch (line.charAt(i)) { case '`': backticks++; diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index 53df27caa..39df3180b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -58,7 +58,8 @@ private static ListData parseList(CharSequence line, final int markerIndex, fina // See at which column the content starts if there is content boolean hasContent = false; - for (int i = indexAfterMarker; i < line.length(); i++) { + int length = line.length(); + for (int i = indexAfterMarker; i < length; i++) { char c = line.charAt(i); if (c == '\t') { contentColumn += Parsing.columnsToNextTabStop(contentColumn); @@ -112,7 +113,8 @@ private static ListMarkerData parseListMarker(CharSequence line, int index) { // `)` character. private static ListMarkerData parseOrderedList(CharSequence line, int index) { int digits = 0; - for (int i = index; i < line.length(); i++) { + int length = line.length(); + for (int i = index; i < length; i++) { char c = line.charAt(i); switch (c) { case '0': diff --git a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java index 0758cc828..6d9edf761 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java @@ -42,7 +42,8 @@ private static boolean isThematicBreak(CharSequence line, int index) { int dashes = 0; int underscores = 0; int asterisks = 0; - for (int i = index; i < line.length(); i++) { + int length = line.length(); + for (int i = index; i < length; i++) { switch (line.charAt(i)) { case '-': dashes++; 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 f298e9158..f5cc888ee 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -25,7 +25,8 @@ public static int columnsToNextTabStop(int column) { } public static int find(char c, CharSequence s, int startIndex) { - for (int i = startIndex; i < s.length(); i++) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { if (s.charAt(i) == c) { return i; } @@ -34,7 +35,8 @@ public static int find(char c, CharSequence s, int startIndex) { } public static int findLineBreak(CharSequence s, int startIndex) { - for (int i = startIndex; i < s.length(); i++) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { switch (s.charAt(i)) { case '\n': case '\r': @@ -70,12 +72,13 @@ public static boolean isSpaceOrTab(CharSequence s, int index) { public static CharSequence prepareLine(CharSequence line) { // Avoid building a new string in the majority of cases (no \0) StringBuilder sb = null; - for (int i = 0; i < line.length(); i++) { + int length = line.length(); + for (int i = 0; i < length; i++) { char c = line.charAt(i); - switch (line.charAt(i)) { + switch (c) { case '\0': if (sb == null) { - sb = new StringBuilder(line.length()); + sb = new StringBuilder(length); sb.append(line, 0, i); } sb.append('\uFFFD'); @@ -139,7 +142,8 @@ public static int skipSpaceTabBackwards(CharSequence s, int startIndex, int last } private static int findNonSpace(CharSequence s, int startIndex) { - for (int i = startIndex; i < s.length(); i++) { + int length = s.length(); + for (int i = startIndex; i < length; i++) { switch (s.charAt(i)) { case ' ': case '\t': From 044c507e7448e942e01abe15332177da54850f43 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 17 Sep 2018 16:53:58 +1000 Subject: [PATCH 300/815] Remove Substring class I'll conclude that this was a failed experiment. Benchmarks show that performance improves up to 4% when we remove it. --- .../commonmark/internal/DocumentParser.java | 31 +++---- .../commonmark/internal/util/Substring.java | 72 --------------- .../org/commonmark/test/SubstringTest.java | 91 ------------------- 3 files changed, 12 insertions(+), 182 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/util/Substring.java delete mode 100644 commonmark/src/test/java/org/commonmark/test/SubstringTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index d090ee5bb..3ce6317e7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,23 +1,14 @@ package org.commonmark.internal; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.Reader; import org.commonmark.internal.util.Parsing; -import org.commonmark.internal.util.Substring; import org.commonmark.node.*; import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.LinkedHashSet; -import java.util.HashSet; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.*; public class DocumentParser implements ParserState { @@ -31,6 +22,7 @@ public class DocumentParser implements ParserState { IndentedCodeBlock.class)); private static final Map, BlockParserFactory> NODES_TO_CORE_FACTORIES; + static { Map, BlockParserFactory> map = new HashMap<>(); map.put(BlockQuote.class, new BlockQuoteParser.Factory()); @@ -77,7 +69,7 @@ public class DocumentParser implements ParserState { public DocumentParser(List blockParserFactories, InlineParser inlineParser) { this.blockParserFactories = blockParserFactories; this.inlineParser = inlineParser; - + this.documentBlockParser = new DocumentBlockParser(); activateBlockParser(this.documentBlockParser); } @@ -103,7 +95,7 @@ public Document parse(String input) { int lineStart = 0; int lineBreak; while ((lineBreak = Parsing.findLineBreak(input, lineStart)) != -1) { - CharSequence line = Substring.of(input, lineStart, lineBreak); + String line = input.substring(lineStart, lineBreak); incorporateLine(line); if (lineBreak + 1 < input.length() && input.charAt(lineBreak) == '\r' && input.charAt(lineBreak + 1) == '\n') { lineStart = lineBreak + 2; @@ -112,12 +104,13 @@ public Document parse(String input) { } } if (input.length() > 0 && (lineStart == 0 || lineStart < input.length())) { - incorporateLine(Substring.of(input, lineStart, input.length())); + String line = input.substring(lineStart); + incorporateLine(line); } return finalizeAndProcess(); } - + public Document parse(Reader input) throws IOException { BufferedReader bufferedReader; if (input instanceof BufferedReader) { @@ -125,7 +118,7 @@ public Document parse(Reader input) throws IOException { } else { bufferedReader = new BufferedReader(input); } - + String line; while ((line = bufferedReader.readLine()) != null) { incorporateLine(line); @@ -532,7 +525,7 @@ private Document finalizeAndProcess() { this.processInlines(); return this.documentBlockParser.getBlock(); } - + private static class MatchedBlockParserImpl implements MatchedBlockParser { private final BlockParser matchedBlockParser; diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Substring.java b/commonmark/src/main/java/org/commonmark/internal/util/Substring.java deleted file mode 100644 index cf8907bdf..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/util/Substring.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.commonmark.internal.util; - -/** - * A CharSequence that avoids copying string data when getting a substring. - */ -public class Substring implements CharSequence { - - private final String base; - private final int beginIndex; - private final int endIndex; - - public static CharSequence of(String base, int beginIndex, int endIndex) { - return new Substring(base, beginIndex, endIndex); - } - - private Substring(String base, int beginIndex, int endIndex) { - if (beginIndex < 0) { - throw new StringIndexOutOfBoundsException("beginIndex must be at least 0"); - } - if (endIndex < 0) { - throw new StringIndexOutOfBoundsException("endIndex must be at least 0"); - } - if (endIndex < beginIndex) { - throw new StringIndexOutOfBoundsException("endIndex must not be less than beginIndex"); - } - if (endIndex > base.length()) { - throw new StringIndexOutOfBoundsException("endIndex must not be greater than length"); - } - this.base = base; - this.beginIndex = beginIndex; - this.endIndex = endIndex; - } - - @Override - public int length() { - return endIndex - beginIndex; - } - - @Override - public char charAt(int index) { - if (index < 0 || beginIndex + index >= endIndex) { - throw new StringIndexOutOfBoundsException("String index out of range: " + index); - } - return base.charAt(index + beginIndex); - } - - @Override - public CharSequence subSequence(int start, int end) { - if (start < 0 || beginIndex + start > endIndex) { - throw new StringIndexOutOfBoundsException("String index out of range: " + start); - } - if (end < 0 || beginIndex + end > endIndex) { - throw new StringIndexOutOfBoundsException("String index out of range: " + end); - } - return new Substring(base, beginIndex + start, beginIndex + end); - } - - @Override - public String toString() { - return base.substring(beginIndex, endIndex); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj == this || (obj instanceof CharSequence && toString().equals(obj.toString())); - } -} diff --git a/commonmark/src/test/java/org/commonmark/test/SubstringTest.java b/commonmark/src/test/java/org/commonmark/test/SubstringTest.java deleted file mode 100644 index cae24c376..000000000 --- a/commonmark/src/test/java/org/commonmark/test/SubstringTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.commonmark.test; - -import org.commonmark.internal.util.Substring; -import org.junit.Test; - -import static junit.framework.TestCase.assertEquals; -import static org.junit.Assert.assertNotEquals; - -public class SubstringTest { - - private final CharSequence substring = Substring.of("abcdefghi", 3, 6); - - @Test - public void testConstructEmpty() { - assertEquals("", Substring.of("ab", 0, 0).toString()); - assertEquals("", Substring.of("ab", 1, 1).toString()); - assertEquals("", Substring.of("ab", 2, 2).toString()); - } - - @Test(expected = StringIndexOutOfBoundsException.class) - public void testConstructBeginIndexNegative() { - Substring.of("abc", -1, 0); - } - - @Test(expected = StringIndexOutOfBoundsException.class) - public void testConstructEndIndexNegative() { - Substring.of("abc", 0, -1); - } - - @Test(expected = StringIndexOutOfBoundsException.class) - public void testConstructEndIndexLessThanBegin() { - Substring.of("abc", 1, 0); - } - - @Test(expected = StringIndexOutOfBoundsException.class) - public void testConstructEndIndexGreaterThanLength() { - Substring.of("abc", 1, 4); - } - - @Test - public void testLength() { - assertEquals(3, substring.length()); - } - - @Test - public void testCharAt() { - assertEquals('d', substring.charAt(0)); - assertEquals('e', substring.charAt(1)); - assertEquals('f', substring.charAt(2)); - } - - @Test(expected = StringIndexOutOfBoundsException.class) - public void testCharAtOutOfBoundsLeft() { - substring.charAt(-1); - } - - @Test(expected = StringIndexOutOfBoundsException.class) - public void testCharAtOutOfBoundsRight() { - substring.charAt(3); - } - - @Test - public void testSubSequence() { - assertEquals("d", substring.subSequence(0, 1).toString()); - assertEquals("e", substring.subSequence(1, 2).toString()); - assertEquals("f", substring.subSequence(2, 3).toString()); - assertEquals("def", substring.subSequence(0, 3).toString()); - } - - @Test(expected = StringIndexOutOfBoundsException.class) - public void testSubSequenceOutOfBoundsLeft() { - substring.subSequence(-1, 2); - } - - @Test(expected = StringIndexOutOfBoundsException.class) - public void testSubSequenceOutOfBoundsRight() { - substring.subSequence(1, 4); - } - - @Test - public void testHashCodeEquals() { - CharSequence a = Substring.of("abcdefghi", 3, 6); - CharSequence b = Substring.of("123def456", 3, 6); - CharSequence other = Substring.of("123de", 3, 5); - assertEquals(a, b); - assertEquals(b, a); - assertNotEquals(a, other); - assertNotEquals(other, a); - assertEquals(a.hashCode(), b.hashCode()); - } -} From cfd28faa221056a1a28ce402ed8a278afe9e527d Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 8 Nov 2018 17:07:25 +1100 Subject: [PATCH 301/815] Move list tight/loose calculation to list parsers and simplify it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This results in an almost 10% performance improvement on both benchmarks. Before: Benchmark Mode Cnt Score Error Units SpecBenchmark.examples thrpt 200 308.071 ± 0.975 ops/s SpecBenchmark.wholeSpec thrpt 200 134.033 ± 0.545 ops/s After: SpecBenchmark.examples thrpt 200 331.033 ± 0.930 ops/s SpecBenchmark.wholeSpec thrpt 200 146.443 ± 0.584 ops/s --- .../commonmark/internal/DocumentParser.java | 78 +------ .../commonmark/internal/ListBlockParser.java | 32 ++- .../commonmark/internal/ListItemParser.java | 19 +- .../java/org/commonmark/node/ListBlock.java | 4 + .../parser/block/AbstractBlockParser.java | 2 +- .../commonmark/parser/block/BlockParser.java | 2 +- .../commonmark/test/ListTightLooseTest.java | 193 ++++++++++++++++++ 7 files changed, 243 insertions(+), 87 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 3ce6317e7..539516c1d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -64,7 +64,6 @@ public class DocumentParser implements ParserState { private List activeBlockParsers = new ArrayList<>(); private Set allBlockParsers = new HashSet<>(); - private Map lastLineBlank = new HashMap<>(); public DocumentParser(List blockParserFactories, InlineParser inlineParser) { this.blockParserFactories = blockParserFactories; @@ -258,7 +257,6 @@ private void incorporateLine(CharSequence ln) { if (!allClosed) { finalizeBlocks(unmatchedBlockParsers); } - propagateLastLineBlank(blockParser, lastMatchedBlockParser); if (!blockParser.isContainer()) { addLine(); @@ -392,9 +390,6 @@ private void finalize(BlockParser blockParser) { && inlineParser instanceof ReferenceParser) { ParagraphParser paragraphParser = (ParagraphParser) blockParser; paragraphParser.closeBlock((ReferenceParser) inlineParser); - } else if (blockParser instanceof ListBlockParser) { - ListBlockParser listBlockParser = (ListBlockParser) blockParser; - finalizeListTight(listBlockParser); } } @@ -407,42 +402,6 @@ private void processInlines() { } } - private void finalizeListTight(ListBlockParser listBlockParser) { - Node item = listBlockParser.getBlock().getFirstChild(); - while (item != null) { - // check for non-final list item ending with blank line: - if (endsWithBlankLine(item) && item.getNext() != null) { - listBlockParser.setTight(false); - break; - } - // recurse into children of list item, to see if there are - // spaces between any of them: - Node subItem = item.getFirstChild(); - while (subItem != null) { - if (endsWithBlankLine(subItem) && (item.getNext() != null || subItem.getNext() != null)) { - listBlockParser.setTight(false); - break; - } - subItem = subItem.getNext(); - } - item = item.getNext(); - } - } - - private boolean endsWithBlankLine(Node block) { - while (block != null) { - if (isLastLineBlank(block)) { - return true; - } - if (block instanceof ListBlock || block instanceof ListItem) { - block = block.getLastChild(); - } else { - break; - } - } - return false; - } - /** * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try * its parent, and so on til we find a block that can accept children. @@ -475,49 +434,14 @@ private void removeActiveBlockParser() { old.getBlock().unlink(); } - private void propagateLastLineBlank(BlockParser blockParser, BlockParser lastMatchedBlockParser) { - if (isBlank() && blockParser.getBlock().getLastChild() != null) { - setLastLineBlank(blockParser.getBlock().getLastChild(), true); - } - - Block block = blockParser.getBlock(); - - // Block quote lines are never blank as they start with `>`. - // We don't count blanks in fenced code for purposes of tight/loose lists. - // We also don't set lastLineBlank on an empty list item. - boolean lastLineBlank = isBlank() && - !(block instanceof BlockQuote || - block instanceof FencedCodeBlock || - (block instanceof ListItem && - block.getFirstChild() == null && - blockParser != lastMatchedBlockParser)); - - // Propagate lastLineBlank up through parents - Node node = blockParser.getBlock(); - while (node != null) { - setLastLineBlank(node, lastLineBlank); - node = node.getParent(); - } - } - - private void setLastLineBlank(Node node, boolean value) { - lastLineBlank.put(node, value); - } - - private boolean isLastLineBlank(Node node) { - Boolean value = lastLineBlank.get(node); - return value != null && value; - } - /** * Finalize blocks of previous line. Returns true. */ - private boolean finalizeBlocks(List blockParsers) { + private void finalizeBlocks(List blockParsers) { for (int i = blockParsers.size() - 1; i >= 0; i--) { BlockParser blockParser = blockParsers.get(i); finalize(blockParser); } - return true; } private Document finalizeAndProcess() { diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index 39df3180b..28f9bfb0f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -8,6 +8,9 @@ public class ListBlockParser extends AbstractBlockParser { private final ListBlock block; + private boolean hadBlankLine; + private int linesAfterBlank; + public ListBlockParser(ListBlock block) { this.block = block; } @@ -18,8 +21,20 @@ public boolean isContainer() { } @Override - public boolean canContain(Block block) { - return block instanceof ListItem; + public boolean canContain(Block childBlock) { + if (childBlock instanceof ListItem) { + // Another list item is added to this list block. If the previous line was blank, that means this list block + // is "loose" (not tight). + // + // spec: A list is loose if any of its constituent list items are separated by blank lines + if (hadBlankLine && linesAfterBlank == 1) { + block.setTight(false); + hadBlankLine = false; + } + return true; + } else { + return false; + } } @Override @@ -29,15 +44,17 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState state) { + if (state.isBlank()) { + hadBlankLine = true; + linesAfterBlank = 0; + } else if (hadBlankLine) { + linesAfterBlank++; + } // List blocks themselves don't have any markers, only list items. So try to stay in the list. // If there is a block start other than list item, canContain makes sure that this list is closed. return BlockContinue.atIndex(state.getIndex()); } - public void setTight(boolean tight) { - block.setTight(tight); - } - /** * Parse a list marker and return data on the marker or null. */ @@ -207,7 +224,8 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar !(listsMatch((ListBlock) matched.getBlock(), listData.listBlock))) { ListBlockParser listBlockParser = new ListBlockParser(listData.listBlock); - listBlockParser.setTight(true); + // We start out with assuming a list is tight. If we find a blank line, we set it to loose later. + listData.listBlock.setTight(true); return BlockStart.of(listBlockParser, listItemParser).atColumn(newColumn); } else { diff --git a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java index 8acafe15d..96b086dab 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java @@ -1,7 +1,9 @@ package org.commonmark.internal; import org.commonmark.node.Block; +import org.commonmark.node.ListBlock; import org.commonmark.node.ListItem; +import org.commonmark.node.Paragraph; import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.BlockContinue; import org.commonmark.parser.block.ParserState; @@ -16,6 +18,8 @@ public class ListItemParser extends AbstractBlockParser { */ private int contentIndent; + private boolean hadBlankLine; + public ListItemParser(int contentIndent) { this.contentIndent = contentIndent; } @@ -26,7 +30,17 @@ public boolean isContainer() { } @Override - public boolean canContain(Block block) { + public boolean canContain(Block childBlock) { + if (hadBlankLine) { + // We saw a blank line in this list item, that means the list block is loose. + // + // spec: if any of its constituent list items directly contain two block-level elements with a blank line + // between them + Block parent = block.getParent(); + if (parent instanceof ListBlock) { + ((ListBlock) parent).setTight(false); + } + } return true; } @@ -42,6 +56,9 @@ public BlockContinue tryContinue(ParserState state) { // Blank line after empty list item return BlockContinue.none(); } else { + Block activeBlock = state.getActiveBlockParser().getBlock(); + // If the active block is a code block, blank lines in it should not affect if the list is tight. + hadBlankLine = activeBlock instanceof Paragraph || activeBlock instanceof ListItem; return BlockContinue.atIndex(state.getNextNonSpaceIndex()); } } diff --git a/commonmark/src/main/java/org/commonmark/node/ListBlock.java b/commonmark/src/main/java/org/commonmark/node/ListBlock.java index d49657ffe..69482f66e 100644 --- a/commonmark/src/main/java/org/commonmark/node/ListBlock.java +++ b/commonmark/src/main/java/org/commonmark/node/ListBlock.java @@ -4,6 +4,10 @@ public abstract class ListBlock extends Block { private boolean tight; + /** + * @return whether this list is tight or loose + * @see CommonMark Spec for tight lists + */ public boolean isTight() { return tight; } 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 709bd7b93..d6b30fa68 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java @@ -11,7 +11,7 @@ public boolean isContainer() { } @Override - public boolean canContain(Block block) { + public boolean canContain(Block childBlock) { return false; } 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 48e9098e3..851436051 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java @@ -15,7 +15,7 @@ public interface BlockParser { */ boolean isContainer(); - boolean canContain(Block block); + boolean canContain(Block childBlock); Block getBlock(); diff --git a/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java b/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java new file mode 100644 index 000000000..4889bb9ab --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/ListTightLooseTest.java @@ -0,0 +1,193 @@ +package org.commonmark.test; + +import org.junit.Test; + +public class ListTightLooseTest extends CoreRenderingTestCase { + + @Test + public void tight() { + assertRendering("- foo\n" + + "- bar\n" + + "+ baz\n", + "
      \n" + + "
    • foo
    • \n" + + "
    • bar
    • \n" + + "
    \n" + + "
      \n" + + "
    • baz
    • \n" + + "
    \n"); + } + + @Test + public void loose() { + assertRendering("- foo\n" + + "\n" + + "- bar\n" + + "\n" + + "\n" + + "- baz\n", + "
      \n" + + "
    • \n" + + "

      foo

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

      bar

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

      baz

      \n" + + "
    • \n" + + "
    \n"); + } + + @Test + public void looseNested() { + assertRendering("- foo\n" + + " - bar\n" + + "\n" + + "\n" + + " baz", + "
      \n" + + "
    • foo\n" + + "
        \n" + + "
      • \n" + + "

        bar

        \n" + + "

        baz

        \n" + + "
      • \n" + + "
      \n" + + "
    • \n" + + "
    \n"); + } + + @Test + public void looseNested2() { + assertRendering("- a\n" + + " - b\n" + + "\n" + + " c\n" + + "- d\n", + "
      \n" + + "
    • a\n" + + "
        \n" + + "
      • \n" + + "

        b

        \n" + + "

        c

        \n" + + "
      • \n" + + "
      \n" + + "
    • \n" + + "
    • d
    • \n" + + "
    \n"); + } + + @Test + public void looseOuter() { + assertRendering("- foo\n" + + " - bar\n" + + "\n" + + "\n" + + " baz", + "
      \n" + + "
    • \n" + + "

      foo

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

      baz

      \n" + + "
    • \n" + + "
    \n"); + } + + @Test + public void looseListItem() { + assertRendering("- one\n" + + "\n" + + " two\n", + "
      \n" + + "
    • \n" + + "

      one

      \n" + + "

      two

      \n" + + "
    • \n" + + "
    \n"); + } + + @Test + public void tightWithBlankLineAfter() { + assertRendering("- foo\n" + + "- bar\n" + + "\n", + "
      \n" + + "
    • foo
    • \n" + + "
    • bar
    • \n" + + "
    \n"); + } + + @Test + public void tightListWithCodeBlock() { + assertRendering("- a\n" + + "- ```\n" + + " b\n" + + "\n" + + "\n" + + " ```\n" + + "- c\n", + "
      \n" + + "
    • a
    • \n" + + "
    • \n" + + "
      b\n" +
      +                        "\n" +
      +                        "\n" +
      +                        "
      \n" + + "
    • \n" + + "
    • c
    • \n" + + "
    \n"); + } + + @Test + public void tightListWithCodeBlock2() { + assertRendering("* foo\n" + + " ```\n" + + " bar\n" + + "\n" + + " ```\n" + + " baz\n", + "
      \n" + + "
    • foo\n" + + "
      bar\n" +
      +                        "\n" +
      +                        "
      \n" + + "baz
    • \n" + + "
    \n"); + } + + @Test + public void looseEmptyListItem() { + assertRendering("* a\n" + + "*\n" + + "\n" + + "* c", + "
      \n" + + "
    • \n" + + "

      a

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

      c

      \n" + + "
    • \n" + + "
    \n"); + } + + @Test + public void looseBlankLineAfterCodeBlock() { + assertRendering("1. ```\n" + + " foo\n" + + " ```\n" + + "\n" + + " bar", + "
      \n" + + "
    1. \n" + + "
      foo\n" +
      +                        "
      \n" + + "

      bar

      \n" + + "
    2. \n" + + "
    \n"); + } +} From 1d17bdbaf367c758799e18f7430b44be07f94b95 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 9 Nov 2018 10:30:56 +1100 Subject: [PATCH 302/815] Add class used for profiling --- .../java/org/commonmark/ProfilingMain.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 commonmark/src/test/java/org/commonmark/ProfilingMain.java diff --git a/commonmark/src/test/java/org/commonmark/ProfilingMain.java b/commonmark/src/test/java/org/commonmark/ProfilingMain.java new file mode 100644 index 000000000..0f0c08153 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/ProfilingMain.java @@ -0,0 +1,33 @@ +package org.commonmark; + +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.TestResources; + +import java.util.Collections; +import java.util.List; + +public class ProfilingMain { + + private static final String SPEC = TestResources.readAsString(TestResources.getSpec()); + // private static final List SPEC_EXAMPLES = ExampleReader.readExampleSources(TestResources.getSpec()); + private static final Parser PARSER = Parser.builder().build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); + + public static void main(String[] args) throws Exception { + System.out.println("Started up, attach profiler now"); + Thread.sleep(10_000); + System.out.println("Parsing and rendering"); + parseAndRender(Collections.singletonList(SPEC)); + System.out.println("Finished parsing"); + } + + private static long parseAndRender(List examples) { + long length = 0; + for (String example : examples) { + String result = RENDERER.render(PARSER.parse(example)); + length += result.length(); + } + return length; + } +} From 729205965eba8941d6988f1bfdf57468f6473112 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 12 Nov 2018 10:44:44 +1100 Subject: [PATCH 303/815] Add parse-only benchmarks Useful for quantifying parsing speed improvements (without including the rendering time). --- .../org/commonmark/test/SpecBenchmark.java | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index 75ce7ffcd..42692e498 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -1,12 +1,11 @@ package org.commonmark.test; +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.ExampleReader; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.options.CommandLineOptions; import org.openjdk.jmh.runner.options.Options; @@ -16,6 +15,9 @@ import java.util.List; @State(Scope.Benchmark) +@Fork(5) +@Warmup(iterations = 10) +@Measurement(iterations = 20) public class SpecBenchmark { private static final String SPEC = TestResources.readAsString(TestResources.getSpec()); @@ -32,12 +34,22 @@ public static void main(String[] args) throws Exception { } @Benchmark - public long wholeSpec() { + public long parseWholeSpec() { + return parse(Collections.singletonList(SPEC)); + } + + @Benchmark + public long parseExamples() { + return parse(SPEC_EXAMPLES); + } + + @Benchmark + public long parseAndRenderWholeSpec() { return parseAndRender(Collections.singletonList(SPEC)); } @Benchmark - public long examples() { + public long parseAndRenderExamples() { return parseAndRender(SPEC_EXAMPLES); } @@ -50,4 +62,12 @@ private static long parseAndRender(List examples) { return length; } + private static long parse(List examples) { + long length = 0; + for (String example : examples) { + Node document = PARSER.parse(example); + length += document.getFirstChild() == document.getLastChild() ? 0 : 1; + } + return length; + } } From 926a05cae169e55a0f93f205ba4ef330b8b8109f Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 12 Nov 2018 15:16:36 +1100 Subject: [PATCH 304/815] Update CHANGELOG after additional parsing improvements --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 199d2c825..966521bed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,9 @@ with the exception that 0.x versions can break between minor versions. ## Unreleased ### Changed -- Speed up block parsing significantly. If your document uses a lot of - blocks (paragraphs, code blocks, lists, headings), there's performance - improvements of 15% to 50% (see #137). +- Speed up parsing significantly: Compared to the previous version, the + benchmarks show up to 55% faster parsing for both small and large + documents! (#137, #140) - Parse backslash followed by unescapable character the same way as the reference implementations. ### Fixed From 609d3c5ad1acc72b73d71eb0de6c19fcb8116f42 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 12 Nov 2018 15:25:15 +1100 Subject: [PATCH 305/815] Prepare for 0.12.0 release --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 966521bed..d9b9676bc 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.12.0] - 2018-11-12 ### Changed - Speed up parsing significantly: Compared to the previous version, the benchmarks show up to 55% faster parsing for both small and large @@ -223,6 +223,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.12.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.11.0...commonmark-parent-0.12.0 [0.11.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.10.0...commonmark-parent-0.11.0 [0.10.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.9.0...commonmark-parent-0.10.0 [0.9.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.8.0...commonmark-parent-0.9.0 From c6f917177527d1e3f890866d2e28261b4f2cd2d9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 12 Nov 2018 15:37:17 +1100 Subject: [PATCH 306/815] Move contributing out of README and into CONTRIBUTING.md Also add a link to the build for releasing. --- CONTRIBUTING.md | 40 ++++++++++++++++++++++++++++++++++++++++ README.md | 25 +------------------------ 2 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..a187cea4b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +Contributing +============ + +Hey, thanks for your interest in contributing to this library! We welcome any +type of pull request, issues and comments! 😀 + +For pull requests, make sure you: + +* Add tests for new features and bug fixes +* Follow the existing style (always use braces, 4 space indent) +* Separate unrelated changes into multiple pull requests + +If you are interested in working on something but don't know what, see the +existing issues with label "help wanted". + +For bigger changes, make sure you start a discussion first by creating +an issue and explaining the intended change. + +CLA +--- + +Atlassian requires contributors to sign a Contributor License Agreement, +known as a CLA. This serves as a record stating that the contributor is +entitled to contribute the code/documentation/translation to the project +and is willing to have it used in distributions and derivative works +(or is willing to transfer ownership). + +Prior to accepting your first contribution we ask that you please follow the +appropriate link below to digitally sign the CLA. The Corporate CLA is for those +who are contributing as a member of an organization and the individual CLA is +for those contributing as an individual. + +* [CLA for corporate contributors](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=e1c17c66-ca4d-4aab-a953-2c231af4a20b) +* [CLA for individuals](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=3f94fbdc-2fbe-46ac-b14c-5d152700ae5d) + +Releasing +--------- + +Releases are done from an Atlassian internal build server: +https://engservices-bamboo.internal.atlassian.com/browse/CM diff --git a/README.md b/README.md index 5b6309dff..2101f0186 100644 --- a/README.md +++ b/README.md @@ -323,30 +323,7 @@ See also Contributing ------------ -Pull requests, issues and comments welcome ☺. For pull requests: - -* Add tests for new features and bug fixes -* Follow the existing style (always use braces, 4 space indent) -* Separate unrelated changes into multiple pull requests - -See the existing "help wanted" issues for things to start contributing. - -For bigger changes, make sure you start a discussion first by creating -an issue and explaining the intended change. - -Atlassian requires contributors to sign a Contributor License Agreement, -known as a CLA. This serves as a record stating that the contributor is -entitled to contribute the code/documentation/translation to the project -and is willing to have it used in distributions and derivative works -(or is willing to transfer ownership). - -Prior to accepting your first contribution we ask that you please follow the -appropriate link below to digitally sign the CLA. The Corporate CLA is for those -who are contributing as a member of an organization and the individual CLA is -for those contributing as an individual. - -* [CLA for corporate contributors](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=e1c17c66-ca4d-4aab-a953-2c231af4a20b) -* [CLA for individuals](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=3f94fbdc-2fbe-46ac-b14c-5d152700ae5d) +See CONTRIBUTING.md file. License ------- From 8d93cb7e0b9296f7caed0adf0c1a9a0b737c2562 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 12 Nov 2018 16:15:03 +1100 Subject: [PATCH 307/815] Let's bump the autolink dependencies before release too --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9b9676bc..e77f047f7 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. -## [0.12.0] - 2018-11-12 +## Unreleased ### Changed - Speed up parsing significantly: Compared to the previous version, the benchmarks show up to 55% faster parsing for both small and large From d5adacdb57b128110f0018085a1a6c38174880b9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 12 Nov 2018 21:37:31 +1100 Subject: [PATCH 308/815] Try building with OpenJDK 11 on Travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 023c2cd1c..f73430272 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ matrix: include: - jdk: oraclejdk8 env: TEST=java + - jdk: openjdk11 + env: TEST=java - jdk: oraclejdk8 env: TEST=android dist: precise From 557e622a2896f936215e91815dd5517c531f83e8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 12 Nov 2018 22:22:55 +1100 Subject: [PATCH 309/815] Bump maven-surefire-plugin version --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 4ade086aa..fcde0c930 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,11 @@ org.apache.maven.plugins maven-release-plugin + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + From 5d82764ad736607407232389bbc161de73e478f0 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 13 Nov 2018 09:29:16 +1100 Subject: [PATCH 310/815] CHANGELOG: Add Java 11 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e77f047f7..90a212148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ with the exception that 0.x versions can break between minor versions. documents! (#137, #140) - Parse backslash followed by unescapable character the same way as the reference implementations. +- Build and test on Java 11 as well. ### Fixed - Fix tab handling in ATX and Setext headings. From c5c609df97830b90b6aaecde89862607a9bfbac7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 13 Nov 2018 10:13:50 +1100 Subject: [PATCH 311/815] Bump autolink to 0.10.0 and use newer API to iterate over spans --- CHANGELOG.md | 1 + commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 +- commonmark-ext-autolink/pom.xml | 2 +- .../internal/AutolinkPostProcessor.java | 42 ++++++++++--------- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90a212148..d3a8c72e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ with the exception that 0.x versions can break between minor versions. - Parse backslash followed by unescapable character the same way as the reference implementations. - Build and test on Java 11 as well. +- autolink: Stop URLs at " and ` as well ### Fixed - Fix tab handling in ATX and Setext headings. diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index a4985d9da..9558176be 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -9,7 +9,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties echo "version.maven=0.9.0" >> test.properties -echo "version.maven_autolink=0.7.0" >> test.properties +echo "version.maven_autolink=0.10.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 84dcffb92..6c7fda569 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -30,12 +30,12 @@ path.report=../report # Version number of commonmark and extensions in maven central. version.maven=0.11.0 # Version number of autolink in maven central (not bundled with extension jar). -version.maven_autolink=0.8.0 +version.maven_autolink=0.10.0 # Version number of commonmark and extensions in project. version.snapshot=0.11.1-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). -version.snapshot_autolink=0.8.0 +version.snapshot_autolink=0.10.0 ``` If you're going to test on device with Android 15 then you can skip downloading emulator. diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index c590ab7b7..1d85fb9ec 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.8.0 + 0.10.0 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 33a7c321a..0f94d5902 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,10 +1,14 @@ package org.commonmark.ext.autolink.internal; -import org.commonmark.node.*; +import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.Link; +import org.commonmark.node.Node; +import org.commonmark.node.Text; import org.commonmark.parser.PostProcessor; import org.nibor.autolink.LinkExtractor; import org.nibor.autolink.LinkSpan; import org.nibor.autolink.LinkType; +import org.nibor.autolink.Span; import java.util.EnumSet; @@ -21,28 +25,26 @@ public Node process(Node node) { return node; } - private void linkify(Text text) { - String literal = text.getLiteral(); - Iterable links = linkExtractor.extractLinks(literal); + private void linkify(Text textNode) { + String literal = textNode.getLiteral(); - Node lastNode = text; - int last = 0; - for (LinkSpan link : links) { - String linkText = literal.substring(link.getBeginIndex(), link.getEndIndex()); - if (link.getBeginIndex() != last) { - lastNode = insertNode(new Text(literal.substring(last, link.getBeginIndex())), lastNode); + Node lastNode = textNode; + + for (Span span : linkExtractor.extractSpans(literal)) { + String text = literal.substring(span.getBeginIndex(), span.getEndIndex()); + if (span instanceof LinkSpan) { + String destination = getDestination((LinkSpan) span, text); + Text contentNode = new Text(text); + Link linkNode = new Link(destination, null); + linkNode.appendChild(contentNode); + lastNode = insertNode(linkNode, lastNode); + } else { + lastNode = insertNode(new Text(text), lastNode); } - Text contentNode = new Text(linkText); - String destination = getDestination(link, linkText); - Link linkNode = new Link(destination, null); - linkNode.appendChild(contentNode); - lastNode = insertNode(linkNode, lastNode); - last = link.getEndIndex(); - } - if (last != literal.length()) { - insertNode(new Text(literal.substring(last)), lastNode); } - text.unlink(); + + // Original node no longer needed + textNode.unlink(); } private static String getDestination(LinkSpan linkSpan, String linkText) { From d370708f90991d267b0cb7107c8cbde6d6ea79c1 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 13 Nov 2018 12:09:26 +1100 Subject: [PATCH 312/815] Prepare for release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a8c72e1..c43329e7b 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.12.0] - 2018-11-13 ### Changed - Speed up parsing significantly: Compared to the previous version, the benchmarks show up to 55% faster parsing for both small and large From d701d914ec295ae62615d84925744f056c660747 Mon Sep 17 00:00:00 2001 From: bambooagent Date: Tue, 13 Nov 2018 01:22:06 +0000 Subject: [PATCH 313/815] [maven-release-plugin] prepare release commonmark-parent-0.12.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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 1d85fb9ec..49db87451 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index bde6bae15..f9f4311d5 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 2e8e687fe..53f266a5e 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 954256dfb..e00a09a31 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index b40673e38..d413ef1ea 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index c5a82c5d5..e048484e8 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.11.1-SNAPSHOT + 0.12.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 57dfcb8c8..00e466210 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index a3bc7cb2b..14cb8f65f 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 528bfc820..d613aa290 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark diff --git a/pom.xml b/pom.xml index fcde0c930..9979a7b66 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.11.1-SNAPSHOT + 0.12.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ com.atlassian.commonmark commonmark - 0.11.1-SNAPSHOT + 0.12.0 com.atlassian.commonmark commonmark-ext-autolink - 0.11.1-SNAPSHOT + 0.12.0 com.atlassian.commonmark commonmark-ext-ins - 0.11.1-SNAPSHOT + 0.12.0 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.11.1-SNAPSHOT + 0.12.0 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.11.1-SNAPSHOT + 0.12.0 com.atlassian.commonmark commonmark-ext-heading-anchor - 0.11.1-SNAPSHOT + 0.12.0 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.11.1-SNAPSHOT + 0.12.0 com.atlassian.commonmark commonmark-test-util - 0.11.1-SNAPSHOT + 0.12.0 @@ -195,7 +195,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.12.0 From cc96bd9480635899fa8209b2555486be28b85d37 Mon Sep 17 00:00:00 2001 From: bambooagent Date: Tue, 13 Nov 2018 01:22:11 +0000 Subject: [PATCH 314/815] [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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 49db87451..3eac634ed 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index f9f4311d5..b9fdc7d5d 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 53f266a5e..83def6f77 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index e00a09a31..61b521004 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index d413ef1ea..172c81290 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e048484e8..701bfe5df 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.12.0 + 0.12.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 00e466210..43c30ee09 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 14cb8f65f..d0d923d87 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index d613aa290..2170a7b4c 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 9979a7b66..ce2368b20 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.0 + 0.12.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ com.atlassian.commonmark commonmark - 0.12.0 + 0.12.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.12.0 + 0.12.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-ins - 0.12.0 + 0.12.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.12.0 + 0.12.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.12.0 + 0.12.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-heading-anchor - 0.12.0 + 0.12.1-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.12.0 + 0.12.1-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.12.0 + 0.12.1-SNAPSHOT @@ -195,7 +195,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.12.0 + HEAD From 0439d5dbc9bd58407d049d69b49f2a3041cfc780 Mon Sep 17 00:00:00 2001 From: bambooagent Date: Tue, 13 Nov 2018 05:17:58 +0000 Subject: [PATCH 315/815] [maven-release-plugin] prepare release commonmark-parent-0.12.1 --- 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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 3eac634ed..11d054db1 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index b9fdc7d5d..041952b7b 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 83def6f77..d479743ea 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 61b521004..75023fe2d 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 172c81290..f43da68e2 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 701bfe5df..8836ede8b 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.12.1-SNAPSHOT + 0.12.1 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 43c30ee09..522a942b1 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index d0d923d87..39b7322e1 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 2170a7b4c..6f1a102c6 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark diff --git a/pom.xml b/pom.xml index ce2368b20..5e698d3e2 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1-SNAPSHOT + 0.12.1 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ com.atlassian.commonmark commonmark - 0.12.1-SNAPSHOT + 0.12.1 com.atlassian.commonmark commonmark-ext-autolink - 0.12.1-SNAPSHOT + 0.12.1 com.atlassian.commonmark commonmark-ext-ins - 0.12.1-SNAPSHOT + 0.12.1 com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.12.1-SNAPSHOT + 0.12.1 com.atlassian.commonmark commonmark-ext-gfm-tables - 0.12.1-SNAPSHOT + 0.12.1 com.atlassian.commonmark commonmark-ext-heading-anchor - 0.12.1-SNAPSHOT + 0.12.1 com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.12.1-SNAPSHOT + 0.12.1 com.atlassian.commonmark commonmark-test-util - 0.12.1-SNAPSHOT + 0.12.1 @@ -195,7 +195,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - HEAD + commonmark-parent-0.12.1 From 51fbf8f9c4fc573f3228e5e3e7937beb0d74e756 Mon Sep 17 00:00:00 2001 From: bambooagent Date: Tue, 13 Nov 2018 05:18:04 +0000 Subject: [PATCH 316/815] [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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 11d054db1..e1b43fa25 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 041952b7b..58b34fd0a 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index d479743ea..9fbe9942a 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 75023fe2d..1bd2e093e 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index f43da68e2..cfd7e5d51 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 8836ede8b..dea897e23 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent com.atlassian.commonmark - 0.12.1 + 0.12.2-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 522a942b1..507dc818c 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 39b7322e1..ea12bec0f 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 6f1a102c6..980bf29ef 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 5e698d3e2..1b15e2d82 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.atlassian.commonmark commonmark-parent - 0.12.1 + 0.12.2-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ com.atlassian.commonmark commonmark - 0.12.1 + 0.12.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-autolink - 0.12.1 + 0.12.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-ins - 0.12.1 + 0.12.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-strikethrough - 0.12.1 + 0.12.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-gfm-tables - 0.12.1 + 0.12.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-heading-anchor - 0.12.1 + 0.12.2-SNAPSHOT com.atlassian.commonmark commonmark-ext-yaml-front-matter - 0.12.1 + 0.12.2-SNAPSHOT com.atlassian.commonmark commonmark-test-util - 0.12.1 + 0.12.2-SNAPSHOT @@ -195,7 +195,7 @@ scm:git:git@github.com:atlassian/commonmark-java.git scm:git:git@github.com:atlassian/commonmark-java.git https://github.com/atlassian/commonmark-java - commonmark-parent-0.12.1 + HEAD From e095e47e4748ad0a0e9b1875a86c306471b7e8c2 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 13 Nov 2018 17:09:57 +1100 Subject: [PATCH 317/815] Bump version in README --- CHANGELOG.md | 4 ++-- README.md | 2 +- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c43329e7b..0fd28128c 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. -## [0.12.0] - 2018-11-13 +## [0.12.1] - 2018-11-13 ### Changed - Speed up parsing significantly: Compared to the previous version, the benchmarks show up to 55% faster parsing for both small and large @@ -225,7 +225,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[0.12.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.11.0...commonmark-parent-0.12.0 +[0.12.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.11.0...commonmark-parent-0.12.1 [0.11.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.10.0...commonmark-parent-0.11.0 [0.10.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.9.0...commonmark-parent-0.10.0 [0.9.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.8.0...commonmark-parent-0.9.0 diff --git a/README.md b/README.md index 2101f0186..f131deba3 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Coordinates for core library (see all on [Maven Central]): com.atlassian.commonmark commonmark - 0.11.0 + 0.12.1 ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index 9558176be..8bfddd864 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -8,7 +8,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.9.0" >> test.properties +echo "version.maven=0.12.1" >> test.properties echo "version.maven_autolink=0.10.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 6c7fda569..aad925daf 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.11.0 +version.maven=0.12.1 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.10.0 # Version number of commonmark and extensions in project. -version.snapshot=0.11.1-SNAPSHOT +version.snapshot=0.12.2-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.10.0 ``` From 941578f4aff1dbaa7265c791d6b92a16a7236c1b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 16 Nov 2018 17:14:48 +1100 Subject: [PATCH 318/815] Add test for table in issue #142 --- .../commonmark/ext/gfm/tables/TablesTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 1e47300cd..00b26c438 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 @@ -373,6 +373,23 @@ public void tableEndWithoutEmptyLine() { "

    table, you are over

    \n"); } + @Test + public void issue142() { + assertRendering("||Alveolar|Bilabial\n" + + "|:--|:-:|:-:\n" + + "|**Plosive**|t, d|b\n" + + "|**Tap**|ɾ|", + "
    AbcDef
    \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
    AlveolarBilabial
    Plosivet, db
    Tapɾ
    \n"); + } + @Test public void attributeProviderIsApplied() { AttributeProviderFactory factory = new AttributeProviderFactory() { From d20ff758db51b8f669300af3645cfe2778ba5499 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 22 Nov 2018 11:58:11 +1100 Subject: [PATCH 319/815] Escape backtick --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd28128c..4d911b1ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ with the exception that 0.x versions can break between minor versions. - Parse backslash followed by unescapable character the same way as the reference implementations. - Build and test on Java 11 as well. -- autolink: Stop URLs at " and ` as well +- autolink: Stop URLs at " and \` as well ### Fixed - Fix tab handling in ATX and Setext headings. From 3e1f55ed728a08f424b86088ac3331566d92cbb6 Mon Sep 17 00:00:00 2001 From: Ben Humphreys Date: Thu, 27 Dec 2018 19:28:45 +1100 Subject: [PATCH 320/815] Add a huge horizontal rule testcase In version 0.11.0 a huge horizontal rule of thousands of characters caused a stack overflow error. In 0.12.0 that was fixed in commit ad397cb79 when ThematicBreakParser.isThematicBreak() was modified to avoid using a regex. This regression test fails on 0.11.0 and passes on 0.12.0 and later. --- .../test/java/org/commonmark/test/PathologicalTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index bd28e578e..8c5d57dd4 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -99,4 +99,11 @@ public void nestedBlockQuotes() { repeat("
    \n", x) + "

    a

    \n" + repeat("
    \n", x)); } + + @Test + public void hugeHorizontalRule() { + assertRendering( + repeat("*", 10000) + "\n", + "
    \n"); + } } From 9ebc5dcc09bf63fa73a78814b0ad421abc9239bb Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Wed, 16 Jan 2019 10:58:08 +1100 Subject: [PATCH 321/815] Check non-null arguments early and provide a nicer message If not for Android we could just use Objects.requireNonNull, but eh. --- CHANGELOG.md | 4 +++ .../java/org/commonmark/parser/Parser.java | 25 +++++++++++++++++-- .../renderer/html/HtmlRenderer.java | 15 +++++++++++ .../commonmark/renderer/html/HtmlWriter.java | 3 +++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d911b1ae..cb5f8c178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## Unreleased +### Changed +- Check non-null arguments early and provide a nicer message + ## [0.12.1] - 2018-11-13 ### Changed - Speed up parsing significantly: Compared to the previous version, the diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 349bdbec0..04d28065f 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -54,10 +54,13 @@ public static Builder builder() { *

    * This method is thread-safe (a new parser state is used for each invocation). * - * @param input the text to parse + * @param input the text to parse - must not be null * @return the root node */ public Node parse(String input) { + if (input == null) { + throw new NullPointerException("input must not be null"); + } InlineParser inlineParser = getInlineParser(); DocumentParser documentParser = new DocumentParser(blockParserFactories, inlineParser); Node document = documentParser.parse(input); @@ -78,11 +81,14 @@ public Node parse(String input) { *

    * This method is thread-safe (a new parser state is used for each invocation). * - * @param input the reader to parse + * @param input the reader to parse - must not be null * @return the root node * @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"); + } InlineParser inlineParser = getInlineParser(); DocumentParser documentParser = new DocumentParser(blockParserFactories, inlineParser); Node document = documentParser.parse(input); @@ -141,6 +147,9 @@ public Parser build() { * @return {@code this} */ public Builder extensions(Iterable extensions) { + if (extensions == null) { + throw new NullPointerException("extensions must not be null"); + } for (Extension extension : extensions) { if (extension instanceof ParserExtension) { ParserExtension parserExtension = (ParserExtension) extension; @@ -178,6 +187,9 @@ public Builder extensions(Iterable extensions) { * @return {@code this} */ public Builder enabledBlockTypes(Set> enabledBlockTypes) { + if (enabledBlockTypes == null) { + throw new NullPointerException("enabledBlockTypes must not be null"); + } this.enabledBlockTypes = enabledBlockTypes; return this; } @@ -193,6 +205,9 @@ public Builder enabledBlockTypes(Set> enabledBlockTypes) * @return {@code this} */ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { + if (blockParserFactory == null) { + throw new NullPointerException("blockParserFactory must not be null"); + } blockParserFactories.add(blockParserFactory); return this; } @@ -208,11 +223,17 @@ public Builder customBlockParserFactory(BlockParserFactory blockParserFactory) { * @return {@code this} */ public Builder customDelimiterProcessor(DelimiterProcessor delimiterProcessor) { + if (delimiterProcessor == null) { + throw new NullPointerException("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"); + } postProcessors.add(postProcessor); return this; } 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 b830e8b3e..fac3b3ba2 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -59,12 +59,18 @@ public static Builder builder() { @Override public void render(Node node, Appendable output) { + if (node == null) { + throw new NullPointerException("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"); + } StringBuilder sb = new StringBuilder(); render(node, sb); return sb.toString(); @@ -144,6 +150,9 @@ public Builder percentEncodeUrls(boolean percentEncodeUrls) { * @return {@code this} */ public Builder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) { + if (attributeProviderFactory == null) { + throw new NullPointerException("attributeProviderFactory must not be null"); + } this.attributeProviderFactories.add(attributeProviderFactory); return this; } @@ -159,6 +168,9 @@ public Builder attributeProviderFactory(AttributeProviderFactory attributeProvid * @return {@code this} */ public Builder nodeRendererFactory(HtmlNodeRendererFactory nodeRendererFactory) { + if (nodeRendererFactory == null) { + throw new NullPointerException("nodeRendererFactory must not be null"); + } this.nodeRendererFactories.add(nodeRendererFactory); return this; } @@ -168,6 +180,9 @@ public Builder nodeRendererFactory(HtmlNodeRendererFactory nodeRendererFactory) * @return {@code this} */ public Builder extensions(Iterable extensions) { + if (extensions == null) { + throw new NullPointerException("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 46b65a2a5..ec38e8f39 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java @@ -14,6 +14,9 @@ public class HtmlWriter { private char lastChar = 0; public HtmlWriter(Appendable out) { + if (out == null) { + throw new NullPointerException("out must not be null"); + } this.buffer = out; } From 88aa226b13922ef93dc81fe65e15da3c9dd780c3 Mon Sep 17 00:00:00 2001 From: Jake Date: Thu, 31 Jan 2019 17:13:42 -0600 Subject: [PATCH 322/815] Update gfm-table version in example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f131deba3..e37011eab 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ First, add an additional dependency (see [Maven Central] for others): com.atlassian.commonmark commonmark-ext-gfm-tables - 0.9.0 + 0.12.1 ``` From 7b80b7fabe94554d2a85e8fa13472e1f18f15939 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 28 Mar 2019 12:37:15 +1100 Subject: [PATCH 323/815] Add test for #151 --- .../org/commonmark/test/FencedCodeBlockParserTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java index 036af3d92..1a64a6374 100644 --- a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java @@ -48,6 +48,16 @@ public void closingCanNotHaveNonSpaces() { "

    code\n``` a\n
    \n"); } + @Test + public void issue151() { + assertRendering("```\nthis code\n\nshould not have BRs or paragraphs in it\nok\n```", + "
    this code\n" +
    +                        "\n" +
    +                        "should not have BRs or paragraphs in it\n" +
    +                        "ok\n" +
    +                        "
    \n"); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); From 8b0fd7c73afaf1756edb6412c15a75cc423a6ba9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 28 Mar 2019 14:05:50 +1100 Subject: [PATCH 324/815] Add test for only rendering the contents of a paragraph (without

    ) --- .../org/commonmark/test/HtmlRendererTest.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 50f321efe..6ccfe5465 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -1,9 +1,6 @@ package org.commonmark.test; -import org.commonmark.node.FencedCodeBlock; -import org.commonmark.node.Image; -import org.commonmark.node.Link; -import org.commonmark.node.Node; +import org.commonmark.node.*; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.*; @@ -207,6 +204,24 @@ public void imageAltTextWithEntities() { defaultRenderer().render(parse("![foo ä](/url)\n"))); } + @Test + public void canRenderContentsOfSingleParagraph() { + Node paragraphs = parse("Here I have a test [link](http://www.google.com)"); + Node paragraph = paragraphs.getFirstChild(); + + Document document = new Document(); + Node child = paragraph.getFirstChild(); + while (child != null) { + Node current = child; + child = current.getNext(); + + document.appendChild(current); + } + + assertEquals("Here I have a test link", + defaultRenderer().render(document)); + } + @Test public void threading() throws Exception { Parser parser = Parser.builder().build(); From 4828d221c2fa9e7cec819e7204d144ba8b63731b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Apr 2019 17:51:44 +1000 Subject: [PATCH 325/815] Remove unnecessary field/side effect in InlineParserImpl Returning the node is nicer. We don't actually need to access the block anywhere else except in the top level parse. I'm planning to extract parsing for link reference definitions out, this is a step in that direction. --- .../commonmark/internal/InlineParserImpl.java | 197 ++++++++---------- 1 file changed, 92 insertions(+), 105 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 78cbacd82..c6bbacc1a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -74,8 +74,6 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { */ private Map referenceMap = new HashMap<>(); - private Node block; - private String input; private int index; @@ -163,16 +161,21 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr */ @Override public void parse(String content, Node block) { - this.block = block; this.input = content.trim(); this.index = 0; this.lastDelimiter = null; this.lastBracket = null; - boolean moreToParse; - do { - moreToParse = parseInline(); - } while (moreToParse); + Node previous = null; + while (true) { + Node node = parseInline(previous); + previous = node; + if (node != null) { + block.appendChild(node); + } else { + break; + } + } processDelimiters(null); mergeChildTextNodes(block); @@ -252,75 +255,73 @@ public int parseReference(String s) { return index - startIndex; } - private Text appendText(CharSequence text, int beginIndex, int endIndex) { - return appendText(text.subSequence(beginIndex, endIndex)); - } - - private Text appendText(CharSequence text) { - Text node = new Text(text.toString()); - appendNode(node); - return node; + private Text text(String text, int beginIndex, int endIndex) { + return new Text(text.substring(beginIndex, endIndex)); } - private void appendNode(Node node) { - block.appendChild(node); + private Text text(String text) { + return new Text(text); } /** * Parse the next inline element in subject, advancing input index. - * On success, add the result to block's children and return true. - * On failure, return false. + * On success, return the new inline node. + * On failure, return null. */ - private boolean parseInline() { - boolean res; + private Node parseInline(Node previous) { char c = peek(); if (c == '\0') { - return false; + return null; } + + Node node; switch (c) { case '\n': - res = parseNewline(); + node = parseNewline(previous); break; case '\\': - res = parseBackslash(); + node = parseBackslash(); break; case '`': - res = parseBackticks(); + node = parseBackticks(); break; case '[': - res = parseOpenBracket(); + node = parseOpenBracket(); break; case '!': - res = parseBang(); + node = parseBang(); break; case ']': - res = parseCloseBracket(); + node = parseCloseBracket(); break; case '<': - res = parseAutolink() || parseHtmlInline(); + node = parseAutolink(); + if (node == null) { + node = parseHtmlInline(); + } break; case '&': - res = parseEntity(); + node = parseEntity(); break; default: boolean isDelimiter = delimiterCharacters.get(c); if (isDelimiter) { DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c); - res = parseDelimiters(delimiterProcessor, c); + node = parseDelimiters(delimiterProcessor, c); } else { - res = parseString(); + node = parseString(); } break; } - if (!res) { + if (node != null) { + return node; + } else { index++; // When we get here, it's only for a single special character that turned out to not have a special meaning. // So we shouldn't have a single surrogate here, hence it should be ok to turn it into a String. String literal = String.valueOf(c); - appendText(literal); + return text(literal); } - - return true; } /** @@ -355,65 +356,62 @@ private char peek() { /** * Parse zero or more space characters, including at most one newline. */ - private boolean spnl() { + private void spnl() { match(SPNL); - return true; } /** * Parse a newline. If it was preceded by two spaces, return a hard line break; otherwise a soft line break. */ - private boolean parseNewline() { + private Node parseNewline(Node previous) { index++; // assume we're at a \n - Node lastChild = block.getLastChild(); // Check previous text for trailing spaces. // The "endsWith" is an optimization to avoid an RE match in the common case. - if (lastChild != null && lastChild instanceof Text && ((Text) lastChild).getLiteral().endsWith(" ")) { - Text text = (Text) lastChild; + if (previous instanceof Text && ((Text) previous).getLiteral().endsWith(" ")) { + Text text = (Text) previous; String literal = text.getLiteral(); Matcher matcher = FINAL_SPACE.matcher(literal); int spaces = matcher.find() ? matcher.end() - matcher.start() : 0; if (spaces > 0) { text.setLiteral(literal.substring(0, literal.length() - spaces)); } - appendNode(spaces >= 2 ? new HardLineBreak() : new SoftLineBreak()); + if (spaces >= 2) { + return new HardLineBreak(); + } else { + return new SoftLineBreak(); + } } else { - appendNode(new SoftLineBreak()); + return new SoftLineBreak(); } - - // gobble leading spaces in next line - while (peek() == ' ') { - index++; - } - return true; } /** * Parse a backslash-escaped special character, adding either the escaped character, a hard line break * (if the backslash is followed by a newline), or a literal backslash to the block's children. */ - private boolean parseBackslash() { + private Node parseBackslash() { index++; + Node node; if (peek() == '\n') { - appendNode(new HardLineBreak()); + node = new HardLineBreak(); index++; } else if (index < input.length() && ESCAPABLE.matcher(input.substring(index, index + 1)).matches()) { - appendText(input, index, index + 1); + node = text(input, index, index + 1); index++; } else { - appendText("\\"); + node = text("\\"); } - return true; + return node; } /** - * Attempt to parse backticks, adding either a backtick code span or a literal sequence of backticks. + * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. */ - private boolean parseBackticks() { + private Node parseBackticks() { String ticks = match(TICKS_HERE); if (ticks == null) { - return false; + return null; } int afterOpenTicks = index; String matched; @@ -423,29 +421,27 @@ private boolean parseBackticks() { String content = input.substring(afterOpenTicks, index - ticks.length()); String literal = WHITESPACE.matcher(content.trim()).replaceAll(" "); node.setLiteral(literal); - appendNode(node); - return true; + return node; } } // If we got here, we didn't match a closing backtick sequence. index = afterOpenTicks; - appendText(ticks); - return true; + return text(ticks); } /** * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ - private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar); if (res == null) { - return false; + return null; } int length = res.count; int startIndex = index; index += length; - Text node = appendText(input, startIndex, index); + Text node = text(input, startIndex, index); // Add entry to stack for this opener lastDelimiter = new Delimiter(node, delimiterChar, res.canOpen, res.canClose, lastDelimiter); @@ -455,49 +451,50 @@ private boolean parseDelimiters(DelimiterProcessor delimiterProcessor, char deli lastDelimiter.previous.next = lastDelimiter; } - return true; + return node; } /** * Add open bracket to delimiter stack and add a text node to block's children. */ - private boolean parseOpenBracket() { + private Node parseOpenBracket() { int startIndex = index; index++; - Text node = appendText("["); + Text node = text("["); // Add entry to stack for this opener addBracket(Bracket.link(node, startIndex, lastBracket, lastDelimiter)); - return true; + return node; } /** * 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 boolean parseBang() { + private Node parseBang() { int startIndex = index; index++; if (peek() == '[') { index++; - Text node = appendText("!["); + Text node = text("!["); // Add entry to stack for this opener addBracket(Bracket.image(node, startIndex + 1, lastBracket, lastDelimiter)); + + return node; } else { - appendText("!"); + return text("!"); } - return true; } /** - * Try to match close bracket against an opening in the delimiter stack. Add either a link or image, or a - * plain [ character, to block's children. If there is a matching delimiter, remove it from the delimiter stack. + * Try to match close bracket against an opening in the delimiter stack. Return either a link or image, or a + * plain [ character. If there is a matching delimiter, remove it from the delimiter stack. */ - private boolean parseCloseBracket() { + private Node parseCloseBracket() { index++; int startIndex = index; @@ -505,15 +502,13 @@ private boolean parseCloseBracket() { Bracket opener = lastBracket; if (opener == null) { // No matching opener, just return a literal. - appendText("]"); - return true; + return text("]"); } if (!opener.allowed) { // Matching opener but it's not allowed, just return a literal. - appendText("]"); removeLastBracket(); - return true; + return text("]"); } // Check to see if we have a link/image @@ -578,7 +573,6 @@ private boolean parseCloseBracket() { linkOrImage.appendChild(node); node = next; } - appendNode(linkOrImage); // Process delimiters such as emphasis inside link/image processDelimiters(opener.previousDelimiter); @@ -599,15 +593,13 @@ private boolean parseCloseBracket() { } } - return true; + return linkOrImage; } else { // no link or image - - appendText("]"); + index = startIndex; removeLastBracket(); - index = startIndex; - return true; + return text("]"); } } @@ -708,57 +700,53 @@ private int parseLinkLabel() { /** * Attempt to parse an autolink (URL or email in pointy brackets). */ - private boolean parseAutolink() { + private Node parseAutolink() { String m; if ((m = match(EMAIL_AUTOLINK)) != null) { String dest = m.substring(1, m.length() - 1); Link node = new Link("mailto:" + dest, null); node.appendChild(new Text(dest)); - appendNode(node); - return true; + return node; } else if ((m = match(AUTOLINK)) != null) { String dest = m.substring(1, m.length() - 1); Link node = new Link(dest, null); node.appendChild(new Text(dest)); - appendNode(node); - return true; + return node; } else { - return false; + return null; } } /** * Attempt to parse inline HTML. */ - private boolean parseHtmlInline() { + private Node parseHtmlInline() { String m = match(HTML_TAG); if (m != null) { HtmlInline node = new HtmlInline(); node.setLiteral(m); - appendNode(node); - return true; + return node; } else { - return false; + return null; } } /** - * Attempt to parse an entity, return Entity object if successful. + * Attempt to parse a HTML style entity. */ - private boolean parseEntity() { + private Node parseEntity() { String m; if ((m = match(ENTITY_HERE)) != null) { - appendText(Html5Entities.entityToString(m)); - return true; + return text(Html5Entities.entityToString(m)); } else { - return false; + return null; } } /** * Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string. */ - private boolean parseString() { + private Node parseString() { int begin = index; int length = input.length(); while (index != length) { @@ -768,10 +756,9 @@ private boolean parseString() { index++; } if (begin != index) { - appendText(input, begin, index); - return true; + return text(input, begin, index); } else { - return false; + return null; } } From 6c3bec2c968d7ed1d7b5c61ddb9a5decd72b5582 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Apr 2019 14:33:10 +1000 Subject: [PATCH 326/815] Update spec to CommonMark 0.29, sync regression tests --- .../src/main/resources/cmark-regression.txt | 67 ++- .../resources/commonmark.js-regression.txt | 53 +- .../src/main/resources/spec.txt | 548 ++++++++++++++---- 3 files changed, 535 insertions(+), 133 deletions(-) diff --git a/commonmark-test-util/src/main/resources/cmark-regression.txt b/commonmark-test-util/src/main/resources/cmark-regression.txt index 2984a3bef..62b1e7efe 100644 --- a/commonmark-test-util/src/main/resources/cmark-regression.txt +++ b/commonmark-test-util/src/main/resources/cmark-regression.txt @@ -4,8 +4,7 @@ Issue #113: EOL character weirdness on Windows (Important: first line ends with CR + CR + LF) ```````````````````````````````` example -line1 - +line1 line2 .

    line1

    @@ -82,7 +81,8 @@ Issue #193 - unescaped left angle brackets in link destination [a]: . -

    a

    +

    [a]

    +

    [a]: <te

    ```````````````````````````````` Issue #192 - escaped spaces in link destination @@ -93,3 +93,64 @@ Issue #192 - escaped spaces in link destination .

    [a](te\ st)

    ```````````````````````````````` + +Issue #527 - meta tags in inline contexts + +```````````````````````````````` example +City: + + + +. +

    City: + + +

    +```````````````````````````````` + +Issue #530 - link parsing corner cases + +```````````````````````````````` example +[a](\ b) + +[a](<[a](\ b)

    +

    [a](<<b)

    +

    [a](<b +)

    +```````````````````````````````` + +Issue commonmark#526 - unescaped ( in link title + +```````````````````````````````` example +[link](url ((title)) +. +

    [link](url ((title))

    +```````````````````````````````` + +Issue commonamrk#517 - script, pre, style close tag without +opener. + +```````````````````````````````` example + + +
    + + +. + + + +```````````````````````````````` + +Issue #289. + +```````````````````````````````` example +[a]( +. +

    [a](<b) c>

    +```````````````````````````````` diff --git a/commonmark-test-util/src/main/resources/commonmark.js-regression.txt b/commonmark-test-util/src/main/resources/commonmark.js-regression.txt index 7300952fe..a99620bb1 100644 --- a/commonmark-test-util/src/main/resources/commonmark.js-regression.txt +++ b/commonmark-test-util/src/main/resources/commonmark.js-regression.txt @@ -15,10 +15,10 @@ bar Type 7 HTML block followed by whitespace (#98). ```````````````````````````````` example - + x . - + x ```````````````````````````````` @@ -95,10 +95,55 @@ Issue #116 - tabs before and after ATX closing heading

    foo

    ```````````````````````````````` -commonmark/CommonMark#493 - escaped space not allowed in link -destination. +commonmark/CommonMark#493 - escaped space not allowed in link destination. + ```````````````````````````````` example [link](a\ b) .

    [link](a\ b)

    ```````````````````````````````` + +Issue #527 - meta tags in inline contexts + +```````````````````````````````` example +City: + + + +. +

    City: + + +

    +```````````````````````````````` + +Double-encoding. + +```````````````````````````````` example +[XSS](javascript&colon;alert%28'XSS'%29) +. +

    XSS

    +```````````````````````````````` + +Issue commonamrk#517 - script, pre, style close tag without +opener. + +```````````````````````````````` example + + + + + +. + + + +```````````````````````````````` + +Issue #289. + +```````````````````````````````` example +[a]( +. +

    [a](<b) c>

    +```````````````````````````````` diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index 9fd584139..3913de442 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.28 -date: '2017-08-01' +version: 0.29 +date: '2019-04-06' license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -248,7 +248,7 @@ satisfactory replacement for a spec. Because there is no unambiguous spec, implementations have diverged considerably. As a result, users are often surprised to find that -a document that renders one way on one system (say, a github wiki) +a document that renders one way on one system (say, a GitHub wiki) renders differently on another (say, converting to docbook using pandoc). To make matters worse, because nothing in Markdown counts as a "syntax error," the divergence often isn't discovered right away. @@ -328,8 +328,10 @@ that is not a [whitespace character]. An [ASCII punctuation character](@) is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, -`*`, `+`, `,`, `-`, `.`, `/`, `:`, `;`, `<`, `=`, `>`, `?`, `@`, -`[`, `\`, `]`, `^`, `_`, `` ` ``, `{`, `|`, `}`, or `~`. +`*`, `+`, `,`, `-`, `.`, `/` (U+0021–2F), +`:`, `;`, `<`, `=`, `>`, `?`, `@` (U+003A–0040), +`[`, `\`, `]`, `^`, `_`, `` ` `` (U+005B–0060), +`{`, `|`, `}`, or `~` (U+007B–007E). A [punctuation character](@) is an [ASCII punctuation character] or anything in @@ -514,8 +516,8 @@ one block element does not affect the inline parsing of any other. ## Container blocks and leaf blocks We can divide blocks into two types: -[container block](@)s, -which can contain other blocks, and [leaf block](@)s, +[container blocks](@), +which can contain other blocks, and [leaf blocks](@), which cannot. # Leaf blocks @@ -527,7 +529,7 @@ Markdown document. A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching `-`, `_`, or `*` characters, each followed -optionally by any number of spaces, forms a +optionally by any number of spaces or tabs, forms a [thematic break](@). ```````````````````````````````` example @@ -825,7 +827,7 @@ Contents are parsed as inlines: ```````````````````````````````` -Leading and trailing blanks are ignored in parsing inline content: +Leading and trailing [whitespace] is ignored in parsing inline content: ```````````````````````````````` example # foo @@ -1024,6 +1026,20 @@ baz* baz
    ```````````````````````````````` +The contents are the result of parsing the headings's raw +content as inlines. The heading's raw content is formed by +concatenating the lines and removing initial and final +[whitespace]. + +```````````````````````````````` example + Foo *bar +baz*→ +==== +. +

    Foo bar +baz

    +```````````````````````````````` + The underlining can be any length: @@ -1584,8 +1600,8 @@ begins with a code fence, indented no more than three spaces. The line with the opening code fence may optionally contain some text following the code fence; this is trimmed of leading and trailing -spaces and called the [info string](@). -The [info string] may not contain any backtick +whitespace and called the [info string](@). If the [info string] comes +after a backtick fence, it may not contain any backtick characters. (The reason for this restriction is that otherwise some inline code would be incorrectly interpreted as the beginning of a fenced code block.) @@ -1870,7 +1886,7 @@ Code fences (opening and closing) cannot contain internal spaces: ``` ``` aaa . -

    +

    aaa

    ```````````````````````````````` @@ -1922,9 +1938,11 @@ bar An [info string] can be provided after the opening code fence. -Opening and closing spaces will be stripped, and the first word, prefixed -with `language-`, is used as the value for the `class` attribute of the -`code` element within the enclosing `pre` element. +Although this spec doesn't mandate any particular treatment of +the info string, the first word is typically used to specify +the language of the code block. In HTML output, the language is +normally indicated by adding a class to the `code` element consisting +of `language-` followed by the language name. ```````````````````````````````` example ```ruby @@ -1973,6 +1991,18 @@ foo

    ```````````````````````````````` +[Info strings] for tilde code blocks can contain backticks and tildes: + +```````````````````````````````` example +~~~ aa ``` ~~~ +foo +~~~ +. +
    foo
    +
    +```````````````````````````````` + + Closing code fences cannot have [info strings]: ```````````````````````````````` example @@ -1991,14 +2021,15 @@ Closing code fences cannot have [info strings]: An [HTML block](@) is a group of lines that is treated as raw HTML (and will not be escaped in HTML output). -There are seven kinds of [HTML block], which can be defined -by their start and end conditions. The block begins with a line that -meets a [start condition](@) (after up to three spaces -optional indentation). It ends with the first subsequent line that -meets a matching [end condition](@), or the last line of -the document or other [container block]), if no line is encountered that meets the -[end condition]. If the first line meets both the [start condition] -and the [end condition], the block will contain just that line. +There are seven kinds of [HTML block], which can be defined by their +start and end conditions. The block begins with a line that meets a +[start condition](@) (after up to three spaces optional indentation). +It ends with the first subsequent line that meets a matching [end +condition](@), or the last line of the document, or the last line of +the [container block](#container-blocks) containing the current HTML +block, if no line is encountered that meets the [end condition]. If +the first line meets both the [start condition] and the [end +condition], the block will contain just that line. 1. **Start condition:** line begins with the string ``, or @@ -2037,16 +2068,17 @@ the string `/>`.\ **End condition:** line is followed by a [blank line]. 7. **Start condition:** line begins with a complete [open tag] -or [closing tag] (with any [tag name] other than `script`, -`style`, or `pre`) followed only by [whitespace] -or the end of the line.\ +(with any [tag name] other than `script`, +`style`, or `pre`) or a complete [closing tag], +followed only by [whitespace] or the end of the line.\ **End condition:** line is followed by a [blank line]. HTML blocks continue until they are closed by their appropriate -[end condition], or the last line of the document or other [container block]. -This means any HTML **within an HTML block** that might otherwise be recognised -as a start condition will be ignored by the parser and passed through as-is, -without changing the parser's state. +[end condition], or the last line of the document or other [container +block](#container-blocks). This means any HTML **within an HTML +block** that might otherwise be recognised as a start condition will +be ignored by the parser and passed through as-is, without changing +the parser's state. For instance, `
    ` within a HTML block started by `` will not affect
     the parser state; as the HTML block was started in by start condition 6, it
    @@ -2069,7 +2101,7 @@ _world_.
     
    ```````````````````````````````` -In this case, the HTML block is terminated by the newline — the `**hello**` +In this case, the HTML block is terminated by the newline — the `**Hello**` text remains verbatim — and regular parsing resumes, with a paragraph, emphasised `world` and inline and block HTML following. @@ -2612,7 +2644,8 @@ bar However, a following blank line is needed, except at the end of -a document, and except for blocks of types 1--5, above: +a document, and except for blocks of types 1--5, [above][HTML +block]: ```````````````````````````````` example
    @@ -2758,8 +2791,8 @@ an indented code block: Fortunately, blank lines are usually not necessary and can be deleted. The exception is inside `
    ` tags, but as described
    -above, raw HTML blocks starting with `
    ` *can* contain blank
    -lines.
    +[above][HTML blocks], raw HTML blocks starting with `
    `
    +*can* contain blank lines.
     
     ## Link reference definitions
     
    @@ -2811,7 +2844,7 @@ them.
     
     ```````````````````````````````` example
     [Foo bar]:
    -
    +
     'title'
     
     [Foo bar]
    @@ -2877,6 +2910,29 @@ The link destination may not be omitted:
     

    [foo]

    ```````````````````````````````` + However, an empty link destination may be specified using + angle brackets: + +```````````````````````````````` example +[foo]: <> + +[foo] +. +

    foo

    +```````````````````````````````` + +The title must be separated from the link destination by +whitespace: + +```````````````````````````````` example +[foo]: (baz) + +[foo] +. +

    [foo]: (baz)

    +

    [foo]

    +```````````````````````````````` + Both title and destination can contain backslash escapes and literal backslashes: @@ -3034,6 +3090,25 @@ and thematic breaks, and it need not be followed by a blank line.
    ```````````````````````````````` +```````````````````````````````` example +[foo]: /url +bar +=== +[foo] +. +

    bar

    +

    foo

    +```````````````````````````````` + +```````````````````````````````` example +[foo]: /url +=== +[foo] +. +

    === +foo

    +```````````````````````````````` + Several [link reference definitions] can occur one after another, without intervening blank lines. @@ -3070,6 +3145,17 @@ are defined: ```````````````````````````````` +Whether something is a [link reference definition] is +independent of whether the link reference it defines is +used in the document. Thus, for example, the following +document contains just a link reference definition, and +no visible content: + +```````````````````````````````` example +[foo]: /url +. +```````````````````````````````` + ## Paragraphs @@ -3207,7 +3293,7 @@ aaa # Container blocks -A [container block] is a block that has other +A [container block](#container-blocks) is a block that has other blocks as its contents. There are two basic kinds of container blocks: [block quotes] and [list items]. [Lists] are meta-containers for [list items]. @@ -3669,9 +3755,8 @@ in some browsers.) The following rules define [list items]: 1. **Basic case.** If a sequence of lines *Ls* constitute a sequence of - blocks *Bs* starting with a [non-whitespace character] and not separated - from each other by more than one blank line, and *M* is a list - marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces, then the result + blocks *Bs* starting with a [non-whitespace character], and *M* is a + list marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces, 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 list item with *Bs* as its contents. The type of the list item @@ -3981,8 +4066,7 @@ A start number may not be negative: 2. **Item starting with indented code.** If a sequence of lines *Ls* constitute a sequence of blocks *Bs* starting with an indented code - block and not separated from each other by more than one blank line, - and *M* is a list marker of width *W* followed by + block, and *M* is a list marker of width *W* followed by one space, then the result of prepending *M* and the following space to the first line of *Ls*, and indenting subsequent lines of *Ls* by *W + 1* spaces, is a list item with *Bs* as its contents. @@ -4458,9 +4542,10 @@ continued here.

    6. **That's all.** Nothing that is not counted as a list item by rules #1--5 counts as a [list item](#list-items). -The rules for sublists follow from the general rules above. A sublist -must be indented the same number of spaces a paragraph would need to be -in order to be included in the list item. +The rules for sublists follow from the general rules +[above][List items]. A sublist must be indented the same number +of spaces a paragraph would need to be in order to be included +in the list item. So, in this case we need two spaces indent: @@ -5049,11 +5134,9 @@ item: - b - c - d - - e - - f - - g - - h -- i + - e + - f +- g .
    • a
    • @@ -5063,8 +5146,6 @@ item:
    • e
    • f
    • g
    • -
    • h
    • -
    • i
    ```````````````````````````````` @@ -5074,7 +5155,7 @@ item: 2. b - 3. c + 3. c .
    1. @@ -5089,6 +5170,49 @@ item:
    ```````````````````````````````` +Note, however, that list items may not be indented more than +three spaces. Here `- e` is treated as a paragraph continuation +line, because it is indented more than three spaces: + +```````````````````````````````` example +- a + - b + - c + - d + - e +. +
      +
    • a
    • +
    • b
    • +
    • c
    • +
    • d +- e
    • +
    +```````````````````````````````` + +And here, `3. c` is treated as in indented code block, +because it is indented four spaces and preceded by a +blank line. + +```````````````````````````````` example +1. a + + 2. b + + 3. c +. +
      +
    1. +

      a

      +
    2. +
    3. +

      b

      +
    4. +
    +
    3. c
    +
    +```````````````````````````````` + This is a loose list, because there is a blank line between two of the list items: @@ -5378,10 +5502,10 @@ Thus, for example, in

    hilo`

    ```````````````````````````````` - `hi` is parsed as code, leaving the backtick at the end as a literal backtick. + ## Backslash escapes Any ASCII punctuation character may be backslash-escaped: @@ -5415,6 +5539,7 @@ not have their usual Markdown meanings: \* not a list \# not a heading \[foo]: /url "not a reference" +\ö not a character entity .

    *not emphasized* <br/> not a tag @@ -5423,7 +5548,8 @@ not have their usual Markdown meanings: 1. not a list * not a list # not a heading -[foo]: /url "not a reference"

    +[foo]: /url "not a reference" +&ouml; not a character entity

    ```````````````````````````````` @@ -5521,13 +5647,23 @@ foo ## Entity and numeric character references -All valid HTML entity references and numeric character -references, except those occuring in code blocks and code spans, -are recognized as such and treated as equivalent to the -corresponding Unicode characters. Conforming CommonMark parsers -need not store information about whether a particular character -was represented in the source using a Unicode character or -an entity reference. +Valid HTML entity references and numeric character references +can be used in place of the corresponding Unicode character, +with the following exceptions: + +- Entity and character references are not recognized in code + blocks and code spans. + +- Entity and character references cannot stand in place of + special characters that define structural elements in + CommonMark. For example, although `*` can be used + in place of a literal `*` character, `*` cannot replace + `*` in emphasis delimiters, bullet list markers, or thematic + breaks. + +Conforming CommonMark parsers need not store information about +whether a particular character was represented in the source +using a Unicode character or an entity reference. [Entity references](@) consist of `&` + any of the valid HTML5 entity names + `;`. The @@ -5548,22 +5684,22 @@ references and their corresponding code points. [Decimal numeric character references](@) -consist of `&#` + a string of 1--8 arabic digits + `;`. A +consist of `&#` + a string of 1--7 arabic digits + `;`. A numeric character reference is parsed as the corresponding Unicode character. Invalid Unicode code points will be replaced by the REPLACEMENT CHARACTER (`U+FFFD`). For security reasons, the code point `U+0000` will also be replaced by `U+FFFD`. ```````````````````````````````` example -# Ӓ Ϡ � � +# Ӓ Ϡ � . -

    # Ӓ Ϡ � �

    +

    # Ӓ Ϡ �

    ```````````````````````````````` [Hexadecimal numeric character references](@) consist of `&#` + -either `X` or `x` + a string of 1-8 hexadecimal digits + `;`. +either `X` or `x` + a string of 1-6 hexadecimal digits + `;`. They too are parsed as the corresponding Unicode character (this time specified with a hexadecimal numeral instead of decimal). @@ -5578,9 +5714,13 @@ Here are some nonentities: ```````````````````````````````` example   &x; &#; &#x; +� +&#abcdef0; &ThisIsNotDefined; &hi?; .

    &nbsp &x; &#; &#x; +&#987654321; +&#abcdef0; &ThisIsNotDefined; &hi?;

    ```````````````````````````````` @@ -5661,6 +5801,51 @@ text in code spans and code blocks: ```````````````````````````````` +Entity and numeric character references cannot be used +in place of symbols indicating structure in CommonMark +documents. + +```````````````````````````````` example +*foo* +*foo* +. +

    *foo* +foo

    +```````````````````````````````` + +```````````````````````````````` example +* foo + +* foo +. +

    * foo

    +
      +
    • foo
    • +
    +```````````````````````````````` + +```````````````````````````````` example +foo bar +. +

    foo + +bar

    +```````````````````````````````` + +```````````````````````````````` example + foo +. +

    →foo

    +```````````````````````````````` + + +```````````````````````````````` example +[a](url "tit") +. +

    [a](url "tit")

    +```````````````````````````````` + + ## Code spans A [backtick string](@) @@ -5669,9 +5854,16 @@ preceded nor followed by a backtick. A [code span](@) begins with a backtick string and ends with a backtick string of equal length. The contents of the code span are -the characters between the two backtick strings, with leading and -trailing spaces and [line endings] removed, and -[whitespace] collapsed to single spaces. +the characters between the two backtick strings, normalized in the +following ways: + +- First, [line endings] are converted to [spaces]. +- If the resulting string both begins *and* ends with a [space] + character, but does not consist entirely of [space] + characters, a single [space] character is removed from the + front and back. This allows you to include code that begins + or ends with backtick characters, which must be separated by + whitespace from the opening or closing backtick strings. This is a simple code span: @@ -5683,10 +5875,11 @@ This is a simple code span: Here two backticks are used, because the code contains a backtick. -This example also illustrates stripping of leading and trailing spaces: +This example also illustrates stripping of a single leading and +trailing space: ```````````````````````````````` example -`` foo ` bar `` +`` foo ` bar `` .

    foo ` bar

    ```````````````````````````````` @@ -5701,58 +5894,79 @@ spaces:

    ``

    ```````````````````````````````` +Note that only *one* space is stripped: -[Line endings] are treated like spaces: +```````````````````````````````` example +` `` ` +. +

    ``

    +```````````````````````````````` + +The stripping only happens if the space is on both +sides of the string: ```````````````````````````````` example -`` -foo -`` +` a` . -

    foo

    +

    a

    ```````````````````````````````` +Only [spaces], and not [unicode whitespace] in general, are +stripped in this way: + +```````````````````````````````` example +` b ` +. +

     b 

    +```````````````````````````````` -Interior spaces and [line endings] are collapsed into -single spaces, just as they would be by a browser: +No stripping occurs if the code span contains only spaces: ```````````````````````````````` example -`foo bar - baz` +` ` +` ` . -

    foo bar baz

    +

      +

    ```````````````````````````````` -Not all [Unicode whitespace] (for instance, non-breaking space) is -collapsed, however: +[Line endings] are treated like spaces: ```````````````````````````````` example -`a  b` +`` +foo +bar +baz +`` . -

    a  b

    +

    foo bar baz

    ```````````````````````````````` +```````````````````````````````` example +`` +foo +`` +. +

    foo

    +```````````````````````````````` -Q: Why not just leave the spaces, since browsers will collapse them -anyway? A: Because we might be targeting a non-HTML format, and we -shouldn't rely on HTML-specific rendering assumptions. -(Existing implementations differ in their treatment of internal -spaces and [line endings]. Some, including `Markdown.pl` and -`showdown`, convert an internal [line ending] into a -`
    ` tag. But this makes things difficult for those who like to -hard-wrap their paragraphs, since a line break in the midst of a code -span will cause an unintended line break in the output. Others just -leave internal spaces as they are, which is fine if only HTML is being -targeted.) +Interior spaces are not collapsed: ```````````````````````````````` example -`foo `` bar` +`foo bar +baz` . -

    foo `` bar

    +

    foo bar baz

    ```````````````````````````````` +Note that browsers will typically collapse consecutive spaces +when rendering `` elements, so it is recommended that +the following CSS be used: + + code{white-space: pre-wrap;} + Note that backslash escapes do not work in code spans. All backslashes are treated literally: @@ -5768,6 +5982,19 @@ Backslash escapes are never needed, because one can always choose a string of *n* backtick characters as delimiters, where the code does not contain any strings of exactly *n* backtick characters. +```````````````````````````````` example +``foo`bar`` +. +

    foo`bar

    +```````````````````````````````` + +```````````````````````````````` example +` foo `` bar ` +. +

    foo `` bar

    +```````````````````````````````` + + Code span backticks have higher precedence than any other inline constructs except HTML tags and autolinks. Thus, for example, this is not parsed as emphasized text, since the second `*` is part of a code @@ -5905,15 +6132,17 @@ of one or more `_` characters that is not preceded or followed by a non-backslash-escaped `_` character. A [left-flanking delimiter run](@) is -a [delimiter run] that is (a) not followed by [Unicode whitespace], -and (b) not followed by a [punctuation character], or +a [delimiter run] that is (1) not followed by [Unicode whitespace], +and either (2a) not followed by a [punctuation character], or +(2b) followed by a [punctuation character] and preceded by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. A [right-flanking delimiter run](@) is -a [delimiter run] that is (a) not preceded by [Unicode whitespace], -and (b) not preceded by a [punctuation character], or +a [delimiter run] that is (1) not preceded by [Unicode whitespace], +and either (2a) not preceded by a [punctuation character], or +(2b) preceded by a [punctuation character] and followed by [Unicode whitespace] or a [punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. @@ -6005,7 +6234,8 @@ The following rules define emphasis and strong emphasis: [delimiter runs]. If one of the delimiters can both open and close emphasis, then the sum of the lengths of the delimiter runs containing the opening and closing delimiters - must not be a multiple of 3. + must not be a multiple of 3 unless both lengths are + multiples of 3. 10. Strong emphasis begins with a delimiter that [can open strong emphasis] and ends with a delimiter that @@ -6015,7 +6245,8 @@ The following rules define emphasis and strong emphasis: [delimiter runs]. If one of the delimiters can both open and close strong emphasis, then the sum of the lengths of the delimiter runs containing the opening and closing - delimiters must not be a multiple of 3. + delimiters must not be a multiple of 3 unless both lengths + are multiples of 3. 11. A literal `*` character cannot occur at the beginning or end of `*`-delimited emphasis or `**`-delimited strong emphasis, unless it @@ -6634,7 +6865,19 @@ is precluded by the condition that a delimiter that can both open and close (like the `*` after `foo`) cannot form emphasis if the sum of the lengths of the delimiter runs containing the opening and -closing delimiters is a multiple of 3. +closing delimiters is a multiple of 3 unless +both lengths are multiples of 3. + + +For the same reason, we don't get two consecutive +emphasis sections in this example: + +```````````````````````````````` example +*foo**bar* +. +

    foo**bar

    +```````````````````````````````` + The same condition ensures that the following cases are all strong emphasis nested inside @@ -6663,6 +6906,23 @@ omitted: ```````````````````````````````` +When the lengths of the interior closing and opening +delimiter runs are *both* multiples of 3, though, +they can match to create emphasis: + +```````````````````````````````` example +foo***bar***baz +. +

    foobarbaz

    +```````````````````````````````` + +```````````````````````````````` example +foo******bar*********baz +. +

    foobar***baz

    +```````````````````````````````` + + Indefinite levels of nesting are possible: ```````````````````````````````` example @@ -7198,15 +7458,16 @@ following rules apply: A [link destination](@) consists of either - a sequence of zero or more characters between an opening `<` and a - closing `>` that contains no spaces, line breaks, or unescaped + closing `>` that contains no line breaks or unescaped `<` or `>` characters, or -- a nonempty sequence of characters that does not include - ASCII space or control characters, and includes parentheses - only if (a) they are backslash-escaped or (b) they are part of - a balanced pair of unescaped parentheses. (Implementations - may impose limits on parentheses nesting to avoid performance - issues, but at least three levels of nesting should be supported.) +- a nonempty sequence of characters that does not start with + `<`, does not include ASCII space or control characters, and + includes parentheses only if (a) they are backslash-escaped or + (b) they are part of a balanced pair of unescaped parentheses. + (Implementations may impose limits on parentheses nesting to + avoid performance issues, but at least three levels of nesting + should be supported.) A [link title](@) consists of either @@ -7219,7 +7480,8 @@ A [link title](@) consists of either backslash-escaped, or - a sequence of zero or more characters between matching parentheses - (`(...)`), including a `)` character only if it is backslash-escaped. + (`(...)`), including a `(` or `)` character only if it is + backslash-escaped. Although [link titles] may span multiple lines, they may not contain a [blank line]. @@ -7269,9 +7531,8 @@ Both the title and the destination may be omitted:

    link

    ```````````````````````````````` - -The destination cannot contain spaces or line breaks, -even if enclosed in pointy brackets: +The destination can only contain spaces if it is +enclosed in pointy brackets: ```````````````````````````````` example [link](/my uri) @@ -7279,13 +7540,14 @@ even if enclosed in pointy brackets:

    [link](/my uri)

    ```````````````````````````````` - ```````````````````````````````` example [link]() . -

    [link](</my uri>)

    +

    link

    ```````````````````````````````` +The destination cannot contain line breaks, +even if enclosed in pointy brackets: ```````````````````````````````` example [link](foo @@ -7295,7 +7557,6 @@ bar) bar)

    ```````````````````````````````` - ```````````````````````````````` example [link]() @@ -7304,6 +7565,36 @@ bar>) bar>)

    ```````````````````````````````` +The destination can contain `)` if it is enclosed +in pointy brackets: + +```````````````````````````````` example +[a]() +. +

    a

    +```````````````````````````````` + +Pointy brackets that enclose links must be unescaped: + +```````````````````````````````` example +[link]() +. +

    [link](<foo>)

    +```````````````````````````````` + +These are not links, because the opening pointy bracket +is not matched properly: + +```````````````````````````````` example +[a]( +[a](c) +. +

    [a](<b)c +[a](<b)c> +[a](c)

    +```````````````````````````````` + Parentheses inside the link destination may be escaped: ```````````````````````````````` example @@ -8411,7 +8702,7 @@ If you want a link after a literal `!`, backslash-escape the as the link label. A [URI autolink](@) consists of `<`, followed by an -[absolute URI] not containing `<`, followed by `>`. It is parsed as +[absolute URI] followed by `>`. It is parsed as a link to the URI, with the URI as the link's label. An [absolute URI](@), @@ -8624,7 +8915,7 @@ a [single-quoted attribute value], or a [double-quoted attribute value]. An [unquoted attribute value](@) is a nonempty string of characters not -including spaces, `"`, `'`, `=`, `<`, `>`, or `` ` ``. +including [whitespace], `"`, `'`, `=`, `<`, `>`, or `` ` ``. A [single-quoted attribute value](@) consists of `'`, zero or more @@ -8745,9 +9036,13 @@ Illegal [whitespace]: ```````````````````````````````` example < a>< foo> + .

    < a>< -foo><bar/ >

    +foo><bar/ > +<foo bar=baz +bim!bop />

    ```````````````````````````````` @@ -8944,10 +9239,10 @@ bar

    Line breaks do not occur inside code spans ```````````````````````````````` example -`code +`code span` . -

    code span

    +

    code span

    ```````````````````````````````` @@ -9365,7 +9660,8 @@ just above `stack_bottom` (or the first element if `stack_bottom` is NULL). We keep track of the `openers_bottom` for each delimiter -type (`*`, `_`). Initialize this to `stack_bottom`. +type (`*`, `_`) and each length of the closing delimiter run +(modulo 3). Initialize this to `stack_bottom`. Then we repeat the following until we run out of potential closers: @@ -9397,7 +9693,7 @@ closers: of the delimiter stack. If the closing node is removed, reset `current_position` to the next element in the stack. -- If none in found: +- If none is found: + Set `openers_bottom` to the element before `current_position`. (We know that there are no openers for this kind of closer up to and From d08f5dca84f810671520ea8fb777706aa2dc9a01 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Apr 2019 15:01:54 +1000 Subject: [PATCH 327/815] Change how newlines/spaces are handled in inline code (spec 0.29) --- .../commonmark/internal/InlineParserImpl.java | 14 ++++++++-- .../org/commonmark/internal/util/Parsing.java | 6 +++++ .../org/commonmark/test/SpecCrLfCoreTest.java | 26 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index c6bbacc1a..bcb21415d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -419,8 +419,18 @@ private Node parseBackticks() { if (matched.equals(ticks)) { Code node = new Code(); String content = input.substring(afterOpenTicks, index - ticks.length()); - String literal = WHITESPACE.matcher(content.trim()).replaceAll(" "); - node.setLiteral(literal); + content = content.replace('\n', ' '); + + // spec: If the resulting string both begins and ends with a space character, but does not consist + // entirely of space characters, a single space character is removed from the front and back. + if (content.length() >= 3 && + content.charAt(0) == ' ' && + content.charAt(content.length() - 1) == ' ' && + Parsing.hasNonSpace(content)) { + content = content.substring(1, content.length() - 1); + } + + node.setLiteral(content); return node; } } 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 f5cc888ee..94f77858d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -50,6 +50,12 @@ 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); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java new file mode 100644 index 000000000..6424ab659 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/SpecCrLfCoreTest.java @@ -0,0 +1,26 @@ +package org.commonmark.test; + +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.SpecTestCase; +import org.commonmark.testutil.example.Example; + +/** + * Same as {@link SpecCoreTest} but converts line endings to Windows-style CR+LF endings before parsing. + */ +public class SpecCrLfCoreTest extends SpecTestCase { + + private static final Parser PARSER = Parser.builder().build(); + // The spec says URL-escaping is optional, but the examples assume that it's enabled. + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); + + public SpecCrLfCoreTest(Example example) { + super(example); + } + + @Override + protected String render(String source) { + String windowsStyle = source.replace("\n", "\r\n"); + return RENDERER.render(PARSER.parse(windowsStyle)); + } +} From 62d5f1068fa3084a0f3757f54658e24336c6bd84 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Apr 2019 15:06:14 +1000 Subject: [PATCH 328/815] Info strings for tilde code blocks can contain backticks and tildes (spec 0.29) See https://github.com/commonmark/commonmark-spec/issues/119 --- .../org/commonmark/internal/FencedCodeBlockParser.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index 6352892cf..e57cc7277 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -102,17 +102,13 @@ private static FencedCodeBlockParser checkOpener(CharSequence line, int index, i } } if (backticks >= 3 && tildes == 0) { - // spec: The info string may not contain any backtick characters. + // spec: If the info string comes after a backtick fence, it may not contain any backtick characters. if (Parsing.find('`', line, index + backticks) != -1) { return null; } return new FencedCodeBlockParser('`', backticks, indent); } else if (tildes >= 3 && backticks == 0) { - // This follows commonmark.js but the spec is unclear about this: - // https://github.com/commonmark/CommonMark/issues/119 - if (Parsing.find('~', line, index + tildes) != -1) { - return null; - } + // spec: Info strings for tilde code blocks can contain backticks and tildes return new FencedCodeBlockParser('~', tildes, indent); } else { return null; From 9e10d399161b19a6b0dbe1bba192bb009f9bd996 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Apr 2019 15:17:43 +1000 Subject: [PATCH 329/815] Allow internal delim runs to match if both have lengths that are multiples of 3 (spec 0.29) --- .../internal/inline/EmphasisDelimiterProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 60f192bef..98b43938c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -33,7 +33,9 @@ public int getMinLength() { @Override public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { // "multiple of 3" rule for internal delimiter runs - if ((opener.canClose() || closer.canOpen()) && (opener.originalLength() + closer.originalLength()) % 3 == 0) { + if ((opener.canClose() || closer.canOpen()) && + closer.originalLength() % 3 != 0 && + (opener.originalLength() + closer.originalLength()) % 3 == 0) { return 0; } // calculate actual number of delimiters used from this closer From a3cc5f08860a75c4fc96be77379995ff80374d34 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Apr 2019 15:38:11 +1000 Subject: [PATCH 330/815] Fix pathological case with input `[\\\\...` (a lot of backslashes) --- .../java/org/commonmark/internal/InlineParserImpl.java | 3 ++- .../test/java/org/commonmark/test/PathologicalTest.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index bcb21415d..d236cca31 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -39,7 +39,8 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile("^(?:[<](?:[^<> \\t\\n\\\\]|\\\\.)*[>])"); - private static final Pattern LINK_LABEL = Pattern.compile("^\\[(?:[^\\\\\\[\\]]|\\\\.)*\\]"); + private static final Pattern LINK_LABEL = Pattern.compile( + "^\\[(?:[^\\\\\\[\\]]|\\\\.){0,1000}\\]"); private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 8c5d57dd4..ddf44e268 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -106,4 +106,11 @@ public void hugeHorizontalRule() { 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"); + } } From e368db33d279b04ec779273c24e7ca4e952825e3 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Apr 2019 15:45:32 +1000 Subject: [PATCH 331/815] Fix pathological case with input `[]([]([](...` See https://github.com/commonmark/commonmark.js/issues/129 --- .../commonmark/internal/InlineParserImpl.java | 21 ++++++++++++------- .../org/commonmark/test/PathologicalTest.java | 7 +++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index d236cca31..c75a9bb02 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -638,18 +638,21 @@ private String parseLinkDestination() { } } else { int startIndex = index; - parseLinkDestinationWithBalancedParens(); - return Escaping.unescapeString(input.substring(startIndex, index)); + if (parseLinkDestinationWithBalancedParens()) { + return Escaping.unescapeString(input.substring(startIndex, index)); + } else { + return null; + } } } - private void parseLinkDestinationWithBalancedParens() { + private boolean parseLinkDestinationWithBalancedParens() { int parens = 0; while (true) { char c = peek(); switch (c) { case '\0': - return; + return true; case '\\': // check if we have an escapable character if (index + 1 < input.length() && ESCAPABLE.matcher(input.substring(index + 1, index + 2)).matches()) { @@ -661,21 +664,25 @@ private void parseLinkDestinationWithBalancedParens() { break; case '(': parens++; + // Limit to 32 nested parens for pathological cases + if (parens > 32) { + return false; + } break; case ')': if (parens == 0) { - return; + return true; } else { parens--; } break; case ' ': // ASCII space - return; + return true; default: // or control character if (Character.isISOControl(c)) { - return; + return true; } } index++; diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index ddf44e268..a853b1b11 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -113,4 +113,11 @@ public void backslashInLink() { 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"); + } } From ccff6918b68fd7951c021ea1a6bd7e207421b2e9 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Apr 2019 16:21:16 +1000 Subject: [PATCH 332/815] Changes to link destination parsing (spec 0.29) * Allow spaces inside link destinations in pointy brackets * Disallow link destination beginning with `<` unless it is inside `<..>` --- .../commonmark/internal/InlineParserImpl.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index c75a9bb02..da3b93ab4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -37,7 +37,8 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { '|' + "\\((" + ESCAPED_CHAR + "|[^)\\x00])*\\))"); - private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile("^(?:[<](?:[^<> \\t\\n\\\\]|\\\\.)*[>])"); + private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile( + "^(?:[<](?:[^<>\n\\\\\\x00]|\\\\.)*[>])"); private static final Pattern LINK_LABEL = Pattern.compile( "^\\[(?:[^\\\\\\[\\]]|\\\\.){0,1000}\\]"); @@ -190,7 +191,7 @@ public int parseReference(String s) { this.input = s; this.index = 0; String dest; - String title; + String title = null; int matchChars; int startIndex = index; @@ -212,13 +213,15 @@ public int parseReference(String s) { spnl(); dest = parseLinkDestination(); - if (dest == null || dest.length() == 0) { + if (dest == null) { return 0; } int beforeTitle = index; spnl(); - title = parseLinkTitle(); + if (index != beforeTitle) { + title = parseLinkTitle(); + } if (title == null) { // rewind before spaces index = beforeTitle; @@ -637,6 +640,9 @@ private String parseLinkDestination() { return Escaping.unescapeString(res.substring(1, res.length() - 1)); } } else { + if (peek() == '<') { + return null; + } int startIndex = index; if (parseLinkDestinationWithBalancedParens()) { return Escaping.unescapeString(input.substring(startIndex, index)); @@ -647,12 +653,14 @@ private String parseLinkDestination() { } private boolean parseLinkDestinationWithBalancedParens() { + int startIndex = index; int parens = 0; while (true) { char c = peek(); switch (c) { case '\0': - return true; + case ' ': + return startIndex != index; case '\\': // check if we have an escapable character if (index + 1 < input.length() && ESCAPABLE.matcher(input.substring(index + 1, index + 2)).matches()) { @@ -676,13 +684,10 @@ private boolean parseLinkDestinationWithBalancedParens() { parens--; } break; - case ' ': - // ASCII space - return true; default: // or control character if (Character.isISOControl(c)) { - return true; + return startIndex != index; } } index++; From 79c0a7cc6d64c541d17a2729dec28cf07dabd43e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Apr 2019 16:26:32 +1000 Subject: [PATCH 333/815] Disallow unescaped '(' in link title See https://github.com/commonmark/commonmark-spec/issues/526 --- .../src/main/java/org/commonmark/internal/InlineParserImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index da3b93ab4..d7d630193 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -35,7 +35,7 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { '|' + "'(" + ESCAPED_CHAR + "|[^'\\x00])*'" + '|' + - "\\((" + ESCAPED_CHAR + "|[^)\\x00])*\\))"); + "\\((" + ESCAPED_CHAR + "|[^()\\x00])*\\))"); private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile( "^(?:[<](?:[^<>\n\\\\\\x00]|\\\\.)*[>])"); From 8e363803253a931c638ab420d8a8558a0a974f00 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 15 Apr 2019 16:35:35 +1000 Subject: [PATCH 334/815] Adapt link reference definition parsing to spec changes The spec was updated to clarify that they can also be part of setext headings. The way we make these work in our parser is to be able to "replace" the active paragraph block. In that case, we still need to collect link reference definitions from it. In order to implement this, I had to separate it from InlineParser. The new way is cleaner, and allows us to add a Node to the document as well (for #98). I think there's still room for improvement: We could parse the definition as we go in ParagraphParser and collect them earlier. That might eliminate the current "double parsing" that we do in some cases. --- CHANGELOG.md | 5 + .../commonmark/internal/DocumentParser.java | 78 +++++++++-- .../internal/InlineParserContextImpl.java | 30 +++++ .../commonmark/internal/InlineParserImpl.java | 125 ++++-------------- .../LinkReferenceDefinitionParser.java | 124 +++++++++++++++++ .../commonmark/internal/ParagraphParser.java | 26 +--- .../commonmark/internal/ReferenceParser.java | 11 -- .../org/commonmark/node/AbstractVisitor.java | 5 + .../node/LinkReferenceDefinition.java | 60 +++++++++ .../java/org/commonmark/node/Visitor.java | 4 +- .../parser/InlineParserContext.java | 16 ++- .../java/org/commonmark/parser/Parser.java | 54 ++++---- 12 files changed, 365 insertions(+), 173 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java delete mode 100644 commonmark/src/main/java/org/commonmark/internal/ReferenceParser.java create mode 100644 commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java diff --git a/CHANGELOG.md b/CHANGELOG.md index cb5f8c178..17e470514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +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 +- `InlineParserContext.getLinkReferenceDefinition` was added to allow + custom inline parsers to look up definitions for reference links. ### Changed +- Link reference definition parsing has been changed according to the + spec: Definitions can now be in setext headings too. - Check non-null arguments early and provide a nicer message ## [0.12.1] - 2018-11-13 diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 539516c1d..b716286dc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -3,7 +3,9 @@ import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserFactory; import org.commonmark.parser.block.*; +import org.commonmark.parser.delimiter.DelimiterProcessor; import java.io.BufferedReader; import java.io.IOException; @@ -59,17 +61,23 @@ public class DocumentParser implements ParserState { private boolean blank; private final List blockParserFactories; - private final InlineParser inlineParser; + private final InlineParserFactory inlineParserFactory; + private final List delimiterProcessors; private final DocumentBlockParser documentBlockParser; + private final LinkReferenceDefinitionParser linkReferenceDefinitionParser; private List activeBlockParsers = new ArrayList<>(); - private Set allBlockParsers = new HashSet<>(); + // LinkedHashSet to have a deterministic order + private Set allBlockParsers = new LinkedHashSet<>(); - public DocumentParser(List blockParserFactories, InlineParser inlineParser) { + public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, + List delimiterProcessors) { this.blockParserFactories = blockParserFactories; - this.inlineParser = inlineParser; + this.inlineParserFactory = inlineParserFactory; + this.delimiterProcessors = delimiterProcessors; this.documentBlockParser = new DocumentBlockParser(); + this.linkReferenceDefinitionParser = new LinkReferenceDefinitionParser(); activateBlockParser(this.documentBlockParser); } @@ -233,7 +241,7 @@ private void incorporateLine(CharSequence ln) { } if (blockStart.isReplaceActiveBlockParser()) { - removeActiveBlockParser(); + prepareActiveBlockParserForReplacement(); } for (BlockParser newBlockParser : blockStart.getBlockParsers()) { @@ -386,10 +394,26 @@ private void finalize(BlockParser blockParser) { blockParser.closeBlock(); - if (blockParser instanceof ParagraphParser - && inlineParser instanceof ReferenceParser) { + if (blockParser instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) blockParser; - paragraphParser.closeBlock((ReferenceParser) inlineParser); + String content = paragraphParser.getContentString(); + + Block paragraph = paragraphParser.getBlock(); + + // TODO: Insert resulting nodes into AST (before paragraph node) + int afterDefinitions = linkReferenceDefinitionParser.parseDefinitions(content); + + if (afterDefinitions != 0) { + String remainingContent = content.substring(afterDefinitions); + if (Parsing.isBlank(remainingContent)) { + // If all we had was reference definitions, remove the block that's now empty + paragraph.unlink(); + paragraphParser.setContentString(""); + } else { + // We had some content after the definitions, use that for the paragraph + paragraphParser.setContentString(remainingContent); + } + } } } @@ -397,6 +421,17 @@ private void finalize(BlockParser blockParser) { * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private void processInlines() { + Map definitions = new LinkedHashMap<>(); + for (LinkReferenceDefinition definition : linkReferenceDefinitionParser.getDefinitions()) { + String label = definition.getLabel(); + // spec: When there are multiple matching link reference definitions, the first is used + if (!definitions.containsKey(label)) { + definitions.put(label, definition); + } + } + InlineParserContextImpl context = new InlineParserContextImpl(delimiterProcessors, definitions); + InlineParser inlineParser = inlineParserFactory.create(context); + for (BlockParser blockParser : allBlockParsers) { blockParser.parseInlines(inlineParser); } @@ -426,11 +461,21 @@ private void deactivateBlockParser() { activeBlockParsers.remove(activeBlockParsers.size() - 1); } - private void removeActiveBlockParser() { + private void prepareActiveBlockParserForReplacement() { BlockParser old = getActiveBlockParser(); deactivateBlockParser(); allBlockParsers.remove(old); + if (old instanceof ParagraphParser) { + String content = ((ParagraphParser) old).getContentString(); + // 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 our + // implementation of that, we strip link reference definitions from the paragraph content before we give it + // to the block parser. We want to keep them. If no replacement happens, we collect the definitions as part + // of finalizing paragraph blocks. + linkReferenceDefinitionParser.parseDefinitions(content); + } + old.getBlock().unlink(); } @@ -467,7 +512,20 @@ public BlockParser getMatchedBlockParser() { public CharSequence getParagraphContent() { if (matchedBlockParser instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) matchedBlockParser; - return paragraphParser.getContentString(); + String content = paragraphParser.getContentString(); + + // Strip link reference definitions, they are not going to be part of the paragraph text. + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + int afterDefinitions = parser.parseDefinitions(content); + if (afterDefinitions != 0) { + content = content.substring(afterDefinitions); + if (Parsing.isBlank(content)) { + // Paragraph consists only of link reference definitions -> no actual paragraph content + return null; + } + } + + return content; } return null; } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java new file mode 100644 index 000000000..bff085ad8 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -0,0 +1,30 @@ +package org.commonmark.internal; + +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 delimiterProcessors; + private final Map linkReferenceDefinitions; + + public InlineParserContextImpl(List delimiterProcessors, + Map linkReferenceDefinitions) { + this.delimiterProcessors = delimiterProcessors; + this.linkReferenceDefinitions = linkReferenceDefinitions; + } + + @Override + public List getCustomDelimiterProcessors() { + return delimiterProcessors; + } + + @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 d7d630193..ad93b586e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -7,13 +7,14 @@ import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserContext; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class InlineParserImpl implements InlineParser, ReferenceParser { +public class InlineParserImpl implements InlineParser { private static final String ESCAPED_CHAR = "\\\\" + Escaping.ESCAPABLE; private static final String HTMLCOMMENT = "|"; @@ -65,19 +66,13 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { private static final Pattern FINAL_SPACE = Pattern.compile(" *$"); - private static final Pattern LINE_END = Pattern.compile("^ *(?:\n|$)"); - private final BitSet specialCharacters; private final BitSet delimiterCharacters; private final Map delimiterProcessors; + private final InlineParserContext context; - /** - * Link references by ID, needs to be built up using parseReference before calling parse. - */ - private Map referenceMap = new HashMap<>(); - - private String input; - private int index; + String input; + int index; /** * Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different @@ -90,10 +85,12 @@ public class InlineParserImpl implements InlineParser, ReferenceParser { */ private Bracket lastBracket; - public InlineParserImpl(List delimiterProcessors) { - this.delimiterProcessors = calculateDelimiterProcessors(delimiterProcessors); + public InlineParserImpl(InlineParserContext inlineParserContext) { + this.delimiterProcessors = calculateDelimiterProcessors(inlineParserContext.getCustomDelimiterProcessors()); this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); this.specialCharacters = calculateSpecialCharacters(delimiterCharacters); + + this.context = inlineParserContext; } public static BitSet calculateDelimiterCharacters(Set characters) { @@ -163,10 +160,7 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr */ @Override public void parse(String content, Node block) { - this.input = content.trim(); - this.index = 0; - this.lastDelimiter = null; - this.lastBracket = null; + reset(content.trim()); Node previous = null; while (true) { @@ -183,82 +177,14 @@ public void parse(String content, Node block) { mergeChildTextNodes(block); } - /** - * Attempt to parse a link reference, modifying the internal reference map. - */ - @Override - public int parseReference(String s) { - this.input = s; + void reset(String content) { + this.input = content; this.index = 0; - String dest; - String title = null; - int matchChars; - int startIndex = index; - - // label: - matchChars = parseLinkLabel(); - if (matchChars == 0) { - return 0; - } - - String rawLabel = input.substring(0, matchChars); - - // colon: - if (peek() != ':') { - return 0; - } - index++; - - // link url - spnl(); - - dest = parseLinkDestination(); - if (dest == null) { - return 0; - } - - int beforeTitle = index; - spnl(); - if (index != beforeTitle) { - title = parseLinkTitle(); - } - if (title == null) { - // rewind before spaces - index = beforeTitle; - } - - boolean atLineEnd = true; - if (index != input.length() && match(LINE_END) == null) { - if (title == null) { - atLineEnd = false; - } else { - // the potential title we found is not at the line end, - // but it could still be a legal link reference if we - // discard the title - title = null; - // rewind before spaces - index = beforeTitle; - // and instead check if the link URL is at the line end - atLineEnd = match(LINE_END) != null; - } - } - - if (!atLineEnd) { - return 0; - } - - String normalizedLabel = Escaping.normalizeReference(rawLabel); - if (normalizedLabel.isEmpty()) { - return 0; - } - - if (!referenceMap.containsKey(normalizedLabel)) { - Link link = new Link(dest, title); - referenceMap.put(normalizedLabel, link); - } - return index - startIndex; + this.lastDelimiter = null; + this.lastBracket = null; } + private Text text(String text, int beginIndex, int endIndex) { return new Text(text.substring(beginIndex, endIndex)); } @@ -331,7 +257,7 @@ private Node parseInline(Node previous) { /** * If RE matches at current index in the input, advance index and return the match; otherwise return null. */ - private String match(Pattern re) { + String match(Pattern re) { if (index >= input.length()) { return null; } @@ -349,7 +275,7 @@ private String match(Pattern re) { /** * Returns the char at the current input index, or {@code '\0'} in case there are no more characters. */ - private char peek() { + char peek() { if (index < input.length()) { return input.charAt(index); } else { @@ -360,7 +286,7 @@ private char peek() { /** * Parse zero or more space characters, including at most one newline. */ - private void spnl() { + void spnl() { match(SPNL); } @@ -568,10 +494,11 @@ private Node parseCloseBracket() { } if (ref != null) { - Link link = referenceMap.get(Escaping.normalizeReference(ref)); - if (link != null) { - dest = link.getDestination(); - title = link.getTitle(); + String label = Escaping.normalizeReference(ref); + LinkReferenceDefinition definition = context.getLinkReferenceDefinition(label); + if (definition != null) { + dest = definition.getDestination(); + title = definition.getTitle(); isLinkOrImage = true; } } @@ -631,7 +558,7 @@ private void removeLastBracket() { /** * Attempt to parse link destination, returning the string or null if no match. */ - private String parseLinkDestination() { + String parseLinkDestination() { String res = match(LINK_DESTINATION_BRACES); if (res != null) { // chop off surrounding <..>: if (res.length() == 2) { @@ -697,7 +624,7 @@ private boolean parseLinkDestinationWithBalancedParens() { /** * Attempt to parse link title (sans quotes), returning the string or null if no match. */ - private String parseLinkTitle() { + String parseLinkTitle() { String title = match(LINK_TITLE); if (title != null) { // chop off quotes from title and unescape: @@ -710,7 +637,7 @@ private String parseLinkTitle() { /** * Attempt to parse a link label, returning number of characters parsed. */ - private int parseLinkLabel() { + int parseLinkLabel() { String m = match(LINK_LABEL); // Spec says "A link label can have at most 999 characters inside the square brackets" if (m == null || m.length() > 1001) { diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java new file mode 100644 index 000000000..200f67d3c --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -0,0 +1,124 @@ +package org.commonmark.internal; + +import org.commonmark.internal.util.Escaping; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +/** + * @see Link reference definitions + */ +class LinkReferenceDefinitionParser extends InlineParserImpl { + + /** + * Parsed link reference definitions by label, in order of occurrence. + */ + private List definitions = new ArrayList<>(); + + private static final Pattern LINE_END = Pattern.compile("^ *(?:\n|$)"); + + LinkReferenceDefinitionParser() { + // Not needed for parsing link reference definitions + super(new InlineParserContextImpl(Collections.emptyList(), + Collections.emptyMap())); + } + + // TODO: Would be better to just return them from the method. + List getDefinitions() { + return definitions; + } + + /** + * Parse all link reference definitions, add them to the map and return the length of the text we parsed (if any). + */ + int parseDefinitions(String content) { + int afterAllDefinitions = 0; + while (content.length() > 3 && content.charAt(0) == '[') { + int afterDefinition = parseDefinition(content); + if (afterDefinition != 0) { + content = content.substring(afterDefinition); + afterAllDefinitions += afterDefinition; + } else { + break; + } + } + return afterAllDefinitions; + } + + /** + * Attempt to parse a single link reference definition, adding it to the map. + */ + private int parseDefinition(String content) { + reset(content); + + String dest; + String title = null; + int matchChars; + int startIndex = index; + + // label: + matchChars = parseLinkLabel(); + if (matchChars == 0) { + return 0; + } + + String rawLabel = input.substring(0, matchChars); + + // colon: + if (peek() != ':') { + return 0; + } + index++; + + // link url + spnl(); + + dest = parseLinkDestination(); + if (dest == null) { + return 0; + } + + int beforeTitle = index; + spnl(); + if (index != beforeTitle) { + title = parseLinkTitle(); + } + if (title == null) { + // rewind before spaces + index = beforeTitle; + } + + boolean atLineEnd = true; + if (index != input.length() && match(LINE_END) == null) { + if (title == null) { + atLineEnd = false; + } else { + // the potential title we found is not at the line end, + // but it could still be a legal link reference if we + // discard the title + title = null; + // rewind before spaces + index = beforeTitle; + // and instead check if the link URL is at the line end + atLineEnd = match(LINE_END) != null; + } + } + + if (!atLineEnd) { + return 0; + } + + String normalizedLabel = Escaping.normalizeReference(rawLabel); + if (normalizedLabel.isEmpty()) { + return 0; + } + + definitions.add(new LinkReferenceDefinition(normalizedLabel, dest, title)); + + return index - startIndex; + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index fac8cfadc..f6556ab2e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -1,11 +1,10 @@ package org.commonmark.internal; -import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.Paragraph; +import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.BlockContinue; -import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.ParserState; public class ParagraphParser extends AbstractBlockParser { @@ -36,25 +35,6 @@ public void addLine(CharSequence line) { public void closeBlock() { } - public void closeBlock(ReferenceParser inlineParser) { - String contentString = content.getString(); - boolean hasReferenceDefs = false; - - int pos; - // try parsing the beginning as link reference definitions: - while (contentString.length() > 3 && contentString.charAt(0) == '[' && - (pos = inlineParser.parseReference(contentString)) != 0) { - contentString = contentString.substring(pos); - hasReferenceDefs = true; - } - if (hasReferenceDefs && Parsing.isBlank(contentString)) { - block.unlink(); - content = null; - } else { - content = new BlockContent(contentString); - } - } - @Override public void parseInlines(InlineParser inlineParser) { if (content != null) { @@ -65,4 +45,8 @@ public void parseInlines(InlineParser inlineParser) { public String getContentString() { return content.getString(); } + + void setContentString(String contentString) { + content = new BlockContent(contentString); + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/ReferenceParser.java b/commonmark/src/main/java/org/commonmark/internal/ReferenceParser.java deleted file mode 100644 index 35f36cb59..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/ReferenceParser.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.commonmark.internal; - -/** - * Parser for inline references - */ -public interface ReferenceParser { - /** - * @return how many characters were parsed as a reference, {@code 0} if none - */ - int parseReference(String s); -} diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index 381c72b66..7edd635d7 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -108,6 +108,11 @@ public void visit(Text text) { visitChildren(text); } + @Override + public void visit(LinkReferenceDefinition linkReferenceDefinition) { + visitChildren(linkReferenceDefinition); + } + @Override public void visit(CustomBlock customBlock) { visitChildren(customBlock); diff --git a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java new file mode 100644 index 000000000..a4578e99b --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java @@ -0,0 +1,60 @@ +package org.commonmark.node; + +// TODO: We're currently not adding these to the document. +// But that would be very useful for being able to render Nodes back to Markdown, see #98. + +/** + * A link reference definition, e.g.: + *
    
    + * [foo]: /url "title"
    + * 
    + *

    + * They can be referenced anywhere else in the document to produce a link using [foo]. The definitions + * themselves are usually not rendered in the final output. + * + * @see Link reference definitions + */ +public class LinkReferenceDefinition extends Node { + + private String label; + private String destination; + private String title; + + public LinkReferenceDefinition() { + } + + public LinkReferenceDefinition(String label, String destination, String title) { + this.label = label; + this.destination = destination; + this.title = title; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } +} diff --git a/commonmark/src/main/java/org/commonmark/node/Visitor.java b/commonmark/src/main/java/org/commonmark/node/Visitor.java index 8851b7b18..a155296f0 100644 --- a/commonmark/src/main/java/org/commonmark/node/Visitor.java +++ b/commonmark/src/main/java/org/commonmark/node/Visitor.java @@ -3,7 +3,7 @@ /** * Node visitor. *

    - * See {@link AbstractVisitor} for a base class that can be extended. + * Implementations should subclass {@link AbstractVisitor} instead of implementing this directly. */ public interface Visitor { @@ -47,6 +47,8 @@ public interface Visitor { void visit(Text text); + void visit(LinkReferenceDefinition linkReferenceDefinition); + void visit(CustomBlock customBlock); void visit(CustomNode customNode); diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 7a3be522d..467742e2c 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -1,13 +1,25 @@ package org.commonmark.parser; +import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.List; -import java.util.Map; /** - * Parameter context for custom inline parser. + * Context for inline parsing. */ public interface InlineParserContext { + + /** + * @return custom delimiter processors that have been configured with {@link Parser.Builder#customDelimiterProcessor(DelimiterProcessor)} + */ List getCustomDelimiterProcessors(); + + /** + * Look up a {@link LinkReferenceDefinition} for a given label. + * + * @param label the link label to look up + * @return the definition if one exists, {@code null} otherwise + */ + LinkReferenceDefinition getLinkReferenceDefinition(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 04d28065f..5e15158ad 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -2,6 +2,7 @@ import org.commonmark.Extension; import org.commonmark.internal.DocumentParser; +import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; import org.commonmark.node.*; import org.commonmark.parser.block.BlockParserFactory; @@ -10,6 +11,7 @@ import java.io.IOException; import java.io.Reader; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -32,12 +34,14 @@ public class Parser { private Parser(Builder builder) { this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); - this.inlineParserFactory = builder.inlineParserFactory; + this.inlineParserFactory = builder.getInlineParserFactory(); this.postProcessors = builder.postProcessors; this.delimiterProcessors = builder.delimiterProcessors; - // Try to construct an inline parser. This might raise exceptions in case of invalid configuration. - getInlineParser(); + // 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, + Collections.emptyMap())); } /** @@ -61,8 +65,7 @@ public Node parse(String input) { if (input == null) { throw new NullPointerException("input must not be null"); } - InlineParser inlineParser = getInlineParser(); - DocumentParser documentParser = new DocumentParser(blockParserFactories, inlineParser); + DocumentParser documentParser = createDocumentParser(); Node document = documentParser.parse(input); return postProcess(document); } @@ -89,19 +92,14 @@ public Node parseReader(Reader input) throws IOException { if (input == null) { throw new NullPointerException("input must not be null"); } - InlineParser inlineParser = getInlineParser(); - DocumentParser documentParser = new DocumentParser(blockParserFactories, inlineParser); + + DocumentParser documentParser = createDocumentParser(); Node document = documentParser.parse(input); return postProcess(document); } - private InlineParser getInlineParser() { - if (this.inlineParserFactory == null) { - return new InlineParserImpl(delimiterProcessors); - } else { - CustomInlineParserContext inlineParserContext = new CustomInlineParserContext(delimiterProcessors); - return this.inlineParserFactory.create(inlineParserContext); - } + private DocumentParser createDocumentParser() { + return new DocumentParser(blockParserFactories, inlineParserFactory, delimiterProcessors); } private Node postProcess(Node document) { @@ -111,20 +109,6 @@ private Node postProcess(Node document) { return document; } - private class CustomInlineParserContext implements InlineParserContext { - - private List delimiterProcessors; - - CustomInlineParserContext(List delimiterProcessors) { - this.delimiterProcessors = delimiterProcessors; - } - - @Override - public List getCustomDelimiterProcessors() { - return delimiterProcessors; - } - } - /** * Builder for configuring a {@link Parser}. */ @@ -133,7 +117,7 @@ public static class Builder { private final List delimiterProcessors = new ArrayList<>(); private final List postProcessors = new ArrayList<>(); private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); - private InlineParserFactory inlineParserFactory = null; + private InlineParserFactory inlineParserFactory; /** * @return the configured {@link Parser} @@ -261,6 +245,18 @@ public Builder inlineParserFactory(InlineParserFactory inlineParserFactory) { this.inlineParserFactory = inlineParserFactory; return this; } + + private InlineParserFactory getInlineParserFactory() { + if (inlineParserFactory != null) { + return inlineParserFactory; + } + return new InlineParserFactory() { + @Override + public InlineParser create(InlineParserContext inlineParserContext) { + return new InlineParserImpl(inlineParserContext); + } + }; + } } /** From 0f4613add1a61dbd3737b7205dfb816eacfa649e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Sat, 1 Jun 2019 20:26:19 +1000 Subject: [PATCH 335/815] Extract link scanning logic and replace use of regexes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hopefully this will allow us to reuse it for parsing link reference definitions in an incremental way. Also speeds up parsing a bit: -SpecBenchmark.parseExamples thrpt 50 453.493 ± 4.647 ops/s +SpecBenchmark.parseExamples thrpt 50 483.467 ± 3.418 ops/s --- .../commonmark/internal/InlineParserImpl.java | 120 +++++--------- .../commonmark/internal/util/Escaping.java | 11 +- .../commonmark/internal/util/LinkScanner.java | 148 ++++++++++++++++++ .../org/commonmark/internal/util/Parsing.java | 41 +++++ 4 files changed, 234 insertions(+), 86 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index ad93b586e..00da9134d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -4,6 +4,7 @@ import org.commonmark.internal.inline.UnderscoreDelimiterProcessor; import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.Html5Entities; +import org.commonmark.internal.util.LinkScanner; import org.commonmark.internal.util.Parsing; import org.commonmark.node.*; import org.commonmark.parser.InlineParser; @@ -15,8 +16,7 @@ import java.util.regex.Pattern; public class InlineParserImpl implements InlineParser { - - private static final String ESCAPED_CHAR = "\\\\" + Escaping.ESCAPABLE; + private static final String HTMLCOMMENT = "|"; private static final String PROCESSINGINSTRUCTION = "[<][?].*?[?][>]"; private static final String DECLARATION = "]*>"; @@ -31,19 +31,6 @@ public class InlineParserImpl implements InlineParser { private static final Pattern HTML_TAG = Pattern.compile('^' + HTMLTAG, Pattern.CASE_INSENSITIVE); - private static final Pattern LINK_TITLE = Pattern.compile( - "^(?:\"(" + ESCAPED_CHAR + "|[^\"\\x00])*\"" + - '|' + - "'(" + ESCAPED_CHAR + "|[^'\\x00])*'" + - '|' + - "\\((" + ESCAPED_CHAR + "|[^()\\x00])*\\))"); - - private static final Pattern LINK_DESTINATION_BRACES = Pattern.compile( - "^(?:[<](?:[^<>\n\\\\\\x00]|\\\\.)*[>])"); - - private static final Pattern LINK_LABEL = Pattern.compile( - "^\\[(?:[^\\\\\\[\\]]|\\\\.){0,1000}\\]"); - private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); private static final Pattern ENTITY_HERE = Pattern.compile('^' + ENTITY, Pattern.CASE_INSENSITIVE); @@ -482,7 +469,8 @@ private Node parseCloseBracket() { // See if there's a link label like `[bar]` or `[]` int beforeLabel = index; - int labelLength = parseLinkLabel(); + parseLinkLabel(); + int labelLength = index - beforeLabel; String ref = null; if (labelLength > 2) { ref = input.substring(beforeLabel, beforeLabel + labelLength); @@ -559,92 +547,58 @@ private void removeLastBracket() { * Attempt to parse link destination, returning the string or null if no match. */ String parseLinkDestination() { - String res = match(LINK_DESTINATION_BRACES); - if (res != null) { // chop off surrounding <..>: - if (res.length() == 2) { - return ""; - } else { - return Escaping.unescapeString(res.substring(1, res.length() - 1)); - } - } else { - if (peek() == '<') { - return null; - } - int startIndex = index; - if (parseLinkDestinationWithBalancedParens()) { - return Escaping.unescapeString(input.substring(startIndex, index)); - } else { - return null; - } + int afterDest = LinkScanner.scanLinkDestination(input, index); + if (afterDest == -1) { + return null; } - } - private boolean parseLinkDestinationWithBalancedParens() { - int startIndex = index; - int parens = 0; - while (true) { - char c = peek(); - switch (c) { - case '\0': - case ' ': - return startIndex != index; - case '\\': - // check if we have an escapable character - if (index + 1 < input.length() && ESCAPABLE.matcher(input.substring(index + 1, index + 2)).matches()) { - // skip over the escaped character (after switch) - index++; - break; - } - // otherwise, we treat this as a literal backslash - break; - case '(': - parens++; - // Limit to 32 nested parens for pathological cases - if (parens > 32) { - return false; - } - break; - case ')': - if (parens == 0) { - return true; - } else { - parens--; - } - break; - default: - // or control character - if (Character.isISOControl(c)) { - return startIndex != index; - } - } - index++; + String dest; + if (peek() == '<') { + // chop off surrounding <..>: + dest = input.substring(index + 1, afterDest - 1); + } else { + dest = input.substring(index, afterDest); } + + index = afterDest; + return Escaping.unescapeString(dest); } /** * Attempt to parse link title (sans quotes), returning the string or null if no match. */ String parseLinkTitle() { - String title = match(LINK_TITLE); - if (title != null) { - // chop off quotes from title and unescape: - return Escaping.unescapeString(title.substring(1, title.length() - 1)); - } else { + int afterTitle = LinkScanner.scanLinkTitle(input, index); + if (afterTitle == -1) { return null; } + + // chop off ', " or parens + String title = input.substring(index + 1, afterTitle - 1); + index = afterTitle; + return Escaping.unescapeString(title); } /** * Attempt to parse a link label, returning number of characters parsed. */ int parseLinkLabel() { - String m = match(LINK_LABEL); - // Spec says "A link label can have at most 999 characters inside the square brackets" - if (m == null || m.length() > 1001) { + if (index >= input.length() || input.charAt(index) != '[') { + return 0; + } + + int startContent = index + 1; + int endContent = LinkScanner.scanLinkLabelContent(input, startContent); + // spec: A link label can have at most 999 characters inside the square brackets. + int contentLength = endContent - startContent; + if (endContent == -1 || contentLength > 999) { + return 0; + } + if (endContent >= input.length() || input.charAt(endContent) != ']') { return 0; - } else { - return m.length(); } + index = endContent + 1; + return contentLength + 2; } /** 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 9136b56f8..6a27f9419 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -109,9 +109,14 @@ public static String percentEncodeUrl(String s) { } public static String normalizeReference(String input) { - // Strip '[' and ']', then trim - String stripped = input.substring(1, input.length() - 1).trim(); - String lowercase = stripped.toLowerCase(Locale.ROOT); + // Strip '[' and ']' + String stripped = input.substring(1, input.length() - 1); + return normalizeLabelContent(stripped); + } + + public static String normalizeLabelContent(String input) { + String trimmed = input.trim(); + String lowercase = trimmed.toLowerCase(Locale.ROOT); return WHITESPACE.matcher(lowercase).replaceAll(" "); } diff --git a/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java b/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java new file mode 100644 index 000000000..dae17d090 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java @@ -0,0 +1,148 @@ +package org.commonmark.internal.util; + +public class LinkScanner { + + /** + * Attempt to scan the contents of a link label (inside the brackets), returning the position after the content or + * -1. The returned position can either be the closing {@code ]}, or the end of the line if the label continues on + * the next line. + */ + public static int scanLinkLabelContent(CharSequence input, int start) { + for (int i = start; i < input.length(); i++) { + char c = input.charAt(i); + switch (c) { + case '\\': + if (Parsing.isEscapable(input, i + 1)) { + i += 1; + } + break; + case ']': + return i; + case '[': + // spec: Unescaped square bracket characters are not allowed inside the opening and closing + // square brackets of link labels. + return -1; + } + } + return input.length(); + } + + /** + * Attempt to scan a link destination, returning the position after the destination or -1. + */ + public static int scanLinkDestination(CharSequence input, int start) { + if (start >= input.length()) { + return -1; + } + + if (input.charAt(start) == '<') { + for (int i = start + 1; i < input.length(); i++) { + char c = input.charAt(i); + switch (c) { + case '\\': + if (Parsing.isEscapable(input, i + 1)) { + i += 1; + } + break; + case '\n': + case '<': + return -1; + case '>': + return i + 1; + } + } + return -1; + } else { + return scanLinkDestinationWithBalancedParens(input, start); + } + } + + public static int scanLinkTitle(CharSequence input, int start) { + if (start >= input.length()) { + return -1; + } + + char endDelimiter; + switch (input.charAt(start)) { + case '"': + endDelimiter = '"'; + break; + case '\'': + endDelimiter = '\''; + break; + case '(': + endDelimiter = ')'; + break; + default: + return -1; + } + + int afterContent = scanLinkTitleContent(input, start, endDelimiter); + if (afterContent == -1) { + return -1; + } + + if (afterContent >= input.length() || input.charAt(afterContent) != endDelimiter) { + // missing or wrong end delimiter + return -1; + } + + return afterContent + 1; + } + + public static int scanLinkTitleContent(CharSequence input, int start, char endDelimiter) { + for (int i = start + 1; i < input.length(); i++) { + char c = input.charAt(i); + if (c == '\\' && Parsing.isEscapable(input, i + 1)) { + i += 1; + } else if (c == endDelimiter) { + return i; + } else if (endDelimiter == ')' && c == '(') { + // unescaped '(' in title within parens is invalid + return -1; + } + } + return input.length(); + } + + // spec: a nonempty sequence of characters that does not start with <, does not include ASCII space or control + // characters, and includes parentheses only if (a) they are backslash-escaped or (b) they are part of a balanced + // pair of unescaped parentheses + private static int scanLinkDestinationWithBalancedParens(CharSequence input, int start) { + int parens = 0; + for (int i = start; i < input.length(); i++) { + char c = input.charAt(i); + switch (c) { + case '\0': + case ' ': + return i != start ? i : -1; + case '\\': + if (Parsing.isEscapable(input, i + 1)) { + i += 1; + } + break; + case '(': + parens++; + // Limit to 32 nested parens for pathological cases + if (parens > 32) { + return -1; + } + break; + case ')': + if (parens == 0) { + return i; + } else { + parens--; + } + break; + default: + // or control character + if (Character.isISOControl(c)) { + return i != start ? i : -1; + } + break; + } + } + return input.length(); + } +} 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 94f77858d..d429d9db0 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -72,6 +72,47 @@ public static boolean isSpaceOrTab(CharSequence s, int index) { return false; } + public static boolean isEscapable(CharSequence s, int index) { + if (index < s.length()) { + switch (s.charAt(index)) { + 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; + } + /** * Prepares the input line replacing {@code \0} */ From 7fee1271d6e91804c7112d43cb075611231f8886 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 11:03:28 +1000 Subject: [PATCH 336/815] Make link reference definition parsing incremental MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old code would parse link reference definitions twice in the worst case. The new one parses it as part of paragraph parsing. Looks like this is faster too: -SpecBenchmark.parseExamples thrpt 50 485.743 ± 1.864 ops/s +SpecBenchmark.parseExamples thrpt 50 550.071 ± 8.638 ops/s -SpecBenchmark.parseWholeSpec thrpt 50 284.494 ± 2.641 ops/s +SpecBenchmark.parseWholeSpec thrpt 50 297.277 ± 3.272 ops/s --- .../commonmark/internal/DocumentParser.java | 54 +--- .../LinkReferenceDefinitionParser.java | 290 +++++++++++++----- .../OldLinkReferenceDefinitionParser.java | 124 ++++++++ .../commonmark/internal/ParagraphParser.java | 23 +- .../commonmark/internal/util/LinkScanner.java | 4 +- .../LinkReferenceDefinitionParserTest.java | 167 ++++++++++ 6 files changed, 534 insertions(+), 128 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/OldLinkReferenceDefinitionParser.java create mode 100644 commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index b716286dc..886db2bf2 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -64,7 +64,7 @@ public class DocumentParser implements ParserState { private final InlineParserFactory inlineParserFactory; private final List delimiterProcessors; private final DocumentBlockParser documentBlockParser; - private final LinkReferenceDefinitionParser linkReferenceDefinitionParser; + private final Map definitions = new LinkedHashMap<>(); private List activeBlockParsers = new ArrayList<>(); // LinkedHashSet to have a deterministic order @@ -77,7 +77,6 @@ public DocumentParser(List blockParserFactories, InlineParse this.delimiterProcessors = delimiterProcessors; this.documentBlockParser = new DocumentBlockParser(); - this.linkReferenceDefinitionParser = new LinkReferenceDefinitionParser(); activateBlockParser(this.documentBlockParser); } @@ -396,39 +395,25 @@ private void finalize(BlockParser blockParser) { if (blockParser instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) blockParser; - String content = paragraphParser.getContentString(); - - Block paragraph = paragraphParser.getBlock(); - // TODO: Insert resulting nodes into AST (before paragraph node) - int afterDefinitions = linkReferenceDefinitionParser.parseDefinitions(content); - - if (afterDefinitions != 0) { - String remainingContent = content.substring(afterDefinitions); - if (Parsing.isBlank(remainingContent)) { - // If all we had was reference definitions, remove the block that's now empty - paragraph.unlink(); - paragraphParser.setContentString(""); - } else { - // We had some content after the definitions, use that for the paragraph - paragraphParser.setContentString(remainingContent); - } - } + addDefinitionsFrom(paragraphParser); } } - /** - * Walk through a block & children recursively, parsing string content into inline content where appropriate. - */ - private void processInlines() { - Map definitions = new LinkedHashMap<>(); - for (LinkReferenceDefinition definition : linkReferenceDefinitionParser.getDefinitions()) { + private void addDefinitionsFrom(ParagraphParser paragraphParser) { + for (LinkReferenceDefinition definition : paragraphParser.getDefinitions()) { String label = definition.getLabel(); // spec: When there are multiple matching link reference definitions, the first is used if (!definitions.containsKey(label)) { definitions.put(label, definition); } } + } + + /** + * Walk through a block & children recursively, parsing string content into inline content where appropriate. + */ + private void processInlines() { InlineParserContextImpl context = new InlineParserContextImpl(delimiterProcessors, definitions); InlineParser inlineParser = inlineParserFactory.create(context); @@ -467,13 +452,14 @@ private void prepareActiveBlockParserForReplacement() { allBlockParsers.remove(old); if (old instanceof ParagraphParser) { - String content = ((ParagraphParser) old).getContentString(); + ParagraphParser paragraphParser = (ParagraphParser) old; + // TODO: adjust comment? // 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 our // implementation of that, we strip link reference definitions from the paragraph content before we give it // to the block parser. We want to keep them. If no replacement happens, we collect the definitions as part // of finalizing paragraph blocks. - linkReferenceDefinitionParser.parseDefinitions(content); + addDefinitionsFrom(paragraphParser); } old.getBlock().unlink(); @@ -512,17 +498,9 @@ public BlockParser getMatchedBlockParser() { public CharSequence getParagraphContent() { if (matchedBlockParser instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) matchedBlockParser; - String content = paragraphParser.getContentString(); - - // Strip link reference definitions, they are not going to be part of the paragraph text. - LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - int afterDefinitions = parser.parseDefinitions(content); - if (afterDefinitions != 0) { - content = content.substring(afterDefinitions); - if (Parsing.isBlank(content)) { - // Paragraph consists only of link reference definitions -> no actual paragraph content - return null; - } + CharSequence content = paragraphParser.getContentString(); + if (content.length() == 0) { + return null; } return content; diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 200f67d3c..e4ddfa8d0 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -1,124 +1,254 @@ package org.commonmark.internal; import org.commonmark.internal.util.Escaping; +import org.commonmark.internal.util.LinkScanner; +import org.commonmark.internal.util.Parsing; import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.regex.Pattern; /** + * Parser for link reference definitions at the beginning of a paragraph. + * * @see Link reference definitions */ -class LinkReferenceDefinitionParser extends InlineParserImpl { +public class LinkReferenceDefinitionParser { - /** - * Parsed link reference definitions by label, in order of occurrence. - */ - private List definitions = new ArrayList<>(); + private State state = State.START_DEFINITION; - private static final Pattern LINE_END = Pattern.compile("^ *(?:\n|$)"); + private final StringBuilder paragraph = new StringBuilder(); + private final List definitions = new ArrayList<>(); - LinkReferenceDefinitionParser() { - // Not needed for parsing link reference definitions - super(new InlineParserContextImpl(Collections.emptyList(), - Collections.emptyMap())); + private StringBuilder label; + private String normalizedLabel; + private String destination; + private char titleDelimiter; + private StringBuilder title; + private boolean referenceValid = false; + + public void parse(CharSequence line) { + if (paragraph.length() != 0) { + paragraph.append('\n'); + } + paragraph.append(line); + + int i = 0; + while (i < line.length()) { + switch (state) { + case PARAGRAPH: { + // We're in a paragraph now. Link reference definitions can only appear at the beginning, so once + // we're in a paragraph, there's no going back. + return; + } + case START_DEFINITION: { + i = startDefinition(line, i); + break; + } + case LABEL: { + i = label(line, i); + break; + } + case DESTINATION: { + i = destination(line, i); + break; + } + case START_TITLE: { + i = startTitle(line, i); + break; + } + case TITLE: { + i = title(line, i); + break; + } + } + // -1 is returned if parsing failed, which means we fall back to treating text as a paragraph. + if (i == -1) { + state = State.PARAGRAPH; + return; + } + } + } + + CharSequence getParagraphContent() { + return paragraph; } - // TODO: Would be better to just return them from the method. List getDefinitions() { + finishReference(); return definitions; } - /** - * Parse all link reference definitions, add them to the map and return the length of the text we parsed (if any). - */ - int parseDefinitions(String content) { - int afterAllDefinitions = 0; - while (content.length() > 3 && content.charAt(0) == '[') { - int afterDefinition = parseDefinition(content); - if (afterDefinition != 0) { - content = content.substring(afterDefinition); - afterAllDefinitions += afterDefinition; - } else { - break; - } + State getState() { + return state; + } + + private int startDefinition(CharSequence line, int i) { + i = Parsing.skipSpaceTab(line, i, line.length()); + if (i >= line.length() || line.charAt(i) != '[') { + return -1; } - return afterAllDefinitions; + + state = State.LABEL; + label = new StringBuilder(); + + return i + 1; } - /** - * Attempt to parse a single link reference definition, adding it to the map. - */ - private int parseDefinition(String content) { - reset(content); - - String dest; - String title = null; - int matchChars; - int startIndex = index; - - // label: - matchChars = parseLinkLabel(); - if (matchChars == 0) { - return 0; + private int label(CharSequence line, int i) { + int afterLabel = LinkScanner.scanLinkLabelContent(line, i); + if (afterLabel == -1) { + return -1; } - String rawLabel = input.substring(0, matchChars); + label.append(line, i, afterLabel); + + if (afterLabel >= line.length()) { + // label might continue on next line + label.append('\n'); + return afterLabel; + } else if (line.charAt(afterLabel) == ']') { + int colon = afterLabel + 1; + // end of label + if (colon >= line.length() || line.charAt(colon) != ':') { + return -1; + } + + int afterSpace = Parsing.skipSpaceTab(line, colon + 1, line.length()); + + String normalizedLabel = Escaping.normalizeLabelContent(label.toString()); + if (normalizedLabel.isEmpty()) { + return -1; + } + + this.normalizedLabel = normalizedLabel; + state = State.DESTINATION; - // colon: - if (peek() != ':') { - return 0; + return afterSpace; + } else { + return -1; } - index++; + } - // link url - spnl(); + private int destination(CharSequence line, int i) { + i = Parsing.skipSpaceTab(line, i, line.length()); + int afterDestination = LinkScanner.scanLinkDestination(line, i); + if (afterDestination == -1) { + return -1; + } - dest = parseLinkDestination(); - if (dest == null) { - return 0; + destination = (line.charAt(i) == '<') + ? line.subSequence(i + 1, afterDestination - 1).toString() + : line.subSequence(i, afterDestination).toString(); + + int afterSpace = Parsing.skipSpaceTab(line, afterDestination, line.length()); + if (afterSpace >= line.length()) { + // Destination was at end of line, so this is a valid reference for sure (and maybe a title). + // If not at end of line, wait for title to be valid first. + referenceValid = true; + paragraph.setLength(0); + } else if (afterSpace == afterDestination) { + // spec: The title must be separated from the link destination by whitespace + return -1; } - int beforeTitle = index; - spnl(); - if (index != beforeTitle) { - title = parseLinkTitle(); + state = State.START_TITLE; + return afterSpace; + } + + private int startTitle(CharSequence line, int i) { + i = Parsing.skipSpaceTab(line, i, line.length()); + if (i >= line.length()) { + state = State.START_DEFINITION; + return i; } - if (title == null) { - // rewind before spaces - index = beforeTitle; + + titleDelimiter = '\0'; + char c = line.charAt(i); + switch (c) { + case '"': + case '\'': + titleDelimiter = c; + break; + case '(': + titleDelimiter = ')'; + break; } - boolean atLineEnd = true; - if (index != input.length() && match(LINE_END) == null) { - if (title == null) { - atLineEnd = false; - } else { - // the potential title we found is not at the line end, - // but it could still be a legal link reference if we - // discard the title - title = null; - // rewind before spaces - index = beforeTitle; - // and instead check if the link URL is at the line end - atLineEnd = match(LINE_END) != null; + if (titleDelimiter != '\0') { + state = State.TITLE; + title = new StringBuilder(); + i++; + if (i == line.length()) { + title.append('\n'); } + } else { + finishReference(); + // There might be another reference instead, try that for the same character. + state = State.START_DEFINITION; } + return i; + } - if (!atLineEnd) { - return 0; + private int title(CharSequence line, int i) { + int afterTitle = LinkScanner.scanLinkTitleContent(line, i, titleDelimiter); + if (afterTitle == -1) { + // Invalid title, stop + return -1; } - String normalizedLabel = Escaping.normalizeReference(rawLabel); - if (normalizedLabel.isEmpty()) { - return 0; + title.append(line.subSequence(i, afterTitle)); + + if (afterTitle >= line.length()) { + // Title still going, continue on next line + title.append('\n'); + return afterTitle; } - definitions.add(new LinkReferenceDefinition(normalizedLabel, dest, title)); + int afterTitleDelimiter = afterTitle + 1; + int afterSpace = Parsing.skipSpaceTab(line, afterTitleDelimiter, line.length()); + if (afterSpace != line.length()) { + // spec: No further non-whitespace characters may occur on the line. + return -1; + } + referenceValid = true; + finishReference(); + paragraph.setLength(0); + + // See if there's another definition. + state = State.START_DEFINITION; + return afterSpace; + } + + private void finishReference() { + if (!referenceValid) { + return; + } + + String d = Escaping.unescapeString(destination); + String t = title != null ? Escaping.unescapeString(title.toString()) : null; + definitions.add(new LinkReferenceDefinition(normalizedLabel, d, t)); + + label = null; + referenceValid = false; + normalizedLabel = null; + destination = null; + title = null; + } - return index - startIndex; + enum State { + // Looking for the start of a definition, i.e. `[` + START_DEFINITION, + // Parsing the label, i.e. `foo` within `[foo]` + LABEL, + // Parsing the destination, i.e. `/url` in `[foo]: /url` + DESTINATION, + // Looking for the start of a title, i.e. the first `"` in `[foo]: /url "title"` + START_TITLE, + // Parsing the content of the title, i.e. `title` in `[foo]: /url "title"` + TITLE, + + // End state, no matter what kind of lines we add, they won't be references + PARAGRAPH, } } diff --git a/commonmark/src/main/java/org/commonmark/internal/OldLinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/OldLinkReferenceDefinitionParser.java new file mode 100644 index 000000000..fc01248f2 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/OldLinkReferenceDefinitionParser.java @@ -0,0 +1,124 @@ +package org.commonmark.internal; + +import org.commonmark.internal.util.Escaping; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.parser.delimiter.DelimiterProcessor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +/** + * @see Link reference definitions + */ +class OldLinkReferenceDefinitionParser extends InlineParserImpl { + + /** + * Parsed link reference definitions by label, in order of occurrence. + */ + private List definitions = new ArrayList<>(); + + private static final Pattern LINE_END = Pattern.compile("^ *(?:\n|$)"); + + OldLinkReferenceDefinitionParser() { + // Not needed for parsing link reference definitions + super(new InlineParserContextImpl(Collections.emptyList(), + Collections.emptyMap())); + } + + // TODO: Would be better to just return them from the method. + List getDefinitions() { + return definitions; + } + + /** + * Parse all link reference definitions, add them to the map and return the length of the text we parsed (if any). + */ + int parseDefinitions(String content) { + int afterAllDefinitions = 0; + while (content.length() > 3 && content.charAt(0) == '[') { + int afterDefinition = parseDefinition(content); + if (afterDefinition != 0) { + content = content.substring(afterDefinition); + afterAllDefinitions += afterDefinition; + } else { + break; + } + } + return afterAllDefinitions; + } + + /** + * Attempt to parse a single link reference definition, adding it to the map. + */ + private int parseDefinition(String content) { + reset(content); + + String dest; + String title = null; + int matchChars; + int startIndex = index; + + // label: + matchChars = parseLinkLabel(); + if (matchChars == 0) { + return 0; + } + + String rawLabel = input.substring(0, matchChars); + + // colon: + if (peek() != ':') { + return 0; + } + index++; + + // link url + spnl(); + + dest = parseLinkDestination(); + if (dest == null) { + return 0; + } + + int beforeTitle = index; + spnl(); + if (index != beforeTitle) { + title = parseLinkTitle(); + } + if (title == null) { + // rewind before spaces + index = beforeTitle; + } + + boolean atLineEnd = true; + if (index != input.length() && match(LINE_END) == null) { + if (title == null) { + atLineEnd = false; + } else { + // the potential title we found is not at the line end, + // but it could still be a legal link reference if we + // discard the title + title = null; + // rewind before spaces + index = beforeTitle; + // and instead check if the link URL is at the line end + atLineEnd = match(LINE_END) != null; + } + } + + if (!atLineEnd) { + return 0; + } + + String normalizedLabel = Escaping.normalizeReference(rawLabel); + if (normalizedLabel.isEmpty()) { + return 0; + } + + definitions.add(new LinkReferenceDefinition(normalizedLabel, dest, title)); + + return index - startIndex; + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index f6556ab2e..fc44cfd57 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -1,16 +1,19 @@ package org.commonmark.internal; import org.commonmark.node.Block; +import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.node.Paragraph; import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.BlockContinue; import org.commonmark.parser.block.ParserState; +import java.util.List; + public class ParagraphParser extends AbstractBlockParser { private final Paragraph block = new Paragraph(); - private BlockContent content = new BlockContent(); + private LinkReferenceDefinitionParser linkReferenceDefinitionParser = new LinkReferenceDefinitionParser(); @Override public Block getBlock() { @@ -28,25 +31,29 @@ public BlockContinue tryContinue(ParserState state) { @Override public void addLine(CharSequence line) { - content.add(line); + linkReferenceDefinitionParser.parse(line); } @Override public void closeBlock() { + if (linkReferenceDefinitionParser.getParagraphContent().length() == 0) { + block.unlink(); + } } @Override public void parseInlines(InlineParser inlineParser) { - if (content != null) { - inlineParser.parse(content.getString(), block); + CharSequence content = linkReferenceDefinitionParser.getParagraphContent(); + if (content.length() > 0) { + inlineParser.parse(content.toString(), block); } } - public String getContentString() { - return content.getString(); + public CharSequence getContentString() { + return linkReferenceDefinitionParser.getParagraphContent(); } - void setContentString(String contentString) { - content = new BlockContent(contentString); + public List getDefinitions() { + return linkReferenceDefinitionParser.getDefinitions(); } } 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 dae17d090..f25cd59e5 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java @@ -77,7 +77,7 @@ public static int scanLinkTitle(CharSequence input, int start) { return -1; } - int afterContent = scanLinkTitleContent(input, start, endDelimiter); + int afterContent = scanLinkTitleContent(input, start + 1, endDelimiter); if (afterContent == -1) { return -1; } @@ -91,7 +91,7 @@ public static int scanLinkTitle(CharSequence input, int start) { } public static int scanLinkTitleContent(CharSequence input, int start, char endDelimiter) { - for (int i = start + 1; i < input.length(); i++) { + for (int i = start; i < input.length(); i++) { char c = input.charAt(i); if (c == '\\' && Parsing.isEscapable(input, i + 1)) { i += 1; diff --git a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java new file mode 100644 index 000000000..75696ba28 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java @@ -0,0 +1,167 @@ +package org.commonmark.internal; + +import org.commonmark.internal.LinkReferenceDefinitionParser.State; +import org.commonmark.node.LinkReferenceDefinition; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class LinkReferenceDefinitionParserTest { + + private LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + + @Test + public void testStartLabel() { + parser.parse("["); + assertEquals(State.LABEL, parser.getState()); + assertEquals("[", parser.getParagraphContent().toString()); + } + + @Test + public void testStartNoLabel() { + // Not a label + assertParagraph("a"); + // Can not go back to parsing link reference definitions + parser.parse("a"); + parser.parse("["); + assertEquals(State.PARAGRAPH, parser.getState()); + assertEquals("a\n[", parser.getParagraphContent().toString()); + } + + @Test + public void testEmptyLabel() { + assertParagraph("[]: /"); + assertParagraph("[ ]: /"); + assertParagraph("[ \t\n\u000B\f\r ]: /"); + } + + @Test + public void testLabelColon() { + assertParagraph("[foo] : /"); + } + + @Test + public void testLabel() { + assertState("[foo]:", State.DESTINATION, "[foo]:"); + assertState("[ foo ]:", State.DESTINATION, "[ foo ]:"); + } + + @Test + public void testLabelInvalid() { + assertParagraph("[foo[]:"); + } + + @Test + public void testLabelMultiline() { + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + parser.parse("[two"); + assertEquals(State.LABEL, parser.getState()); + parser.parse("lines]:"); + assertEquals(State.DESTINATION, parser.getState()); + parser.parse("/url"); + assertEquals(State.START_TITLE, parser.getState()); + assertDef(parser.getDefinitions().get(0), "two lines", "/url", null); + } + + @Test + public void testDestination() { + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + parser.parse("[foo]: /url"); + assertEquals(State.START_TITLE, parser.getState()); + assertEquals("", parser.getParagraphContent().toString()); + + assertEquals(1, parser.getDefinitions().size()); + assertDef(parser.getDefinitions().get(0), "foo", "/url", null); + + parser.parse("[bar]: "); + assertDef(parser.getDefinitions().get(1), "bar", "/url2", null); + } + + @Test + public void testDestinationInvalid() { + assertParagraph("[foo]: "); + } + + @Test + public void testTitle() { + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + parser.parse("[foo]: /url 'title'"); + assertEquals(State.START_DEFINITION, parser.getState()); + assertEquals("", parser.getParagraphContent().toString()); + + assertEquals(1, parser.getDefinitions().size()); + assertDef(parser.getDefinitions().get(0), "foo", "/url", "title"); + } + + @Test + public void testTitleStartWhitespace() { + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + parser.parse("[foo]: /url"); + assertEquals(State.START_TITLE, parser.getState()); + assertEquals("", parser.getParagraphContent().toString()); + + parser.parse(" "); + + assertEquals(State.START_DEFINITION, parser.getState()); + assertEquals(" ", parser.getParagraphContent().toString()); + + assertEquals(1, parser.getDefinitions().size()); + assertDef(parser.getDefinitions().get(0), "foo", "/url", null); + } + + @Test + public void testTitleMultiline() { + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + parser.parse("[foo]: /url 'two"); + assertEquals(State.TITLE, parser.getState()); + assertEquals("[foo]: /url 'two", parser.getParagraphContent().toString()); + assertEquals(0, parser.getDefinitions().size()); + + parser.parse("lines"); + assertEquals(State.TITLE, parser.getState()); + assertEquals("[foo]: /url 'two\nlines", parser.getParagraphContent().toString()); + assertEquals(0, parser.getDefinitions().size()); + + parser.parse("'"); + assertEquals(State.START_DEFINITION, parser.getState()); + assertEquals("", parser.getParagraphContent().toString()); + + assertEquals(1, parser.getDefinitions().size()); + assertDef(parser.getDefinitions().get(0), "foo", "/url", "two\nlines\n"); + } + + @Test + public void testTitleMultiline2() { + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + parser.parse("[foo]: /url '"); + assertEquals(State.TITLE, parser.getState()); + parser.parse("title'"); + assertEquals(State.START_DEFINITION, parser.getState()); + + assertDef(parser.getDefinitions().get(0), "foo", "/url", "\ntitle"); + } + + @Test + public void testTitleInvalid() { + assertParagraph("[foo]: /url (invalid("); + assertParagraph("[foo]: 'title'"); + assertParagraph("[foo]: /url 'title' INVALID"); + } + + private static void assertParagraph(String input) { + assertState(input, State.PARAGRAPH, input); + } + + private static void assertState(String input, State state, String paragraphContent) { + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + parser.parse(input); + assertEquals(state, parser.getState()); + assertEquals(paragraphContent, parser.getParagraphContent().toString()); + } + + private static void assertDef(LinkReferenceDefinition def, String label, String destination, String title) { + assertEquals(label, def.getLabel()); + assertEquals(destination, def.getDestination()); + assertEquals(title, def.getTitle()); + } +} From 320d57069da5d94a46f180e077a48496cbf130d6 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 14:26:20 +1000 Subject: [PATCH 337/815] Cleanup after extracting link reference definition parsing --- .../commonmark/internal/InlineParserImpl.java | 16 +-- .../OldLinkReferenceDefinitionParser.java | 124 ------------------ 2 files changed, 8 insertions(+), 132 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/OldLinkReferenceDefinitionParser.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 00da9134d..7d6612c07 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -16,7 +16,7 @@ import java.util.regex.Pattern; public class InlineParserImpl implements InlineParser { - + private static final String HTMLCOMMENT = "|"; private static final String PROCESSINGINSTRUCTION = "[<][?].*?[?][>]"; private static final String DECLARATION = "]*>"; @@ -58,8 +58,8 @@ public class InlineParserImpl implements InlineParser { private final Map delimiterProcessors; private final InlineParserContext context; - String input; - int index; + private String input; + private int index; /** * Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different @@ -244,7 +244,7 @@ private Node parseInline(Node previous) { /** * If RE matches at current index in the input, advance index and return the match; otherwise return null. */ - String match(Pattern re) { + private String match(Pattern re) { if (index >= input.length()) { return null; } @@ -262,7 +262,7 @@ String match(Pattern re) { /** * Returns the char at the current input index, or {@code '\0'} in case there are no more characters. */ - char peek() { + private char peek() { if (index < input.length()) { return input.charAt(index); } else { @@ -273,7 +273,7 @@ char peek() { /** * Parse zero or more space characters, including at most one newline. */ - void spnl() { + private void spnl() { match(SPNL); } @@ -546,7 +546,7 @@ private void removeLastBracket() { /** * Attempt to parse link destination, returning the string or null if no match. */ - String parseLinkDestination() { + private String parseLinkDestination() { int afterDest = LinkScanner.scanLinkDestination(input, index); if (afterDest == -1) { return null; @@ -567,7 +567,7 @@ String parseLinkDestination() { /** * Attempt to parse link title (sans quotes), returning the string or null if no match. */ - String parseLinkTitle() { + private String parseLinkTitle() { int afterTitle = LinkScanner.scanLinkTitle(input, index); if (afterTitle == -1) { return null; diff --git a/commonmark/src/main/java/org/commonmark/internal/OldLinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/OldLinkReferenceDefinitionParser.java deleted file mode 100644 index fc01248f2..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/OldLinkReferenceDefinitionParser.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.commonmark.internal; - -import org.commonmark.internal.util.Escaping; -import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.parser.delimiter.DelimiterProcessor; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.regex.Pattern; - -/** - * @see Link reference definitions - */ -class OldLinkReferenceDefinitionParser extends InlineParserImpl { - - /** - * Parsed link reference definitions by label, in order of occurrence. - */ - private List definitions = new ArrayList<>(); - - private static final Pattern LINE_END = Pattern.compile("^ *(?:\n|$)"); - - OldLinkReferenceDefinitionParser() { - // Not needed for parsing link reference definitions - super(new InlineParserContextImpl(Collections.emptyList(), - Collections.emptyMap())); - } - - // TODO: Would be better to just return them from the method. - List getDefinitions() { - return definitions; - } - - /** - * Parse all link reference definitions, add them to the map and return the length of the text we parsed (if any). - */ - int parseDefinitions(String content) { - int afterAllDefinitions = 0; - while (content.length() > 3 && content.charAt(0) == '[') { - int afterDefinition = parseDefinition(content); - if (afterDefinition != 0) { - content = content.substring(afterDefinition); - afterAllDefinitions += afterDefinition; - } else { - break; - } - } - return afterAllDefinitions; - } - - /** - * Attempt to parse a single link reference definition, adding it to the map. - */ - private int parseDefinition(String content) { - reset(content); - - String dest; - String title = null; - int matchChars; - int startIndex = index; - - // label: - matchChars = parseLinkLabel(); - if (matchChars == 0) { - return 0; - } - - String rawLabel = input.substring(0, matchChars); - - // colon: - if (peek() != ':') { - return 0; - } - index++; - - // link url - spnl(); - - dest = parseLinkDestination(); - if (dest == null) { - return 0; - } - - int beforeTitle = index; - spnl(); - if (index != beforeTitle) { - title = parseLinkTitle(); - } - if (title == null) { - // rewind before spaces - index = beforeTitle; - } - - boolean atLineEnd = true; - if (index != input.length() && match(LINE_END) == null) { - if (title == null) { - atLineEnd = false; - } else { - // the potential title we found is not at the line end, - // but it could still be a legal link reference if we - // discard the title - title = null; - // rewind before spaces - index = beforeTitle; - // and instead check if the link URL is at the line end - atLineEnd = match(LINE_END) != null; - } - } - - if (!atLineEnd) { - return 0; - } - - String normalizedLabel = Escaping.normalizeReference(rawLabel); - if (normalizedLabel.isEmpty()) { - return 0; - } - - definitions.add(new LinkReferenceDefinition(normalizedLabel, dest, title)); - - return index - startIndex; - } -} From bdbc9d3b563ec90fb580d7a94677206047ab6da7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 14:49:14 +1000 Subject: [PATCH 338/815] Fix edge cases around link parsing --- .../internal/LinkReferenceDefinitionParser.java | 14 +++++++++++--- .../LinkReferenceDefinitionParserTest.java | 12 ++++++++++++ .../java/org/commonmark/test/SpecialInputTest.java | 5 +++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index e4ddfa8d0..1fe2cbea7 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -92,7 +92,12 @@ private int startDefinition(CharSequence line, int i) { state = State.LABEL; label = new StringBuilder(); - return i + 1; + int labelStart = i + 1; + if (labelStart >= line.length()) { + label.append('\n'); + } + + return labelStart; } private int label(CharSequence line, int i) { @@ -114,7 +119,10 @@ private int label(CharSequence line, int i) { return -1; } - int afterSpace = Parsing.skipSpaceTab(line, colon + 1, line.length()); + // spec: A link label can have at most 999 characters inside the square brackets. + if (label.length() > 999) { + return -1; + } String normalizedLabel = Escaping.normalizeLabelContent(label.toString()); if (normalizedLabel.isEmpty()) { @@ -124,7 +132,7 @@ private int label(CharSequence line, int i) { this.normalizedLabel = normalizedLabel; state = State.DESTINATION; - return afterSpace; + return Parsing.skipSpaceTab(line, colon + 1, line.length()); } else { return -1; } diff --git a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java index 75696ba28..f0bdef492 100644 --- a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java @@ -63,6 +63,18 @@ public void testLabelMultiline() { assertDef(parser.getDefinitions().get(0), "two lines", "/url", null); } + @Test + public void testLabelStartsWithNewline() { + LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + parser.parse("["); + assertEquals(State.LABEL, parser.getState()); + parser.parse("weird]:"); + assertEquals(State.DESTINATION, parser.getState()); + parser.parse("/url"); + assertEquals(State.START_TITLE, parser.getState()); + assertDef(parser.getDefinitions().get(0), "weird", "/url", null); + } + @Test public void testDestination() { LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 1550d3197..55ba71e2f 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -114,12 +114,13 @@ public void linkDestinationEscaping() { assertRendering("[foo](\\))", "

    foo

    \n"); // ` ` is not escapable, so the backslash is a literal backslash and there's an optional space at the end assertRendering("[foo](\\ )", "

    foo

    \n"); - // Backslash escapes `>`, so it's not a `(<...>)` link, but a `(...)` link instead - assertRendering("[foo](<\\>)", "

    foo

    \n"); // Backslash is a literal, so valid assertRendering("[foo]()", "

    foo

    \n"); // Backslash escapes `>` but there's another `>`, valid assertRendering("[foo](>)", "

    foo

    \n"); + + // This is a tricky one. There's `<` so we try to parse it as a `<` link but fail. + assertRendering("[foo](<\\>)", "

    [foo](<>)

    \n"); } // commonmark/CommonMark#468 From 48614f381bc532f185c9815bcbff6f248949186e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 14:50:07 +1000 Subject: [PATCH 339/815] Disallow lists indented more than 3 spaces (spec 0.29) --- .../commonmark/internal/ListBlockParser.java | 2 +- .../org/commonmark/test/SpecialInputTest.java | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index 28f9bfb0f..de1558f92 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -205,7 +205,7 @@ public static class Factory extends AbstractBlockParserFactory { public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { BlockParser matched = matchedBlockParser.getMatchedBlockParser(); - if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT && !(matched instanceof ListBlockParser)) { + if (state.getIndent() >= Parsing.CODE_BLOCK_INDENT) { return BlockStart.none(); } int markerIndex = state.getNextNonSpaceIndex(); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 55ba71e2f..c2bb9fd4c 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -139,4 +139,27 @@ public void linkReferenceBackslash() { public void emphasisMultipleOf3Rule() { assertRendering("a***b* c*", "

    a*b c

    \n"); } + + @Test + public void deeplyIndentedList() { + assertRendering("* one\n" + + " * two\n" + + " * three\n" + + " * four", + "
      \n" + + "
    • one\n" + + "
        \n" + + "
      • two\n" + + "
          \n" + + "
        • three\n" + + "
            \n" + + "
          • four
          • \n" + + "
          \n" + + "
        • \n" + + "
        \n" + + "
      • \n" + + "
      \n" + + "
    • \n" + + "
    \n"); + } } From e760f758733e4c1e01fef0b6fa541ac46391054a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 14:56:20 +1000 Subject: [PATCH 340/815] Adjust delimiter test to not use code block marker --- .../test/DelimiterProcessorTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index e7348f5dd..948c484cd 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -52,18 +52,18 @@ public void asymmetricDelimiter() { @Test public void multipleDelimitersWithDifferentLengths() { Parser parser = Parser.builder() - .customDelimiterProcessor(new OneTildeDelimiterProcessor()) - .customDelimiterProcessor(new TwoTildesDelimiterProcessor()) + .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~~~"))); + 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+++"))); } @Test(expected = IllegalArgumentException.class) public void multipleDelimitersWithSameLength() { Parser.builder() - .customDelimiterProcessor(new OneTildeDelimiterProcessor()) - .customDelimiterProcessor(new OneTildeDelimiterProcessor()) + .customDelimiterProcessor(new OneDelimiterProcessor()) + .customDelimiterProcessor(new OneDelimiterProcessor()) .build(); } @@ -180,16 +180,16 @@ public void render(Node node) { } } - private static class OneTildeDelimiterProcessor implements DelimiterProcessor { + private static class OneDelimiterProcessor implements DelimiterProcessor { @Override public char getOpeningCharacter() { - return '~'; + return '+'; } @Override public char getClosingCharacter() { - return '~'; + return '+'; } @Override @@ -209,16 +209,16 @@ public void process(Text opener, Text closer, int delimiterUse) { } } - private static class TwoTildesDelimiterProcessor implements DelimiterProcessor { + private static class TwoDelimiterProcessor implements DelimiterProcessor { @Override public char getOpeningCharacter() { - return '~'; + return '+'; } @Override public char getClosingCharacter() { - return '~'; + return '+'; } @Override From 08b774819680fbc0b862e770567ac05c07209302 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 14:58:52 +1000 Subject: [PATCH 341/815] Adjust tests to spec changes --- .../java/org/commonmark/test/FencedCodeBlockParserTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java index 1a64a6374..774c6ff0e 100644 --- a/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/FencedCodeBlockParserTest.java @@ -26,14 +26,12 @@ public void backtickInfo() { public void backtickInfoDoesntAllowBacktick() { assertRendering("```info ` test\ncode\n```", "

    ```info ` test\ncode

    \n
    \n"); - // Note, it's unclear in the spec whether a ~~~ code block can contain ` in info or not, see: - // https://github.com/commonmark/CommonMark/issues/119 } @Test public void backtickAndTildeCantBeMixed() { assertRendering("``~`\ncode\n``~`", - "

    ~` code~`

    \n"); + "

    ~` code ~`

    \n"); } @Test From 490af42e6f7347be71acda12c6946020f9c8ddde Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 17:12:51 +1000 Subject: [PATCH 342/815] Update regression tests from commonmark.js --- .../src/main/resources/commonmark.js-regression.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/commonmark-test-util/src/main/resources/commonmark.js-regression.txt b/commonmark-test-util/src/main/resources/commonmark.js-regression.txt index a99620bb1..ec5143eff 100644 --- a/commonmark-test-util/src/main/resources/commonmark.js-regression.txt +++ b/commonmark-test-util/src/main/resources/commonmark.js-regression.txt @@ -147,3 +147,15 @@ Issue #289. .

    [a](<b) c>

    ```````````````````````````````` + +Issue #161. + +```````````````````````````````` example +*failed to be italic!*\ +text +. +

    failed to be italic!
    +text

    +```````````````````````````````` + + From ab7c1c7051b2f6a6521635b96830fe776ca89906 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 17:19:53 +1000 Subject: [PATCH 343/815] No longer treat as a block tag (spec 0.29) --- .../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 c80822d3c..3b3a0e64f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -42,7 +42,7 @@ public class HtmlBlockParser extends AbstractBlockParser { "h1|h2|h3|h4|h5|h6|head|header|hr|html|" + "iframe|" + "legend|li|link|" + - "main|menu|menuitem|meta|" + + "main|menu|menuitem|" + "nav|noframes|" + "ol|optgroup|option|" + "p|param|" + From c5f5a348acd1c67b846355607f0c7d72c1d39558 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Thu, 11 Jul 2019 17:22:18 +1000 Subject: [PATCH 344/815] Don't preserve entities when rendering HTML attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/commonmark/commonmark.js/commit/c89b35c5fc99bdf1d2181f7f0c9fcb8a1abc27c8 Also replaced the regex for escaping with a loop, which speeds up HTML rendering: -SpecBenchmark.parseAndRenderExamples thrpt 50 344.820 ± 1.215 ops/s +SpecBenchmark.parseAndRenderExamples thrpt 50 374.342 ± 2.445 ops/s -SpecBenchmark.parseAndRenderWholeSpec thrpt 50 151.209 ± 1.148 ops/s +SpecBenchmark.parseAndRenderWholeSpec thrpt 50 198.357 ± 2.601 ops/s (Note that these benchmarks include parsing, so rendering itself saw a very nice improvement.) --- .../commonmark/internal/util/Escaping.java | 67 ++++++++++--------- .../commonmark/renderer/html/HtmlWriter.java | 6 +- .../internal/util/EscapingTest.java | 21 ++++++ .../org/commonmark/test/HtmlRendererTest.java | 9 +++ .../org/commonmark/test/RegressionTest.java | 20 +++++- 5 files changed, 87 insertions(+), 36 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java 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 6a27f9419..dbcdc4a5a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -16,13 +16,6 @@ public class Escaping { private static final Pattern ENTITY_OR_ESCAPED_CHAR = Pattern.compile("\\\\" + ESCAPABLE + '|' + ENTITY, Pattern.CASE_INSENSITIVE); - private static final String XML_SPECIAL = "[&<>\"]"; - - private static final Pattern XML_SPECIAL_RE = Pattern.compile(XML_SPECIAL); - - private static final Pattern XML_SPECIAL_OR_ENTITY = - Pattern.compile(ENTITY + '|' + XML_SPECIAL, Pattern.CASE_INSENSITIVE); - // From RFC 3986 (see "reserved", "unreserved") except don't escape '[' or ']' to be compatible with JS encodeURI private static final Pattern ESCAPE_IN_URI = Pattern.compile("(%[a-fA-F0-9]{0,2}|[^:/?#@!$&'()*+,;=a-zA-Z0-9\\-._~])"); @@ -32,28 +25,6 @@ public class Escaping { private static final Pattern WHITESPACE = Pattern.compile("[ \t\r\n]+"); - private static final Replacer UNSAFE_CHAR_REPLACER = new Replacer() { - @Override - public void replace(String input, StringBuilder sb) { - switch (input) { - case "&": - sb.append("&"); - break; - case "<": - sb.append("<"); - break; - case ">": - sb.append(">"); - break; - case "\"": - sb.append("""); - break; - default: - sb.append(input); - } - } - }; - private static final Replacer UNESCAPE_REPLACER = new Replacer() { @Override public void replace(String input, StringBuilder sb) { @@ -88,9 +59,41 @@ public void replace(String input, StringBuilder sb) { } }; - public static String escapeHtml(String input, boolean preserveEntities) { - Pattern p = preserveEntities ? XML_SPECIAL_OR_ENTITY : XML_SPECIAL_RE; - return replaceAll(p, input, UNSAFE_CHAR_REPLACER); + public static String escapeHtml(String input) { + // Avoid building a new string in the majority of cases (nothing to escape) + StringBuilder sb = null; + + loop: + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + String replacement; + switch (c) { + case '&': + replacement = "&"; + break; + case '<': + replacement = "<"; + break; + case '>': + replacement = ">"; + break; + case '\"': + replacement = """; + break; + default: + if (sb != null) { + sb.append(c); + } + continue loop; + } + if (sb == null) { + sb = new StringBuilder(); + sb.append(input, 0, i); + } + sb.append(replacement); + } + + return sb != null ? sb.toString() : input; } /** 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 ec38e8f39..8c79eb8b4 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlWriter.java @@ -25,7 +25,7 @@ public void raw(String s) { } public void text(String text) { - append(Escaping.escapeHtml(text, false)); + append(Escaping.escapeHtml(text)); } public void tag(String name) { @@ -42,9 +42,9 @@ public void tag(String name, Map attrs, boolean voidElement) { if (attrs != null && !attrs.isEmpty()) { for (Map.Entry attrib : attrs.entrySet()) { append(" "); - append(Escaping.escapeHtml(attrib.getKey(), true)); + append(Escaping.escapeHtml(attrib.getKey())); append("=\""); - append(Escaping.escapeHtml(attrib.getValue(), true)); + append(Escaping.escapeHtml(attrib.getValue())); append("\""); } } diff --git a/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java b/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java new file mode 100644 index 000000000..9433eb7d0 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/internal/util/EscapingTest.java @@ -0,0 +1,21 @@ +package org.commonmark.internal.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public 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 >")); + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 6ccfe5465..30cbf24f3 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -50,6 +50,15 @@ public void textEscaping() { assertEquals("

    escaping: & < > " '

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

    \n", defaultRenderer().render(paragraph)); + } + @Test public void percentEncodeUrlDisabled() { assertEquals("

    a

    \n", defaultRenderer().render(parse("[a](foo&bar)"))); diff --git a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java index 5d49c2abd..c4a0d3be5 100644 --- a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java +++ b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java @@ -13,7 +13,9 @@ import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @RunWith(Parameterized.class) public class RegressionTest extends RenderingTestCase { @@ -22,6 +24,8 @@ public class RegressionTest extends RenderingTestCase { // The spec says URL-escaping is optional, but the examples assume that it's enabled. private static final HtmlRenderer RENDERER = HtmlRenderer.builder().percentEncodeUrls(true).build(); + private static final Map OVERRIDDEN_EXAMPLES = getOverriddenExamples(); + private final Example example; public RegressionTest(Example example) { @@ -42,11 +46,25 @@ public static List data() { @Test public void testHtmlRendering() { - assertRendering(example.getSource(), example.getHtml()); + String expectedHtml = OVERRIDDEN_EXAMPLES.get(example.getSource()); + if (expectedHtml == null) { + expectedHtml = example.getHtml(); + } + assertRendering(example.getSource(), expectedHtml); } @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); } + + private static Map getOverriddenExamples() { + Map m = new HashMap<>(); + + // The only difference is that we don't change `%28` and `%29` to `(` and `)` (percent encoding is preserved) + m.put("[XSS](javascript&colon;alert%28'XSS'%29)\n", + "

    XSS

    \n"); + + return m; + } } From c8ccf85a7a0b57fa42e3ce02c44214aaefd3f1d7 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Jul 2019 14:20:42 +1000 Subject: [PATCH 345/815] Fix strikethrough test by avoiding confusion with code block --- .../org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 85ae72eb2..225977854 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 @@ -44,7 +44,7 @@ public void unmatched() { @Test public void threeInnerThree() { - assertRendering("~~~foo~~~", "

    ~foo~

    \n"); + assertRendering("a ~~~foo~~~", "

    a ~foo~

    \n"); } @Test From b1d8bb49cb1852822ab3e2e54473daa3887baf6a Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Jul 2019 14:24:57 +1000 Subject: [PATCH 346/815] Adjust max length for decimal/numeric entities See commonmark/commonmark-spec#487 --- .../main/java/org/commonmark/internal/InlineParserImpl.java | 3 +-- .../src/main/java/org/commonmark/internal/util/Escaping.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 7d6612c07..38972cada 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -23,7 +23,6 @@ public class InlineParserImpl implements InlineParser { private static final String CDATA = ""; private static final String HTMLTAG = "(?:" + Parsing.OPENTAG + "|" + Parsing.CLOSETAG + "|" + HTMLCOMMENT + "|" + PROCESSINGINSTRUCTION + "|" + DECLARATION + "|" + CDATA + ")"; - private static final String ENTITY = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"; private static final String ASCII_PUNCTUATION = "!\"#\\$%&'\\(\\)\\*\\+,\\-\\./:;<=>\\?@\\[\\\\\\]\\^_`\\{\\|\\}~"; private static final Pattern PUNCTUATION = Pattern @@ -33,7 +32,7 @@ public class InlineParserImpl implements InlineParser { private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); - private static final Pattern ENTITY_HERE = Pattern.compile('^' + ENTITY, Pattern.CASE_INSENSITIVE); + private static final Pattern ENTITY_HERE = Pattern.compile('^' + Escaping.ENTITY, Pattern.CASE_INSENSITIVE); private static final Pattern TICKS = Pattern.compile("`+"); 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 dbcdc4a5a..15197556c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -9,7 +9,7 @@ public class Escaping { public static final String ESCAPABLE = "[!\"#$%&\'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~-]"; - private static final String ENTITY = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"; + public static final String ENTITY = "&(?:#x[a-f0-9]{1,6}|#[0-9]{1,7}|[a-z][a-z0-9]{1,31});"; private static final Pattern BACKSLASH_OR_AMP = Pattern.compile("[\\\\&]"); From 3011dcb7e1cbff761994e803696e01aa37c826d8 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Jul 2019 14:57:10 +1000 Subject: [PATCH 347/815] Adjust comment and remove TODO --- .../java/org/commonmark/internal/DocumentParser.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 886db2bf2..31716da88 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -453,12 +453,11 @@ private void prepareActiveBlockParserForReplacement() { if (old instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) old; - // TODO: adjust comment? // 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 our - // implementation of that, we strip link reference definitions from the paragraph content before we give it - // to the block parser. We want to keep them. If no replacement happens, we collect the definitions as part - // of finalizing paragraph blocks. + // 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. addDefinitionsFrom(paragraphParser); } From cb43ae953f14f1399447e17517946800e21b9453 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 12 Jul 2019 16:08:52 +1000 Subject: [PATCH 348/815] Add LinkReferenceDefinition nodes into document This is part of #98 and was enabled by the refactoring of definition parsing for spec 0.29. --- CHANGELOG.md | 2 + .../commonmark/internal/DocumentParser.java | 11 +- .../node/LinkReferenceDefinition.java | 3 - .../test/LinkReferenceDefinitionNodeTest.java | 119 ++++++++++++++++++ .../test/java/org/commonmark/test/Nodes.java | 17 +++ 5 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java create mode 100644 commonmark/src/test/java/org/commonmark/test/Nodes.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e470514..57b73ef80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ with the exception that 0.x versions can break between minor versions. ## Unreleased ### Added +- `LinkReferenceDefinition` nodes are now part of the document (not + rendered by default). - `InlineParserContext.getLinkReferenceDefinition` was added to allow custom inline parsers to look up definitions for reference links. ### Changed diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 31716da88..9c186e87d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -391,17 +391,18 @@ private void finalize(BlockParser blockParser) { deactivateBlockParser(); } - blockParser.closeBlock(); - if (blockParser instanceof ParagraphParser) { - ParagraphParser paragraphParser = (ParagraphParser) blockParser; - // TODO: Insert resulting nodes into AST (before paragraph node) - addDefinitionsFrom(paragraphParser); + addDefinitionsFrom((ParagraphParser) blockParser); } + + blockParser.closeBlock(); } private void addDefinitionsFrom(ParagraphParser paragraphParser) { for (LinkReferenceDefinition definition : paragraphParser.getDefinitions()) { + // Add nodes into document before paragraph. + paragraphParser.getBlock().insertBefore(definition); + String label = definition.getLabel(); // spec: When there are multiple matching link reference definitions, the first is used if (!definitions.containsKey(label)) { diff --git a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java index a4578e99b..3f8bfd0f0 100644 --- a/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java +++ b/commonmark/src/main/java/org/commonmark/node/LinkReferenceDefinition.java @@ -1,8 +1,5 @@ package org.commonmark.node; -// TODO: We're currently not adding these to the document. -// But that would be very useful for being able to render Nodes back to Markdown, see #98. - /** * A link reference definition, e.g.: *
    
    diff --git a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java
    new file mode 100644
    index 000000000..37b3d5dcd
    --- /dev/null
    +++ b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java
    @@ -0,0 +1,119 @@
    +package org.commonmark.test;
    +
    +import org.commonmark.node.*;
    +import org.commonmark.parser.Parser;
    +import org.junit.Test;
    +
    +import java.util.List;
    +
    +import static org.hamcrest.CoreMatchers.instanceOf;
    +import static org.hamcrest.CoreMatchers.is;
    +import static org.junit.Assert.assertThat;
    +
    +public class LinkReferenceDefinitionNodeTest {
    +
    +    @Test
    +    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));
    +        LinkReferenceDefinition definition = assertDef(nodes.get(1), "foo");
    +
    +        assertThat(definition.getDestination(), is("/url"));
    +        assertThat(definition.getTitle(), is("title"));
    +    }
    +
    +    @Test
    +    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));
    +        // 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));
    +    }
    +
    +    @Test
    +    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));
    +        assertDef(nodes.get(1), "foo");
    +        assertDef(nodes.get(2), "bar");
    +    }
    +
    +    @Test
    +    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));
    +        LinkReferenceDefinition def1 = assertDef(nodes.get(1), "foo");
    +        assertThat(def1.getDestination(), is("/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"));
    +    }
    +
    +    @Test
    +    public void testDefinitionOfReplacedBlock() {
    +        Node document = parse("[foo]: /url\nHeading\n=======");
    +        List nodes = Nodes.getChildren(document);
    +
    +        assertThat(nodes.size(), is(2));
    +        assertDef(nodes.get(0), "foo");
    +        assertThat(nodes.get(1), instanceOf(Heading.class));
    +    }
    +
    +    @Test
    +    public void testDefinitionInListItem() {
    +        Node document = parse("* [foo]: /url\n  [foo]\n");
    +        assertThat(document.getFirstChild(), instanceOf(BulletList.class));
    +        Node item = document.getFirstChild().getFirstChild();
    +        assertThat(item, instanceOf(ListItem.class));
    +
    +        List nodes = Nodes.getChildren(item);
    +        assertThat(nodes.size(), is(2));
    +        assertDef(nodes.get(0), "foo");
    +        assertThat(nodes.get(1), instanceOf(Paragraph.class));
    +    }
    +
    +    @Test
    +    public void testDefinitionInListItem2() {
    +        Node document = parse("* [foo]: /url\n* [foo]\n");
    +        assertThat(document.getFirstChild(), instanceOf(BulletList.class));
    +
    +        List items = Nodes.getChildren(document.getFirstChild());
    +        assertThat(items.size(), is(2));
    +        Node item1 = items.get(0);
    +        Node item2 = items.get(1);
    +
    +        assertThat(item1, instanceOf(ListItem.class));
    +        assertThat(item2, instanceOf(ListItem.class));
    +
    +        assertThat(Nodes.getChildren(item1).size(), is(1));
    +        assertDef(item1.getFirstChild(), "foo");
    +
    +        assertThat(Nodes.getChildren(item2).size(), is(1));
    +        assertThat(item2.getFirstChild(), instanceOf(Paragraph.class));
    +    }
    +
    +    private static Node parse(String input) {
    +        Parser parser = Parser.builder().build();
    +        return parser.parse(input);
    +    }
    +
    +    private static LinkReferenceDefinition assertDef(Node node, String label) {
    +        assertThat(node, instanceOf(LinkReferenceDefinition.class));
    +        LinkReferenceDefinition def = (LinkReferenceDefinition) node;
    +        assertThat(def.getLabel(), is(label));
    +        return def;
    +    }
    +}
    diff --git a/commonmark/src/test/java/org/commonmark/test/Nodes.java b/commonmark/src/test/java/org/commonmark/test/Nodes.java
    new file mode 100644
    index 000000000..bbc019a6a
    --- /dev/null
    +++ b/commonmark/src/test/java/org/commonmark/test/Nodes.java
    @@ -0,0 +1,17 @@
    +package org.commonmark.test;
    +
    +import org.commonmark.node.Node;
    +
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +public class Nodes {
    +
    +    public static List getChildren(Node parent) {
    +        List children = new ArrayList<>();
    +        for (Node child = parent.getFirstChild(); child != null; child = child.getNext()) {
    +            children.add(child);
    +        }
    +        return children;
    +    }
    +}
    
    From cbc09cc2ee08076b3bfe15e261fe1b828cb9a997 Mon Sep 17 00:00:00 2001
    From: Robin Stocker 
    Date: Fri, 12 Jul 2019 16:40:37 +1000
    Subject: [PATCH 349/815] Add gfm spec and test for table extension
    
    Will make changes to implementation later.
    ---
     .../ext/gfm/tables/TablesSpecTest.java        |    54 +
     .../src/test/resources/gfm-spec.txt           | 10227 ++++++++++++++++
     .../commonmark/testutil/example/Example.java  |     8 +-
     .../testutil/example/ExampleReader.java       |     8 +-
     etc/update-spec.sh                            |     1 +
     5 files changed, 10295 insertions(+), 3 deletions(-)
     create mode 100644 commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java
     create mode 100644 commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt
    
    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
    new file mode 100644
    index 000000000..12c806e32
    --- /dev/null
    +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesSpecTest.java
    @@ -0,0 +1,54 @@
    +package org.commonmark.ext.gfm.tables;
    +
    +import org.commonmark.Extension;
    +import org.commonmark.parser.Parser;
    +import org.commonmark.renderer.html.HtmlRenderer;
    +import org.commonmark.testutil.RenderingTestCase;
    +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 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 Parser PARSER = Parser.builder().extensions(EXTENSIONS).build();
    +    private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build();
    +
    +    private final Example example;
    +
    +    public TablesSpecTest(Example example) {
    +        this.example = example;
    +    }
    +
    +    @Parameters(name = "{0}")
    +    public static List data() {
    +        List examples = ExampleReader.readExamples(TestResources.class.getResource("/gfm-spec.txt"));
    +        List data = new ArrayList<>();
    +        for (Example example : examples) {
    +            if (example.getInfo().contains("table")) {
    +                data.add(new Object[]{example});
    +            }
    +        }
    +        return data;
    +    }
    +
    +    @Test
    +    public void testHtmlRendering() {
    +        assertRendering(example.getSource(), example.getHtml());
    +    }
    +
    +    @Override
    +    protected String render(String source) {
    +        return RENDERER.render(PARSER.parse(source));
    +    }
    +}
    diff --git a/commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt b/commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt
    new file mode 100644
    index 000000000..582131d70
    --- /dev/null
    +++ b/commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt
    @@ -0,0 +1,10227 @@
    +---
    +title: GitHub Flavored Markdown Spec
    +version: 0.29
    +date: '2019-04-06'
    +license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)'
    +...
    +
    +# Introduction
    +
    +## What is GitHub Flavored Markdown?
    +
    +GitHub Flavored Markdown, often shortened as GFM, is the dialect of Markdown
    +that is currently supported for user content on GitHub.com and GitHub
    +Enterprise.
    +
    +This formal specification, based on the CommonMark Spec, defines the syntax and
    +semantics of this dialect.
    +
    +GFM is a strict superset of CommonMark. All the features which are supported in
    +GitHub user content and that are not specified on the original CommonMark Spec
    +are hence known as **extensions**, and highlighted as such.
    +
    +While GFM supports a wide range of inputs, it's worth noting that GitHub.com
    +and GitHub Enterprise perform additional post-processing and sanitization after
    +GFM is converted to HTML to ensure security and consistency of the website.
    +
    +## What is Markdown?
    +
    +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)
    +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
    +Markdown syntax with conventions for footnotes, tables, and
    +other document elements.  Some allowed Markdown documents to be
    +rendered in formats other than HTML.  Websites like Reddit,
    +StackOverflow, and GitHub had millions of people using Markdown.
    +And Markdown started to be used beyond the web, to author books,
    +articles, slide shows, letters, and lecture notes.
    +
    +What distinguishes Markdown from many other lightweight markup
    +syntaxes, which are often easier to write, is its readability.
    +As Gruber writes:
    +
    +> The overriding design goal for Markdown's formatting syntax is
    +> to make it as readable as possible. The idea is that a
    +> 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
    +an equivalent sample of Markdown.  Here is a sample of
    +AsciiDoc from the AsciiDoc manual:
    +
    +```
    +1. List item one.
    ++
    +List item one continued with a second paragraph followed by an
    +Indented block.
    ++
    +.................
    +$ ls *.sh
    +$ mv *.sh ~/tmp
    +.................
    ++
    +List item continued with a third paragraph.
    +
    +2. List item two continued with an open block.
    ++
    +--
    +This paragraph is part of the preceding list item.
    +
    +a. This list is nested and does not require explicit item
    +continuation.
    ++
    +This paragraph is part of the preceding list item.
    +
    +b. List item b.
    +
    +This paragraph belongs to item two of the outer list.
    +--
    +```
    +
    +And here is the equivalent in Markdown:
    +```
    +1.  List item one.
    +
    +    List item one continued with a second paragraph followed by an
    +    Indented block.
    +
    +        $ ls *.sh
    +        $ mv *.sh ~/tmp
    +
    +    List item continued with a third paragraph.
    +
    +2.  List item two continued with an open block.
    +
    +    This paragraph is part of the preceding list item.
    +
    +    1. This list is nested and does not require explicit item continuation.
    +
    +       This paragraph is part of the preceding list item.
    +
    +    2. List item b.
    +
    +    This paragraph belongs to item two of the outer list.
    +```
    +
    +The AsciiDoc version is, arguably, easier to write. You don't need
    +to worry about indentation.  But the Markdown version is much easier
    +to read.  The nesting of list items is apparent to the eye in the
    +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)
    +does not specify the syntax unambiguously.  Here are some examples of
    +questions it does not answer:
    +
    +1.  How much indentation is needed for a sublist?  The spec says that
    +    continuation paragraphs need to be indented four spaces, but is
    +    not fully explicit about sublists.  It is natural to think that
    +    they, too, must be indented four spaces, but `Markdown.pl` does
    +    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).)
    +
    +2.  Is a blank line needed before a block quote or heading?
    +    Most implementations do not require the blank line.  However,
    +    this can lead to unexpected results in hard-wrapped text, and
    +    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).)
    +
    +3.  Is a blank line needed before an indented code block?
    +    (`Markdown.pl` requires it, but this is not mentioned in the
    +    documentation, and some implementations do not require it.)
    +
    +    ``` markdown
    +    paragraph
    +        code?
    +    ```
    +
    +4.  What is the exact rule for determining when list items get
    +    wrapped in `

    ` tags? Can a list be partially "loose" and partially + "tight"? What should we do with a list like this? + + ``` markdown + 1. one + + 2. two + 3. three + ``` + + Or this? + + ``` markdown + 1. one + - a + + - b + 2. two + ``` + + (There are some relevant comments by John Gruber + [here](http://article.gmane.org/gmane.text.markdown.general/2554).) + +5. Can list markers be indented? Can ordered list markers be right-aligned? + + ``` markdown + 8. item 1 + 9. item 2 + 10. item 2a + ``` + +6. Is this one list with a thematic break in its second item, + or two lists separated by a thematic break? + + ``` markdown + * a + * * * * * + * b + ``` + +7. When list markers change from numbers to bullets, do we have + two lists or one? (The Markdown syntax description suggests two, + but the perl scripts and many other implementations produce one.) + + ``` markdown + 1. fee + 2. fie + - foe + - fum + ``` + +8. What are the precedence rules for the markers of inline structure? + For example, is the following a valid link, or does the code span + take precedence ? + + ``` markdown + [a backtick (`)](/url) and [another backtick (`)](/url). + ``` + +9. What are the precedence rules for markers of emphasis and strong + emphasis? For example, how should the following be parsed? + + ``` markdown + *foo *bar* baz* + ``` + +10. What are the precedence rules between block-level and inline-level + structure? For example, how should the following be parsed? + + ``` markdown + - `a long code span can contain a hyphen like this + - and it can screw things up` + ``` + +11. Can list items include section headings? (`Markdown.pl` does not + allow this, but does allow blockquotes to include headings.) + + ``` markdown + - # Heading + ``` + +12. Can list items be empty? + + ``` markdown + * a + * + * b + ``` + +13. Can link references be defined inside block quotes or list items? + + ``` markdown + > Blockquote [foo]. + > + > [foo]: /url + ``` + +14. If there are multiple definitions for the same reference, which takes + precedence? + + ``` markdown + [foo]: /url1 + [foo]: /url2 + + [foo][] + ``` + +In the absence of a spec, early implementers consulted `Markdown.pl` +to resolve these ambiguities. But `Markdown.pl` was quite buggy, and +gave manifestly bad results in many cases, so it was not a +satisfactory replacement for a spec. + +Because there is no unambiguous spec, implementations have diverged +considerably. As a result, users are often surprised to find that +a document that renders one way on one system (say, a GitHub wiki) +renders differently on another (say, converting to docbook using +pandoc). To make matters worse, because nothing in Markdown counts +as a "syntax error," the divergence often isn't discovered right away. + +## About this document + +This document attempts to specify Markdown syntax unambiguously. +It contains many examples with side-by-side Markdown and +HTML. These are intended to double as conformance tests. An +accompanying script `spec_tests.py` can be used to run the tests +against any Markdown program: + + python test/spec_tests.py --spec spec.txt --program PROGRAM + +Since this document describes how Markdown is to be parsed into +an abstract syntax tree, it would have made sense to use an abstract +representation of the syntax tree instead of HTML. But HTML is capable +of representing the structural distinctions we need to make, and the +choice of HTML for the tests makes it possible to run the tests against +an implementation without writing an abstract syntax tree renderer. + +This document is generated from a text file, `spec.txt`, written +in Markdown with a small extension for the side-by-side tests. +The script `tools/makespec.py` can be used to convert `spec.txt` into +HTML or CommonMark (which can then be converted into other formats). + +In the examples, the `→` character is used to represent tabs. + +# Preliminaries + +## Characters and lines + +Any sequence of [characters] is a valid CommonMark +document. + +A [character](@) is a Unicode code point. Although some +code points (for example, combining accents) do not correspond to +characters in an intuitive sense, all code points count as characters +for purposes of this spec. + +This spec does not specify an encoding; it thinks of lines as composed +of [characters] rather than bytes. A conforming parser may be limited +to a certain encoding. + +A [line](@) is a sequence of zero or more [characters] +other than newline (`U+000A`) or carriage return (`U+000D`), +followed by a [line ending] or by the end of file. + +A [line ending](@) is a newline (`U+000A`), a carriage return +(`U+000D`) not followed by a newline, or a carriage return and a +following newline. + +A line containing no characters, or a line containing only spaces +(`U+0020`) or tabs (`U+0009`), is called a [blank line](@). + +The following definitions of character classes will be used in this spec: + +A [whitespace character](@) is a space +(`U+0020`), tab (`U+0009`), newline (`U+000A`), line tabulation (`U+000B`), +form feed (`U+000C`), or carriage return (`U+000D`). + +[Whitespace](@) is a sequence of one or more [whitespace +characters]. + +A [Unicode whitespace character](@) is +any code point in the Unicode `Zs` general category, or a tab (`U+0009`), +carriage return (`U+000D`), newline (`U+000A`), or form feed +(`U+000C`). + +[Unicode whitespace](@) is a sequence of one +or more [Unicode whitespace characters]. + +A [space](@) is `U+0020`. + +A [non-whitespace character](@) is any character +that is not a [whitespace character]. + +An [ASCII punctuation character](@) +is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, +`*`, `+`, `,`, `-`, `.`, `/` (U+0021–2F), +`:`, `;`, `<`, `=`, `>`, `?`, `@` (U+003A–0040), +`[`, `\`, `]`, `^`, `_`, `` ` `` (U+005B–0060), +`{`, `|`, `}`, or `~` (U+007B–007E). + +A [punctuation character](@) is an [ASCII +punctuation character] or anything in +the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. + +## Tabs + +Tabs in lines are not expanded to [spaces]. However, +in contexts where whitespace helps to define block structure, +tabs behave as if they were replaced by spaces with a tab stop +of 4 characters. + +Thus, for example, a tab can be used instead of four spaces +in an indented code block. (Note, however, that internal +tabs are passed through as literal tabs, not expanded to +spaces.) + +```````````````````````````````` example +→foo→baz→→bim +. +

    foo→baz→→bim
    +
    +```````````````````````````````` + +```````````````````````````````` example + →foo→baz→→bim +. +
    foo→baz→→bim
    +
    +```````````````````````````````` + +```````````````````````````````` example + a→a + ὐ→a +. +
    a→a
    +ὐ→a
    +
    +```````````````````````````````` + +In the following example, a continuation paragraph of a list +item is indented with a tab; this has exactly the same effect +as indentation with four spaces would: + +```````````````````````````````` example + - foo + +→bar +. +
      +
    • +

      foo

      +

      bar

      +
    • +
    +```````````````````````````````` + +```````````````````````````````` example +- foo + +→→bar +. +
      +
    • +

      foo

      +
        bar
      +
      +
    • +
    +```````````````````````````````` + +Normally the `>` that begins a block quote may be followed +optionally by a space, which is not considered part of the +content. In the following case `>` is followed by a tab, +which is treated as if it were expanded into three spaces. +Since one of these spaces is considered part of the +delimiter, `foo` is considered to be indented six spaces +inside the block quote context, so we get an indented +code block starting with two spaces. + +```````````````````````````````` example +>→→foo +. +
    +
      foo
    +
    +
    +```````````````````````````````` + +```````````````````````````````` example +-→→foo +. +
      +
    • +
        foo
      +
      +
    • +
    +```````````````````````````````` + + +```````````````````````````````` example + foo +→bar +. +
    foo
    +bar
    +
    +```````````````````````````````` + +```````````````````````````````` example + - foo + - bar +→ - baz +. +
      +
    • foo +
        +
      • bar +
          +
        • baz
        • +
        +
      • +
      +
    • +
    +```````````````````````````````` + +```````````````````````````````` example +#→Foo +. +

    Foo

    +```````````````````````````````` + +```````````````````````````````` example +*→*→*→ +. +
    +```````````````````````````````` + + +## Insecure characters + +For security reasons, the Unicode character `U+0000` must be replaced +with the REPLACEMENT CHARACTER (`U+FFFD`). + +# Blocks and inlines + +We can think of a document as a sequence of +[blocks](@)---structural elements like paragraphs, block +quotations, lists, headings, rules, and code blocks. Some blocks (like +block quotes and list items) contain other blocks; others (like +headings and paragraphs) contain [inline](@) content---text, +links, emphasized text, images, code spans, and so on. + +## Precedence + +Indicators of block structure always take precedence over indicators +of inline structure. So, for example, the following is a list with +two items, not a list with one item containing a code span: + +```````````````````````````````` example +- `one +- two` +. +
      +
    • `one
    • +
    • two`
    • +
    +```````````````````````````````` + + +This means that parsing can proceed in two steps: first, the block +structure of the document can be discerned; second, text lines inside +paragraphs, headings, and other block constructs can be parsed for inline +structure. The second step requires information about link reference +definitions that will be available only at the end of the first +step. Note that the first step requires processing lines in sequence, +but the second can be parallelized, since the inline parsing of +one block element does not affect the inline parsing of any other. + +## Container blocks and leaf blocks + +We can divide blocks into two types: +[container blocks](@), +which can contain other blocks, and [leaf blocks](@), +which cannot. + +# Leaf blocks + +This section describes the different kinds of leaf block that make up a +Markdown document. + +## Thematic breaks + +A line consisting of 0-3 spaces of indentation, followed by a sequence +of three or more matching `-`, `_`, or `*` characters, each followed +optionally by any number of spaces or tabs, forms a +[thematic break](@). + +```````````````````````````````` example +*** +--- +___ +. +
    +
    +
    +```````````````````````````````` + + +Wrong characters: + +```````````````````````````````` example ++++ +. +

    +++

    +```````````````````````````````` + + +```````````````````````````````` example +=== +. +

    ===

    +```````````````````````````````` + + +Not enough characters: + +```````````````````````````````` example +-- +** +__ +. +

    -- +** +__

    +```````````````````````````````` + + +One to three spaces indent are allowed: + +```````````````````````````````` example + *** + *** + *** +. +
    +
    +
    +```````````````````````````````` + + +Four spaces is too many: + +```````````````````````````````` example + *** +. +
    ***
    +
    +```````````````````````````````` + + +```````````````````````````````` example +Foo + *** +. +

    Foo +***

    +```````````````````````````````` + + +More than three characters may be used: + +```````````````````````````````` example +_____________________________________ +. +
    +```````````````````````````````` + + +Spaces are allowed between the characters: + +```````````````````````````````` example + - - - +. +
    +```````````````````````````````` + + +```````````````````````````````` example + ** * ** * ** * ** +. +
    +```````````````````````````````` + + +```````````````````````````````` example +- - - - +. +
    +```````````````````````````````` + + +Spaces are allowed at the end: + +```````````````````````````````` example +- - - - +. +
    +```````````````````````````````` + + +However, no other characters may occur in the line: + +```````````````````````````````` example +_ _ _ _ a + +a------ + +---a--- +. +

    _ _ _ _ a

    +

    a------

    +

    ---a---

    +```````````````````````````````` + + +It is required that all of the [non-whitespace characters] be the same. +So, this is not a thematic break: + +```````````````````````````````` example + *-* +. +

    -

    +```````````````````````````````` + + +Thematic breaks do not need blank lines before or after: + +```````````````````````````````` example +- foo +*** +- bar +. +
      +
    • foo
    • +
    +
    +
      +
    • bar
    • +
    +```````````````````````````````` + + +Thematic breaks can interrupt a paragraph: + +```````````````````````````````` example +Foo +*** +bar +. +

    Foo

    +
    +

    bar

    +```````````````````````````````` + + +If a line of dashes that meets the above conditions for being a +thematic break could also be interpreted as the underline of a [setext +heading], the interpretation as a +[setext heading] takes precedence. Thus, for example, +this is a setext heading, not a paragraph followed by a thematic break: + +```````````````````````````````` example +Foo +--- +bar +. +

    Foo

    +

    bar

    +```````````````````````````````` + + +When both a thematic break and a list item are possible +interpretations of a line, the thematic break takes precedence: + +```````````````````````````````` example +* Foo +* * * +* Bar +. +
      +
    • Foo
    • +
    +
    +
      +
    • Bar
    • +
    +```````````````````````````````` + + +If you want a thematic break in a list item, use a different bullet: + +```````````````````````````````` example +- Foo +- * * * +. +
      +
    • Foo
    • +
    • +
      +
    • +
    +```````````````````````````````` + + +## ATX headings + +An [ATX heading](@) +consists of a string of characters, parsed as inline content, between an +opening sequence of 1--6 unescaped `#` characters and an optional +closing sequence of any number of unescaped `#` characters. +The opening sequence of `#` characters must be followed by a +[space] or by the end of line. The optional closing sequence of `#`s must be +preceded by a [space] and may be followed by spaces only. The opening +`#` character may be indented 0-3 spaces. The raw contents of the +heading are stripped of leading and trailing spaces before being parsed +as inline content. The heading level is equal to the number of `#` +characters in the opening sequence. + +Simple headings: + +```````````````````````````````` example +# foo +## foo +### foo +#### foo +##### foo +###### foo +. +

    foo

    +

    foo

    +

    foo

    +

    foo

    +
    foo
    +
    foo
    +```````````````````````````````` + + +More than six `#` characters is not a heading: + +```````````````````````````````` example +####### foo +. +

    ####### foo

    +```````````````````````````````` + + +At least one space is required between the `#` characters and the +heading's contents, unless the heading is empty. Note that many +implementations currently do not require the space. However, the +space was required by the +[original ATX implementation](http://www.aaronsw.com/2002/atx/atx.py), +and it helps prevent things like the following from being parsed as +headings: + +```````````````````````````````` example +#5 bolt + +#hashtag +. +

    #5 bolt

    +

    #hashtag

    +```````````````````````````````` + + +This is not a heading, because the first `#` is escaped: + +```````````````````````````````` example +\## foo +. +

    ## foo

    +```````````````````````````````` + + +Contents are parsed as inlines: + +```````````````````````````````` example +# foo *bar* \*baz\* +. +

    foo bar *baz*

    +```````````````````````````````` + + +Leading and trailing [whitespace] is ignored in parsing inline content: + +```````````````````````````````` example +# foo +. +

    foo

    +```````````````````````````````` + + +One to three spaces indentation are allowed: + +```````````````````````````````` example + ### foo + ## foo + # foo +. +

    foo

    +

    foo

    +

    foo

    +```````````````````````````````` + + +Four spaces are too much: + +```````````````````````````````` example + # foo +. +
    # foo
    +
    +```````````````````````````````` + + +```````````````````````````````` example +foo + # bar +. +

    foo +# bar

    +```````````````````````````````` + + +A closing sequence of `#` characters is optional: + +```````````````````````````````` example +## foo ## + ### bar ### +. +

    foo

    +

    bar

    +```````````````````````````````` + + +It need not be the same length as the opening sequence: + +```````````````````````````````` example +# foo ################################## +##### foo ## +. +

    foo

    +
    foo
    +```````````````````````````````` + + +Spaces are allowed after the closing sequence: + +```````````````````````````````` example +### foo ### +. +

    foo

    +```````````````````````````````` + + +A sequence of `#` characters with anything but [spaces] following it +is not a closing sequence, but counts as part of the contents of the +heading: + +```````````````````````````````` example +### foo ### b +. +

    foo ### b

    +```````````````````````````````` + + +The closing sequence must be preceded by a space: + +```````````````````````````````` example +# foo# +. +

    foo#

    +```````````````````````````````` + + +Backslash-escaped `#` characters do not count as part +of the closing sequence: + +```````````````````````````````` example +### foo \### +## foo #\## +# foo \# +. +

    foo ###

    +

    foo ###

    +

    foo #

    +```````````````````````````````` + + +ATX headings need not be separated from surrounding content by blank +lines, and they can interrupt paragraphs: + +```````````````````````````````` example +**** +## foo +**** +. +
    +

    foo

    +
    +```````````````````````````````` + + +```````````````````````````````` example +Foo bar +# baz +Bar foo +. +

    Foo bar

    +

    baz

    +

    Bar foo

    +```````````````````````````````` + + +ATX headings can be empty: + +```````````````````````````````` example +## +# +### ### +. +

    +

    +

    +```````````````````````````````` + + +## Setext headings + +A [setext heading](@) consists of one or more +lines of text, each containing at least one [non-whitespace +character], with no more than 3 spaces indentation, followed by +a [setext heading underline]. The lines of text must be such +that, were they not followed by the setext heading underline, +they would be interpreted as a paragraph: they cannot be +interpretable as a [code fence], [ATX heading][ATX headings], +[block quote][block quotes], [thematic break][thematic breaks], +[list item][list items], or [HTML block][HTML blocks]. + +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]. + +The heading is a level 1 heading if `=` characters are used in +the [setext heading underline], and a level 2 heading if `-` +characters are used. The contents of the heading are the result +of parsing the preceding lines of text as CommonMark inline +content. + +In general, a setext heading need not be preceded or followed by a +blank line. However, it cannot interrupt a paragraph, so when a +setext heading comes after a paragraph, a blank line is needed between +them. + +Simple examples: + +```````````````````````````````` example +Foo *bar* +========= + +Foo *bar* +--------- +. +

    Foo bar

    +

    Foo bar

    +```````````````````````````````` + + +The content of the header may span more than one line: + +```````````````````````````````` example +Foo *bar +baz* +==== +. +

    Foo bar +baz

    +```````````````````````````````` + +The contents are the result of parsing the headings's raw +content as inlines. The heading's raw content is formed by +concatenating the lines and removing initial and final +[whitespace]. + +```````````````````````````````` example + Foo *bar +baz*→ +==== +. +

    Foo bar +baz

    +```````````````````````````````` + + +The underlining can be any length: + +```````````````````````````````` example +Foo +------------------------- + +Foo += +. +

    Foo

    +

    Foo

    +```````````````````````````````` + + +The heading content can be indented up to three spaces, and need +not line up with the underlining: + +```````````````````````````````` example + Foo +--- + + Foo +----- + + Foo + === +. +

    Foo

    +

    Foo

    +

    Foo

    +```````````````````````````````` + + +Four spaces indent is too much: + +```````````````````````````````` example + Foo + --- + + Foo +--- +. +
    Foo
    +---
    +
    +Foo
    +
    +
    +```````````````````````````````` + + +The setext heading underline can be indented up to three spaces, and +may have trailing spaces: + +```````````````````````````````` example +Foo + ---- +. +

    Foo

    +```````````````````````````````` + + +Four spaces is too much: + +```````````````````````````````` example +Foo + --- +. +

    Foo +---

    +```````````````````````````````` + + +The setext heading underline cannot contain internal spaces: + +```````````````````````````````` example +Foo += = + +Foo +--- - +. +

    Foo += =

    +

    Foo

    +
    +```````````````````````````````` + + +Trailing spaces in the content line do not cause a line break: + +```````````````````````````````` example +Foo +----- +. +

    Foo

    +```````````````````````````````` + + +Nor does a backslash at the end: + +```````````````````````````````` example +Foo\ +---- +. +

    Foo\

    +```````````````````````````````` + + +Since indicators of block structure take precedence over +indicators of inline structure, the following are setext headings: + +```````````````````````````````` example +`Foo +---- +` + + +. +

    `Foo

    +

    `

    +

    <a title="a lot

    +

    of dashes"/>

    +```````````````````````````````` + + +The setext heading underline cannot be a [lazy continuation +line] in a list item or block quote: + +```````````````````````````````` example +> Foo +--- +. +
    +

    Foo

    +
    +
    +```````````````````````````````` + + +```````````````````````````````` example +> foo +bar +=== +. +
    +

    foo +bar +===

    +
    +```````````````````````````````` + + +```````````````````````````````` example +- Foo +--- +. +
      +
    • Foo
    • +
    +
    +```````````````````````````````` + + +A blank line is needed between a paragraph and a following +setext heading, since otherwise the paragraph becomes part +of the heading's content: + +```````````````````````````````` example +Foo +Bar +--- +. +

    Foo +Bar

    +```````````````````````````````` + + +But in general a blank line is not required before or after +setext headings: + +```````````````````````````````` example +--- +Foo +--- +Bar +--- +Baz +. +
    +

    Foo

    +

    Bar

    +

    Baz

    +```````````````````````````````` + + +Setext headings cannot be empty: + +```````````````````````````````` example + +==== +. +

    ====

    +```````````````````````````````` + + +Setext heading text lines must not be interpretable as block +constructs other than paragraphs. So, the line of dashes +in these examples gets interpreted as a thematic break: + +```````````````````````````````` example +--- +--- +. +
    +
    +```````````````````````````````` + + +```````````````````````````````` example +- foo +----- +. +
      +
    • foo
    • +
    +
    +```````````````````````````````` + + +```````````````````````````````` example + foo +--- +. +
    foo
    +
    +
    +```````````````````````````````` + + +```````````````````````````````` example +> foo +----- +. +
    +

    foo

    +
    +
    +```````````````````````````````` + + +If you want a heading with `> foo` as its literal text, you can +use backslash escapes: + +```````````````````````````````` example +\> foo +------ +. +

    > foo

    +```````````````````````````````` + + +**Compatibility note:** Most existing Markdown implementations +do not allow the text of setext headings to span multiple lines. +But there is no consensus about how to interpret + +``` markdown +Foo +bar +--- +baz +``` + +One can find four different interpretations: + +1. paragraph "Foo", heading "bar", paragraph "baz" +2. paragraph "Foo bar", thematic break, paragraph "baz" +3. paragraph "Foo bar --- baz" +4. heading "Foo bar", paragraph "baz" + +We find interpretation 4 most natural, and interpretation 4 +increases the expressive power of CommonMark, by allowing +multiline headings. Authors who want interpretation 1 can +put a blank line after the first paragraph: + +```````````````````````````````` example +Foo + +bar +--- +baz +. +

    Foo

    +

    bar

    +

    baz

    +```````````````````````````````` + + +Authors who want interpretation 2 can put blank lines around +the thematic break, + +```````````````````````````````` example +Foo +bar + +--- + +baz +. +

    Foo +bar

    +
    +

    baz

    +```````````````````````````````` + + +or use a thematic break that cannot count as a [setext heading +underline], such as + +```````````````````````````````` example +Foo +bar +* * * +baz +. +

    Foo +bar

    +
    +

    baz

    +```````````````````````````````` + + +Authors who want interpretation 3 can use backslash escapes: + +```````````````````````````````` example +Foo +bar +\--- +baz +. +

    Foo +bar +--- +baz

    +```````````````````````````````` + + +## Indented code blocks + +An [indented code block](@) is composed of one or more +[indented chunks] separated by blank lines. +An [indented chunk](@) is a sequence of non-blank lines, +each indented four or more spaces. The contents of the code block are +the literal contents of the lines, including trailing +[line endings], minus four spaces of indentation. +An indented code block has no [info string]. + +An indented code block cannot interrupt a paragraph, so there must be +a blank line between a paragraph and a following indented code block. +(A blank line is not needed, however, between a code block and a following +paragraph.) + +```````````````````````````````` example + a simple + indented code block +. +
    a simple
    +  indented code block
    +
    +```````````````````````````````` + + +If there is any ambiguity between an interpretation of indentation +as a code block and as indicating that material belongs to a [list +item][list items], the list item interpretation takes precedence: + +```````````````````````````````` example + - foo + + bar +. +
      +
    • +

      foo

      +

      bar

      +
    • +
    +```````````````````````````````` + + +```````````````````````````````` example +1. foo + + - bar +. +
      +
    1. +

      foo

      +
        +
      • bar
      • +
      +
    2. +
    +```````````````````````````````` + + + +The contents of a code block are literal text, and do not get parsed +as Markdown: + +```````````````````````````````` example +
    + *hi* + + - one +. +
    <a/>
    +*hi*
    +
    +- one
    +
    +```````````````````````````````` + + +Here we have three chunks separated by blank lines: + +```````````````````````````````` example + chunk1 + + chunk2 + + + + chunk3 +. +
    chunk1
    +
    +chunk2
    +
    +
    +
    +chunk3
    +
    +```````````````````````````````` + + +Any initial spaces beyond four will be included in the content, even +in interior blank lines: + +```````````````````````````````` example + chunk1 + + chunk2 +. +
    chunk1
    +  
    +  chunk2
    +
    +```````````````````````````````` + + +An indented code block cannot interrupt a paragraph. (This +allows hanging indents and the like.) + +```````````````````````````````` example +Foo + bar + +. +

    Foo +bar

    +```````````````````````````````` + + +However, any non-blank line with fewer than four leading spaces ends +the code block immediately. So a paragraph may occur immediately +after indented code: + +```````````````````````````````` example + foo +bar +. +
    foo
    +
    +

    bar

    +```````````````````````````````` + + +And indented code can occur immediately before and after other kinds of +blocks: + +```````````````````````````````` example +# Heading + foo +Heading +------ + foo +---- +. +

    Heading

    +
    foo
    +
    +

    Heading

    +
    foo
    +
    +
    +```````````````````````````````` + + +The first line can be indented more than four spaces: + +```````````````````````````````` example + foo + bar +. +
        foo
    +bar
    +
    +```````````````````````````````` + + +Blank lines preceding or following an indented code block +are not included in it: + +```````````````````````````````` example + + + foo + + +. +
    foo
    +
    +```````````````````````````````` + + +Trailing spaces are included in the code block's content: + +```````````````````````````````` example + foo +. +
    foo  
    +
    +```````````````````````````````` + + + +## Fenced code blocks + +A [code fence](@) is a sequence +of at least three consecutive backtick characters (`` ` ``) or +tildes (`~`). (Tildes and backticks cannot be mixed.) +A [fenced code block](@) +begins with a code fence, indented no more than three spaces. + +The line with the opening code fence may optionally contain some text +following the code fence; this is trimmed of leading and trailing +whitespace and called the [info string](@). If the [info string] comes +after a backtick fence, it may not contain any backtick +characters. (The reason for this restriction is that otherwise +some inline code would be incorrectly interpreted as the +beginning of a fenced code block.) + +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. If the leading code fence is +indented N spaces, then up to N spaces of indentation are removed from +each line of the content (if present). (If a content line is not +indented, it is preserved unchanged. If it is indented less than N +spaces, all of the indentation is removed.) + +The closing code fence may be indented up to three spaces, and may be +followed only by spaces, which are ignored. If the end of the +containing block (or document) is reached and no closing code fence +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 +behavior described here.) + +A fenced code block may interrupt a paragraph, and does not require +a blank line either before or after. + +The content of a code fence is treated as literal text, not parsed +as inlines. The first word of the [info string] is typically used to +specify the language of the code sample, and rendered in the `class` +attribute of the `code` tag. However, this spec does not mandate any +particular treatment of the [info string]. + +Here is a simple example with backticks: + +```````````````````````````````` example +``` +< + > +``` +. +
    <
    + >
    +
    +```````````````````````````````` + + +With tildes: + +```````````````````````````````` example +~~~ +< + > +~~~ +. +
    <
    + >
    +
    +```````````````````````````````` + +Fewer than three backticks is not enough: + +```````````````````````````````` example +`` +foo +`` +. +

    foo

    +```````````````````````````````` + +The closing code fence must use the same character as the opening +fence: + +```````````````````````````````` example +``` +aaa +~~~ +``` +. +
    aaa
    +~~~
    +
    +```````````````````````````````` + + +```````````````````````````````` example +~~~ +aaa +``` +~~~ +. +
    aaa
    +```
    +
    +```````````````````````````````` + + +The closing code fence must be at least as long as the opening fence: + +```````````````````````````````` example +```` +aaa +``` +`````` +. +
    aaa
    +```
    +
    +```````````````````````````````` + + +```````````````````````````````` example +~~~~ +aaa +~~~ +~~~~ +. +
    aaa
    +~~~
    +
    +```````````````````````````````` + + +Unclosed code blocks are closed by the end of the document +(or the enclosing [block quote][block quotes] or [list item][list items]): + +```````````````````````````````` example +``` +. +
    +```````````````````````````````` + + +```````````````````````````````` example +````` + +``` +aaa +. +
    
    +```
    +aaa
    +
    +```````````````````````````````` + + +```````````````````````````````` example +> ``` +> aaa + +bbb +. +
    +
    aaa
    +
    +
    +

    bbb

    +```````````````````````````````` + + +A code block can have all empty lines as its content: + +```````````````````````````````` example +``` + + +``` +. +
    
    +  
    +
    +```````````````````````````````` + + +A code block can be empty: + +```````````````````````````````` example +``` +``` +. +
    +```````````````````````````````` + + +Fences can be indented. If the opening fence is indented, +content lines will have equivalent opening indentation removed, +if present: + +```````````````````````````````` example + ``` + aaa +aaa +``` +. +
    aaa
    +aaa
    +
    +```````````````````````````````` + + +```````````````````````````````` example + ``` +aaa + aaa +aaa + ``` +. +
    aaa
    +aaa
    +aaa
    +
    +```````````````````````````````` + + +```````````````````````````````` example + ``` + aaa + aaa + aaa + ``` +. +
    aaa
    + aaa
    +aaa
    +
    +```````````````````````````````` + + +Four spaces indentation produces an indented code block: + +```````````````````````````````` example + ``` + aaa + ``` +. +
    ```
    +aaa
    +```
    +
    +```````````````````````````````` + + +Closing fences may be indented by 0-3 spaces, and their indentation +need not match that of the opening fence: + +```````````````````````````````` example +``` +aaa + ``` +. +
    aaa
    +
    +```````````````````````````````` + + +```````````````````````````````` example + ``` +aaa + ``` +. +
    aaa
    +
    +```````````````````````````````` + + +This is not a closing fence, because it is indented 4 spaces: + +```````````````````````````````` example +``` +aaa + ``` +. +
    aaa
    +    ```
    +
    +```````````````````````````````` + + + +Code fences (opening and closing) cannot contain internal spaces: + +```````````````````````````````` example +``` ``` +aaa +. +

    +aaa

    +```````````````````````````````` + + +```````````````````````````````` example +~~~~~~ +aaa +~~~ ~~ +. +
    aaa
    +~~~ ~~
    +
    +```````````````````````````````` + + +Fenced code blocks can interrupt paragraphs, and can be followed +directly by paragraphs, without a blank line between: + +```````````````````````````````` example +foo +``` +bar +``` +baz +. +

    foo

    +
    bar
    +
    +

    baz

    +```````````````````````````````` + + +Other blocks can also occur before and after fenced code blocks +without an intervening blank line: + +```````````````````````````````` example +foo +--- +~~~ +bar +~~~ +# baz +. +

    foo

    +
    bar
    +
    +

    baz

    +```````````````````````````````` + + +An [info string] can be provided after the opening code fence. +Although this spec doesn't mandate any particular treatment of +the info string, the first word is typically used to specify +the language of the code block. In HTML output, the language is +normally indicated by adding a class to the `code` element consisting +of `language-` followed by the language name. + +```````````````````````````````` example +```ruby +def foo(x) + return 3 +end +``` +. +
    def foo(x)
    +  return 3
    +end
    +
    +```````````````````````````````` + + +```````````````````````````````` example +~~~~ ruby startline=3 $%@#$ +def foo(x) + return 3 +end +~~~~~~~ +. +
    def foo(x)
    +  return 3
    +end
    +
    +```````````````````````````````` + + +```````````````````````````````` example +````; +```` +. +
    +```````````````````````````````` + + +[Info strings] for backtick code blocks cannot contain backticks: + +```````````````````````````````` example +``` aa ``` +foo +. +

    aa +foo

    +```````````````````````````````` + + +[Info strings] for tilde code blocks can contain backticks and tildes: + +```````````````````````````````` example +~~~ aa ``` ~~~ +foo +~~~ +. +
    foo
    +
    +```````````````````````````````` + + +Closing code fences cannot have [info strings]: + +```````````````````````````````` example +``` +``` aaa +``` +. +
    ``` aaa
    +
    +```````````````````````````````` + + + +## HTML blocks + +An [HTML block](@) is a group of lines that is treated +as raw HTML (and will not be escaped in HTML output). + +There are seven kinds of [HTML block], which can be defined by their +start and end conditions. The block begins with a line that meets a +[start condition](@) (after up to three spaces optional indentation). +It ends with the first subsequent line that meets a matching [end +condition](@), or the last line of the document, or the last line of +the [container block](#container-blocks) containing the current HTML +block, if no line is encountered that meets the [end condition]. If +the first line meets both the [start condition] and the [end +condition], the block will contain just that line. + +1. **Start condition:** line begins with the string ``, or the end of the line.\ +**End condition:** line contains an end tag +``, `
    `, or `` (case-insensitive; it +need not match the start tag). + +2. **Start condition:** line begins with the string ``. + +3. **Start condition:** line begins with the string ``. + +4. **Start condition:** line begins with the string ``. + +5. **Start condition:** line begins with the string +``. + +6. **Start condition:** line begins the string `<` or ``, or +the string `/>`.\ +**End condition:** line is followed by a [blank line]. + +7. **Start condition:** line begins with a complete [open tag] +(with any [tag name] other than `script`, +`style`, or `pre`) or a complete [closing tag], +followed only by [whitespace] or the end of the line.\ +**End condition:** line is followed by a [blank line]. + +HTML blocks continue until they are closed by their appropriate +[end condition], or the last line of the document or other [container +block](#container-blocks). This means any HTML **within an HTML +block** that might otherwise be recognised as a start condition will +be ignored by the parser and passed through as-is, without changing +the parser's state. + +For instance, `
    ` within a HTML block started by `` will not affect
    +the parser state; as the HTML block was started in by start condition 6, it
    +will end at any blank line. This can be surprising:
    +
    +```````````````````````````````` example
    +
    +
    +**Hello**,
    +
    +_world_.
    +
    +
    +. +
    +
    +**Hello**,
    +

    world. +

    +
    +```````````````````````````````` + +In this case, the HTML block is terminated by the newline — the `**Hello**` +text remains verbatim — and regular parsing resumes, with a paragraph, +emphasised `world` and inline and block HTML following. + +All types of [HTML blocks] except type 7 may interrupt +a paragraph. Blocks of type 7 may not interrupt a paragraph. +(This restriction is intended to prevent unwanted interpretation +of long tags inside a wrapped paragraph as starting HTML blocks.) + +Some simple examples follow. Here are some basic HTML blocks +of type 6: + +```````````````````````````````` example + + + + +
    + hi +
    + +okay. +. + + + + +
    + hi +
    +

    okay.

    +```````````````````````````````` + + +```````````````````````````````` example +
    +*foo* +```````````````````````````````` + + +Here we have two HTML blocks with a Markdown paragraph between them: + +```````````````````````````````` example +
    + +*Markdown* + +
    +. +
    +

    Markdown

    +
    +```````````````````````````````` + + +The tag on the first line can be partial, as long +as it is split where there would be whitespace: + +```````````````````````````````` example +
    +
    +. +
    +
    +```````````````````````````````` + + +```````````````````````````````` example +
    +
    +. +
    +
    +```````````````````````````````` + + +An open tag need not be closed: +```````````````````````````````` example +
    +*foo* + +*bar* +. +
    +*foo* +

    bar

    +```````````````````````````````` + + + +A partial tag need not even be completed (garbage +in, garbage out): + +```````````````````````````````` example +
    +. + +```````````````````````````````` + + +```````````````````````````````` example +
    +foo +
    +. +
    +foo +
    +```````````````````````````````` + + +Everything until the next blank line or end of document +gets included in the HTML block. So, in the following +example, what looks like a Markdown code block +is actually part of the HTML block, which continues until a blank +line or the end of the document is reached: + +```````````````````````````````` example +
    +``` c +int x = 33; +``` +. +
    +``` c +int x = 33; +``` +```````````````````````````````` + + +To start an [HTML block] with a tag that is *not* in the +list of block-level tags in (6), you must put the tag by +itself on the first line (and it must be complete): + +```````````````````````````````` example + +*bar* + +. + +*bar* + +```````````````````````````````` + + +In type 7 blocks, the [tag name] can be anything: + +```````````````````````````````` example + +*bar* + +. + +*bar* + +```````````````````````````````` + + +```````````````````````````````` example + +*bar* + +. + +*bar* + +```````````````````````````````` + + +```````````````````````````````` example + +*bar* +. + +*bar* +```````````````````````````````` + + +These rules are designed to allow us to work with tags that +can function as either block-level or inline-level tags. +The `` tag is a nice example. We can surround content with +`` tags in three different ways. In this case, we get a raw +HTML block, because the `` tag is on a line by itself: + +```````````````````````````````` example + +*foo* + +. + +*foo* + +```````````````````````````````` + + +In this case, we get a raw HTML block that just includes +the `` tag (because it ends with the following blank +line). So the contents get interpreted as CommonMark: + +```````````````````````````````` example + + +*foo* + + +. + +

    foo

    +
    +```````````````````````````````` + + +Finally, in this case, the `` tags are interpreted +as [raw HTML] *inside* the CommonMark paragraph. (Because +the tag is not on a line by itself, we get inline HTML +rather than an [HTML block].) + +```````````````````````````````` example +*foo* +. +

    foo

    +```````````````````````````````` + + +HTML tags designed to contain literal content +(`script`, `style`, `pre`), comments, processing instructions, +and declarations are treated somewhat differently. +Instead of ending at the first blank line, these blocks +end at the first line containing a corresponding end tag. +As a result, these blocks can contain blank lines: + +A pre tag (type 1): + +```````````````````````````````` example +
    
    +import Text.HTML.TagSoup
    +
    +main :: IO ()
    +main = print $ parseTags tags
    +
    +okay +. +
    
    +import Text.HTML.TagSoup
    +
    +main :: IO ()
    +main = print $ parseTags tags
    +
    +

    okay

    +```````````````````````````````` + + +A script tag (type 1): + +```````````````````````````````` example + +okay +. + +

    okay

    +```````````````````````````````` + + +A style tag (type 1): + +```````````````````````````````` example + +okay +. + +

    okay

    +```````````````````````````````` + + +If there is no matching end tag, the block will end at the +end of the document (or the enclosing [block quote][block quotes] +or [list item][list items]): + +```````````````````````````````` example + +*foo* +. + +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +*bar* +*baz* +. +*bar* +

    baz

    +```````````````````````````````` + + +Note that anything on the last line after the +end tag will be included in the [HTML block]: + +```````````````````````````````` example +1. *bar* +. +1. *bar* +```````````````````````````````` + + +A comment (type 2): + +```````````````````````````````` example + +okay +. + +

    okay

    +```````````````````````````````` + + + +A processing instruction (type 3): + +```````````````````````````````` example +'; + +?> +okay +. +'; + +?> +

    okay

    +```````````````````````````````` + + +A declaration (type 4): + +```````````````````````````````` example + +. + +```````````````````````````````` + + +CDATA (type 5): + +```````````````````````````````` example + +okay +. + +

    okay

    +```````````````````````````````` + + +The opening tag can be indented 1-3 spaces, but not 4: + +```````````````````````````````` example + + + +. + +
    <!-- foo -->
    +
    +```````````````````````````````` + + +```````````````````````````````` example +
    + +
    +. +
    +
    <div>
    +
    +```````````````````````````````` + + +An HTML block of types 1--6 can interrupt a paragraph, and need not be +preceded by a blank line. + +```````````````````````````````` example +Foo +
    +bar +
    +. +

    Foo

    +
    +bar +
    +```````````````````````````````` + + +However, a following blank line is needed, except at the end of +a document, and except for blocks of types 1--5, [above][HTML +block]: + +```````````````````````````````` example +
    +bar +
    +*foo* +. +
    +bar +
    +*foo* +```````````````````````````````` + + +HTML blocks of type 7 cannot interrupt a paragraph: + +```````````````````````````````` example +Foo + +baz +. +

    Foo + +baz

    +```````````````````````````````` + + +This rule differs from John Gruber's original Markdown syntax +specification, which says: + +> The only restrictions are that block-level HTML elements — +> e.g. `
    `, ``, `
    `, `

    `, etc. — must be separated from +> surrounding content by blank lines, and the start and end tags of the +> block should not be indented with tabs or spaces. + +In some ways Gruber's rule is more restrictive than the one given +here: + +- It requires that an HTML block be preceded by a blank line. +- It does not allow the start tag to be indented. +- It requires a matching end tag, which it also does not allow to + be indented. + +Most Markdown implementations (including some of Gruber's own) do not +respect all of these restrictions. + +There is one respect, however, in which Gruber's rule is more liberal +than the one given here, since it allows blank lines to occur inside +an HTML block. There are two reasons for disallowing them here. +First, it removes the need to parse balanced tags, which is +expensive and can require backtracking from the end of the document +if no matching end tag is found. Second, it provides a very simple +and flexible way of including Markdown content inside HTML tags: +simply separate the Markdown from the HTML using blank lines: + +Compare: + +```````````````````````````````` example +

    + +*Emphasized* text. + +
    +. +
    +

    Emphasized text.

    +
    +```````````````````````````````` + + +```````````````````````````````` example +
    +*Emphasized* text. +
    +. +
    +*Emphasized* text. +
    +```````````````````````````````` + + +Some Markdown implementations have adopted a convention of +interpreting content inside tags as text if the open tag has +the attribute `markdown=1`. The rule given above seems a simpler and +more elegant way of achieving the same expressive power, which is also +much simpler to parse. + +The main potential drawback is that one can no longer paste HTML +blocks into Markdown documents with 100% reliability. However, +*in most cases* this will work fine, because the blank lines in +HTML are usually followed by HTML block tags. For example: + +```````````````````````````````` example +
    + + + + + + + +
    +Hi +
    +. + + + + +
    +Hi +
    +```````````````````````````````` + + +There are problems, however, if the inner tags are indented +*and* separated by spaces, as then they will be interpreted as +an indented code block: + +```````````````````````````````` example + + + + + + + + +
    + Hi +
    +. + + +
    <td>
    +  Hi
    +</td>
    +
    + +
    +```````````````````````````````` + + +Fortunately, blank lines are usually not necessary and can be +deleted. The exception is inside `
    ` tags, but as described
    +[above][HTML blocks], raw HTML blocks starting with `
    `
    +*can* contain blank lines.
    +
    +## Link reference definitions
    +
    +A [link reference definition](@)
    +consists of a [link label], indented up to three spaces, followed
    +by a colon (`:`), optional [whitespace] (including up to one
    +[line ending]), a [link destination],
    +optional [whitespace] (including up to one
    +[line ending]), and an optional [link
    +title], which if it is present must be separated
    +from the [link destination] by [whitespace].
    +No further [non-whitespace characters] may occur on the line.
    +
    +A [link reference definition]
    +does not correspond to a structural element of a document.  Instead, it
    +defines a label which can be used in [reference links]
    +and reference-style [images] elsewhere in the document.  [Link
    +reference definitions] can come either before or after the links that use
    +them.
    +
    +```````````````````````````````` example
    +[foo]: /url "title"
    +
    +[foo]
    +.
    +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example + [foo]: + /url + 'the title' + +[foo] +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +[Foo*bar\]]:my_(url) 'title (with parens)' + +[Foo*bar\]] +. +

    Foo*bar]

    +```````````````````````````````` + + +```````````````````````````````` example +[Foo bar]: + +'title' + +[Foo bar] +. +

    Foo bar

    +```````````````````````````````` + + +The title may extend over multiple lines: + +```````````````````````````````` example +[foo]: /url ' +title +line1 +line2 +' + +[foo] +. +

    foo

    +```````````````````````````````` + + +However, it may not contain a [blank line]: + +```````````````````````````````` example +[foo]: /url 'title + +with blank line' + +[foo] +. +

    [foo]: /url 'title

    +

    with blank line'

    +

    [foo]

    +```````````````````````````````` + + +The title may be omitted: + +```````````````````````````````` example +[foo]: +/url + +[foo] +. +

    foo

    +```````````````````````````````` + + +The link destination may not be omitted: + +```````````````````````````````` example +[foo]: + +[foo] +. +

    [foo]:

    +

    [foo]

    +```````````````````````````````` + + However, an empty link destination may be specified using + angle brackets: + +```````````````````````````````` example +[foo]: <> + +[foo] +. +

    foo

    +```````````````````````````````` + +The title must be separated from the link destination by +whitespace: + +```````````````````````````````` example +[foo]: (baz) + +[foo] +. +

    [foo]: (baz)

    +

    [foo]

    +```````````````````````````````` + + +Both title and destination can contain backslash escapes +and literal backslashes: + +```````````````````````````````` example +[foo]: /url\bar\*baz "foo\"bar\baz" + +[foo] +. +

    foo

    +```````````````````````````````` + + +A link can come before its corresponding definition: + +```````````````````````````````` example +[foo] + +[foo]: url +. +

    foo

    +```````````````````````````````` + + +If there are several matching definitions, the first one takes +precedence: + +```````````````````````````````` example +[foo] + +[foo]: first +[foo]: second +. +

    foo

    +```````````````````````````````` + + +As noted in the section on [Links], matching of labels is +case-insensitive (see [matches]). + +```````````````````````````````` example +[FOO]: /url + +[Foo] +. +

    Foo

    +```````````````````````````````` + + +```````````````````````````````` example +[ΑΓΩ]: /φου + +[αγω] +. +

    αγω

    +```````````````````````````````` + + +Here is a link reference definition with no corresponding link. +It contributes nothing to the document. + +```````````````````````````````` example +[foo]: /url +. +```````````````````````````````` + + +Here is another one: + +```````````````````````````````` example +[ +foo +]: /url +bar +. +

    bar

    +```````````````````````````````` + + +This is not a link reference definition, because there are +[non-whitespace characters] after the title: + +```````````````````````````````` example +[foo]: /url "title" ok +. +

    [foo]: /url "title" ok

    +```````````````````````````````` + + +This is a link reference definition, but it has no title: + +```````````````````````````````` example +[foo]: /url +"title" ok +. +

    "title" ok

    +```````````````````````````````` + + +This is not a link reference definition, because it is indented +four spaces: + +```````````````````````````````` example + [foo]: /url "title" + +[foo] +. +
    [foo]: /url "title"
    +
    +

    [foo]

    +```````````````````````````````` + + +This is not a link reference definition, because it occurs inside +a code block: + +```````````````````````````````` example +``` +[foo]: /url +``` + +[foo] +. +
    [foo]: /url
    +
    +

    [foo]

    +```````````````````````````````` + + +A [link reference definition] cannot interrupt a paragraph. + +```````````````````````````````` example +Foo +[bar]: /baz + +[bar] +. +

    Foo +[bar]: /baz

    +

    [bar]

    +```````````````````````````````` + + +However, it can directly follow other block elements, such as headings +and thematic breaks, and it need not be followed by a blank line. + +```````````````````````````````` example +# [Foo] +[foo]: /url +> bar +. +

    Foo

    +
    +

    bar

    +
    +```````````````````````````````` + +```````````````````````````````` example +[foo]: /url +bar +=== +[foo] +. +

    bar

    +

    foo

    +```````````````````````````````` + +```````````````````````````````` example +[foo]: /url +=== +[foo] +. +

    === +foo

    +```````````````````````````````` + + +Several [link reference definitions] +can occur one after another, without intervening blank lines. + +```````````````````````````````` example +[foo]: /foo-url "foo" +[bar]: /bar-url + "bar" +[baz]: /baz-url + +[foo], +[bar], +[baz] +. +

    foo, +bar, +baz

    +```````````````````````````````` + + +[Link reference definitions] can occur +inside block containers, like lists and block quotations. They +affect the entire document, not just the container in which they +are defined: + +```````````````````````````````` example +[foo] + +> [foo]: /url +. +

    foo

    +
    +
    +```````````````````````````````` + + +Whether something is a [link reference definition] is +independent of whether the link reference it defines is +used in the document. Thus, for example, the following +document contains just a link reference definition, and +no visible content: + +```````````````````````````````` example +[foo]: /url +. +```````````````````````````````` + + +## Paragraphs + +A sequence of non-blank lines that cannot be interpreted as other +kinds of blocks forms a [paragraph](@). +The contents of the paragraph are the result of parsing the +paragraph's raw content as inlines. The paragraph's raw content +is formed by concatenating the lines and removing initial and final +[whitespace]. + +A simple example with two paragraphs: + +```````````````````````````````` example +aaa + +bbb +. +

    aaa

    +

    bbb

    +```````````````````````````````` + + +Paragraphs can contain multiple lines, but no blank lines: + +```````````````````````````````` example +aaa +bbb + +ccc +ddd +. +

    aaa +bbb

    +

    ccc +ddd

    +```````````````````````````````` + + +Multiple blank lines between paragraph have no effect: + +```````````````````````````````` example +aaa + + +bbb +. +

    aaa

    +

    bbb

    +```````````````````````````````` + + +Leading spaces are skipped: + +```````````````````````````````` example + aaa + bbb +. +

    aaa +bbb

    +```````````````````````````````` + + +Lines after the first may be indented any amount, since indented +code blocks cannot interrupt paragraphs. + +```````````````````````````````` example +aaa + bbb + ccc +. +

    aaa +bbb +ccc

    +```````````````````````````````` + + +However, the first line may be indented at most three spaces, +or an indented code block will be triggered: + +```````````````````````````````` example + aaa +bbb +. +

    aaa +bbb

    +```````````````````````````````` + + +```````````````````````````````` example + aaa +bbb +. +
    aaa
    +
    +

    bbb

    +```````````````````````````````` + + +Final spaces are stripped before inline parsing, so a paragraph +that ends with two or more spaces will not end with a [hard line +break]: + +```````````````````````````````` example +aaa +bbb +. +

    aaa
    +bbb

    +```````````````````````````````` + + +## Blank lines + +[Blank lines] between block-level elements are ignored, +except for the role they play in determining whether a [list] +is [tight] or [loose]. + +Blank lines at the beginning and end of the document are also ignored. + +```````````````````````````````` example + + +aaa + + +# aaa + + +. +

    aaa

    +

    aaa

    +```````````````````````````````` + +
    + +## Tables (extension) + +GFM enables the `table` extension, where an additional leaf block type is +available. + +A [table](@) is an arrangement of data with rows and columns, consisting of a +single header row, a [delimiter row] separating the header from the data, and +zero or more data rows. + +Each row consists of cells containing arbitrary text, in which [inlines] are +parsed, separated by pipes (`|`). A leading and trailing pipe is also +recommended for clarity of reading, and if there's otherwise parsing ambiguity. +Spaces between pipes and cell content are trimmed. Block-level elements cannot +be inserted in a table. + +The [delimiter row](@) consists of cells whose only content are hyphens (`-`), +and optionally, a leading or trailing colon (`:`), or both, to indicate left, +right, or center alignment respectively. + +```````````````````````````````` example table +| foo | bar | +| --- | --- | +| baz | bim | +. + + + + + + + + + + + + + +
    foobar
    bazbim
    +```````````````````````````````` + +Cells in one column don't need to match length, though it's easier to read if +they are. Likewise, use of leading and trailing pipes may be inconsistent: + +```````````````````````````````` example table +| abc | defghi | +:-: | -----------: +bar | baz +. + + + + + + + + + + + + + +
    abcdefghi
    barbaz
    +```````````````````````````````` + +Include a pipe in a cell's content by escaping it, including inside other +inline spans: + +```````````````````````````````` example table +| f\|oo | +| ------ | +| b `\|` az | +| b **\|** im | +. + + + + + + + + + + + + + + +
    f|oo
    b | az
    b | im
    +```````````````````````````````` + +The table is broken at the first empty line, or beginning of another +block-level structure: + +```````````````````````````````` example table +| abc | def | +| --- | --- | +| bar | baz | +> bar +. + + + + + + + + + + + + + +
    abcdef
    barbaz
    +
    +

    bar

    +
    +```````````````````````````````` + +```````````````````````````````` example table +| abc | def | +| --- | --- | +| bar | baz | +bar + +bar +. + + + + + + + + + + + + + + + + + +
    abcdef
    barbaz
    bar
    +

    bar

    +```````````````````````````````` + +The header row must match the [delimiter row] in the number of cells. If not, +a table will not be recognized: + +```````````````````````````````` example table +| abc | def | +| --- | +| bar | +. +

    | abc | def | +| --- | +| bar |

    +```````````````````````````````` + +The remainder of the table's rows may vary in the number of cells. If there +are a number of cells fewer than the number of cells in the header row, empty +cells are inserted. If there are greater, the excess is ignored: + +```````````````````````````````` example table +| abc | def | +| --- | --- | +| bar | +| bar | baz | boo | +. + + + + + + + + + + + + + + + + + +
    abcdef
    bar
    barbaz
    +```````````````````````````````` + +If there are no rows in the body, no `` is generated in HTML output: + +```````````````````````````````` example table +| abc | def | +| --- | --- | +. + + + + + + + +
    abcdef
    +```````````````````````````````` + +
    + +# Container blocks + +A [container block](#container-blocks) is a block that has other +blocks as its contents. There are two basic kinds of container blocks: +[block quotes] and [list items]. +[Lists] are meta-containers for [list items]. + +We define the syntax for container blocks recursively. The general +form of the definition is: + +> If X is a sequence of blocks, then the result of +> transforming X in such-and-such a way is a container of type Y +> with these blocks as its content. + +So, we explain what counts as a block quote or list item by explaining +how these can be *generated* from their contents. This should suffice +to define the syntax, although it does not give a recipe for *parsing* +these constructions. (A recipe is provided below in the section entitled +[A parsing strategy](#appendix-a-parsing-strategy).) + +## Block quotes + +A [block quote marker](@) +consists of 0-3 spaces of initial indent, plus (a) the character `>` together +with a following space, or (b) a single character `>` not followed by a space. + +The following rules define [block quotes]: + +1. **Basic case.** If a string of lines *Ls* constitute a sequence + of blocks *Bs*, then the result of prepending a [block quote + marker] to the beginning of each line in *Ls* + is a [block quote](#block-quotes) containing *Bs*. + +2. **Laziness.** If a string of lines *Ls* constitute a [block + quote](#block-quotes) with contents *Bs*, then the result of deleting + the initial [block quote marker] from one or + more lines in which the next [non-whitespace character] after the [block + quote marker] is [paragraph continuation + text] is a block quote with *Bs* as its content. + [Paragraph continuation text](@) is text + that will be parsed as part of the content of a paragraph, but does + not occur at the beginning of the paragraph. + +3. **Consecutiveness.** A document cannot contain two [block + quotes] in a row unless there is a [blank line] between them. + +Nothing else counts as a [block quote](#block-quotes). + +Here is a simple example: + +```````````````````````````````` example +> # Foo +> bar +> baz +. +
    +

    Foo

    +

    bar +baz

    +
    +```````````````````````````````` + + +The spaces after the `>` characters can be omitted: + +```````````````````````````````` example +># Foo +>bar +> baz +. +
    +

    Foo

    +

    bar +baz

    +
    +```````````````````````````````` + + +The `>` characters can be indented 1-3 spaces: + +```````````````````````````````` example + > # Foo + > bar + > baz +. +
    +

    Foo

    +

    bar +baz

    +
    +```````````````````````````````` + + +Four spaces gives us a code block: + +```````````````````````````````` example + > # Foo + > bar + > baz +. +
    > # Foo
    +> bar
    +> baz
    +
    +```````````````````````````````` + + +The Laziness clause allows us to omit the `>` before +[paragraph continuation text]: + +```````````````````````````````` example +> # Foo +> bar +baz +. +
    +

    Foo

    +

    bar +baz

    +
    +```````````````````````````````` + + +A block quote can contain some lazy and some non-lazy +continuation lines: + +```````````````````````````````` example +> bar +baz +> foo +. +
    +

    bar +baz +foo

    +
    +```````````````````````````````` + + +Laziness only applies to lines that would have been continuations of +paragraphs had they been prepended with [block quote markers]. +For example, the `> ` cannot be omitted in the second line of + +``` markdown +> foo +> --- +``` + +without changing the meaning: + +```````````````````````````````` example +> foo +--- +. +
    +

    foo

    +
    +
    +```````````````````````````````` + + +Similarly, if we omit the `> ` in the second line of + +``` markdown +> - foo +> - bar +``` + +then the block quote ends after the first line: + +```````````````````````````````` example +> - foo +- bar +. +
    +
      +
    • foo
    • +
    +
    +
      +
    • bar
    • +
    +```````````````````````````````` + + +For the same reason, we can't omit the `> ` in front of +subsequent lines of an indented or fenced code block: + +```````````````````````````````` example +> foo + bar +. +
    +
    foo
    +
    +
    +
    bar
    +
    +```````````````````````````````` + + +```````````````````````````````` example +> ``` +foo +``` +. +
    +
    +
    +

    foo

    +
    +```````````````````````````````` + + +Note that in the following case, we have a [lazy +continuation line]: + +```````````````````````````````` example +> foo + - bar +. +
    +

    foo +- bar

    +
    +```````````````````````````````` + + +To see why, note that in + +```markdown +> foo +> - bar +``` + +the `- bar` is indented too far to start a list, and can't +be an indented code block because indented code blocks cannot +interrupt paragraphs, so it is [paragraph continuation text]. + +A block quote can be empty: + +```````````````````````````````` example +> +. +
    +
    +```````````````````````````````` + + +```````````````````````````````` example +> +> +> +. +
    +
    +```````````````````````````````` + + +A block quote can have initial or final blank lines: + +```````````````````````````````` example +> +> foo +> +. +
    +

    foo

    +
    +```````````````````````````````` + + +A blank line always separates block quotes: + +```````````````````````````````` example +> foo + +> bar +. +
    +

    foo

    +
    +
    +

    bar

    +
    +```````````````````````````````` + + +(Most current Markdown implementations, including John Gruber's +original `Markdown.pl`, will parse this example as a single block quote +with two paragraphs. But it seems better to allow the author to decide +whether two block quotes or one are wanted.) + +Consecutiveness means that if we put these block quotes together, +we get a single block quote: + +```````````````````````````````` example +> foo +> bar +. +
    +

    foo +bar

    +
    +```````````````````````````````` + + +To get a block quote with two paragraphs, use: + +```````````````````````````````` example +> foo +> +> bar +. +
    +

    foo

    +

    bar

    +
    +```````````````````````````````` + + +Block quotes can interrupt paragraphs: + +```````````````````````````````` example +foo +> bar +. +

    foo

    +
    +

    bar

    +
    +```````````````````````````````` + + +In general, blank lines are not needed before or after block +quotes: + +```````````````````````````````` example +> aaa +*** +> bbb +. +
    +

    aaa

    +
    +
    +
    +

    bbb

    +
    +```````````````````````````````` + + +However, because of laziness, a blank line is needed between +a block quote and a following paragraph: + +```````````````````````````````` example +> bar +baz +. +
    +

    bar +baz

    +
    +```````````````````````````````` + + +```````````````````````````````` example +> bar + +baz +. +
    +

    bar

    +
    +

    baz

    +```````````````````````````````` + + +```````````````````````````````` example +> bar +> +baz +. +
    +

    bar

    +
    +

    baz

    +```````````````````````````````` + + +It is a consequence of the Laziness rule that any number +of initial `>`s may be omitted on a continuation line of a +nested block quote: + +```````````````````````````````` example +> > > foo +bar +. +
    +
    +
    +

    foo +bar

    +
    +
    +
    +```````````````````````````````` + + +```````````````````````````````` example +>>> foo +> bar +>>baz +. +
    +
    +
    +

    foo +bar +baz

    +
    +
    +
    +```````````````````````````````` + + +When including an indented code block in a block quote, +remember that the [block quote marker] includes +both the `>` and a following space. So *five spaces* are needed after +the `>`: + +```````````````````````````````` example +> code + +> not code +. +
    +
    code
    +
    +
    +
    +

    not code

    +
    +```````````````````````````````` + + + +## List items + +A [list marker](@) is a +[bullet list marker] or an [ordered list marker]. + +A [bullet list marker](@) +is a `-`, `+`, or `*` character. + +An [ordered list marker](@) +is a sequence of 1--9 arabic digits (`0-9`), followed by either a +`.` character or a `)` character. (The reason for the length +limit is that with 10 digits we start seeing integer overflows +in some browsers.) + +The following rules define [list items]: + +1. **Basic case.** If a sequence of lines *Ls* constitute a sequence of + blocks *Bs* starting with a [non-whitespace character], and *M* is a + list marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces, 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 + 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 + number, based on the ordered list marker. + + Exceptions: + + 1. When the first list item in a [list] interrupts + a paragraph---that is, when it starts on a line that would + otherwise count as [paragraph continuation text]---then (a) + the lines *Ls* must not begin with a blank line, and (b) if + the list item is ordered, the start number must be 1. + 2. If any line is a [thematic break][thematic breaks] then + that line is not a list item. + +For example, let *Ls* be the lines + +```````````````````````````````` example +A paragraph +with two lines. + + indented code + +> A block quote. +. +

    A paragraph +with two lines.

    +
    indented code
    +
    +
    +

    A block quote.

    +
    +```````````````````````````````` + + +And let *M* be the marker `1.`, and *N* = 2. Then rule #1 says +that the following is an ordered list item with start number 1, +and the same contents as *Ls*: + +```````````````````````````````` example +1. A paragraph + with two lines. + + indented code + + > A block quote. +. +
      +
    1. +

      A paragraph +with two lines.

      +
      indented code
      +
      +
      +

      A block quote.

      +
      +
    2. +
    +```````````````````````````````` + + +The most important thing to notice is that the position of +the text after the list marker determines how much indentation +is needed in subsequent blocks in the list item. If the list +marker takes up two spaces, and there are three spaces between +the list marker and the next [non-whitespace character], then blocks +must be indented five spaces in order to fall under the list +item. + +Here are some examples showing how far content must be indented to be +put under the list item: + +```````````````````````````````` example +- one + + two +. +
      +
    • one
    • +
    +

    two

    +```````````````````````````````` + + +```````````````````````````````` example +- one + + two +. +
      +
    • +

      one

      +

      two

      +
    • +
    +```````````````````````````````` + + +```````````````````````````````` example + - one + + two +. +
      +
    • one
    • +
    +
     two
    +
    +```````````````````````````````` + + +```````````````````````````````` example + - one + + two +. +
      +
    • +

      one

      +

      two

      +
    • +
    +```````````````````````````````` + + +It is tempting to think of this in terms of columns: the continuation +blocks must be indented at least to the column of the first +[non-whitespace character] after the list marker. However, that is not quite right. +The spaces after the list marker determine how much relative indentation +is needed. Which column this indentation reaches will depend on +how the list item is embedded in other constructions, as shown by +this example: + +```````````````````````````````` example + > > 1. one +>> +>> two +. +
    +
    +
      +
    1. +

      one

      +

      two

      +
    2. +
    +
    +
    +```````````````````````````````` + + +Here `two` occurs in the same column as the list marker `1.`, +but is actually contained in the list item, because there is +sufficient indentation after the last containing blockquote marker. + +The converse is also possible. In the following example, the word `two` +occurs far to the right of the initial text of the list item, `one`, but +it is not considered part of the list item, because it is not indented +far enough past the blockquote marker: + +```````````````````````````````` example +>>- one +>> + > > two +. +
    +
    +
      +
    • one
    • +
    +

    two

    +
    +
    +```````````````````````````````` + + +Note that at least one space is needed between the list marker and +any following content, so these are not list items: + +```````````````````````````````` example +-one + +2.two +. +

    -one

    +

    2.two

    +```````````````````````````````` + + +A list item may contain blocks that are separated by more than +one blank line. + +```````````````````````````````` example +- foo + + + bar +. +
      +
    • +

      foo

      +

      bar

      +
    • +
    +```````````````````````````````` + + +A list item may contain any kind of block: + +```````````````````````````````` example +1. foo + + ``` + bar + ``` + + baz + + > bam +. +
      +
    1. +

      foo

      +
      bar
      +
      +

      baz

      +
      +

      bam

      +
      +
    2. +
    +```````````````````````````````` + + +A list item that contains an indented code block will preserve +empty lines within the code block verbatim. + +```````````````````````````````` example +- Foo + + bar + + + baz +. +
      +
    • +

      Foo

      +
      bar
      +
      +
      +baz
      +
      +
    • +
    +```````````````````````````````` + +Note that ordered list start numbers must be nine digits or less: + +```````````````````````````````` example +123456789. ok +. +
      +
    1. ok
    2. +
    +```````````````````````````````` + + +```````````````````````````````` example +1234567890. not ok +. +

    1234567890. not ok

    +```````````````````````````````` + + +A start number may begin with 0s: + +```````````````````````````````` example +0. ok +. +
      +
    1. ok
    2. +
    +```````````````````````````````` + + +```````````````````````````````` example +003. ok +. +
      +
    1. ok
    2. +
    +```````````````````````````````` + + +A start number may not be negative: + +```````````````````````````````` example +-1. not ok +. +

    -1. not ok

    +```````````````````````````````` + + + +2. **Item starting with indented code.** If a sequence of lines *Ls* + constitute a sequence of blocks *Bs* starting with an indented code + block, and *M* is a list marker of width *W* followed by + one space, then the result of prepending *M* and the following + space to the first line of *Ls*, and indenting subsequent lines of + *Ls* by *W + 1* spaces, is a list item with *Bs* as its contents. + If a line is empty, then it need not be indented. 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 number, based on the ordered list marker. + +An indented code block will have to be indented four spaces beyond +the edge of the region where text will be included in the list item. +In the following case that is 6 spaces: + +```````````````````````````````` example +- foo + + bar +. +
      +
    • +

      foo

      +
      bar
      +
      +
    • +
    +```````````````````````````````` + + +And in this case it is 11 spaces: + +```````````````````````````````` example + 10. foo + + bar +. +
      +
    1. +

      foo

      +
      bar
      +
      +
    2. +
    +```````````````````````````````` + + +If the *first* block in the list item is an indented code block, +then by rule #2, the contents must be indented *one* space after the +list marker: + +```````````````````````````````` example + indented code + +paragraph + + more code +. +
    indented code
    +
    +

    paragraph

    +
    more code
    +
    +```````````````````````````````` + + +```````````````````````````````` example +1. indented code + + paragraph + + more code +. +
      +
    1. +
      indented code
      +
      +

      paragraph

      +
      more code
      +
      +
    2. +
    +```````````````````````````````` + + +Note that an additional space indent is interpreted as space +inside the code block: + +```````````````````````````````` example +1. indented code + + paragraph + + more code +. +
      +
    1. +
       indented code
      +
      +

      paragraph

      +
      more code
      +
      +
    2. +
    +```````````````````````````````` + + +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 +[non-whitespace character], and (b) cases in which +they begin with an indented code +block. In a case like the following, where the first block begins with +a three-space indent, the rules do not allow us to form a list item by +indenting the whole thing and prepending a list marker: + +```````````````````````````````` example + foo + +bar +. +

    foo

    +

    bar

    +```````````````````````````````` + + +```````````````````````````````` example +- foo + + bar +. +
      +
    • foo
    • +
    +

    bar

    +```````````````````````````````` + + +This is not a significant restriction, because when a block begins +with 1-3 spaces indent, the indentation can always be removed without +a change in interpretation, allowing rule #1 to be applied. So, in +the above case: + +```````````````````````````````` example +- foo + + bar +. +
      +
    • +

      foo

      +

      bar

      +
    • +
    +```````````````````````````````` + + +3. **Item starting with a blank line.** If a sequence of lines *Ls* + starting with a single [blank line] constitute a (possibly empty) + sequence of blocks *Bs*, not separated from each other by more than + one blank line, and *M* is a list marker of width *W*, + then the result of prepending *M* to the first line of *Ls*, and + indenting subsequent lines of *Ls* by *W + 1* spaces, is a list + item with *Bs* as its contents. + If a line is empty, then it need not be indented. 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 number, based on the ordered list marker. + +Here are some list items that start with a blank line but are not empty: + +```````````````````````````````` example +- + foo +- + ``` + bar + ``` +- + baz +. +
      +
    • foo
    • +
    • +
      bar
      +
      +
    • +
    • +
      baz
      +
      +
    • +
    +```````````````````````````````` + +When the list item starts with a blank line, the number of spaces +following the list marker doesn't change the required indentation: + +```````````````````````````````` example +- + foo +. +
      +
    • foo
    • +
    +```````````````````````````````` + + +A list item can begin with at most one blank line. +In the following example, `foo` is not part of the list +item: + +```````````````````````````````` example +- + + foo +. +
      +
    • +
    +

    foo

    +```````````````````````````````` + + +Here is an empty bullet list item: + +```````````````````````````````` example +- foo +- +- bar +. +
      +
    • foo
    • +
    • +
    • bar
    • +
    +```````````````````````````````` + + +It does not matter whether there are spaces following the [list marker]: + +```````````````````````````````` example +- foo +- +- bar +. +
      +
    • foo
    • +
    • +
    • bar
    • +
    +```````````````````````````````` + + +Here is an empty ordered list item: + +```````````````````````````````` example +1. foo +2. +3. bar +. +
      +
    1. foo
    2. +
    3. +
    4. bar
    5. +
    +```````````````````````````````` + + +A list may start or end with an empty list item: + +```````````````````````````````` example +* +. +
      +
    • +
    +```````````````````````````````` + +However, an empty list item cannot interrupt a paragraph: + +```````````````````````````````` example +foo +* + +foo +1. +. +

    foo +*

    +

    foo +1.

    +```````````````````````````````` + + +4. **Indentation.** If a sequence of lines *Ls* constitutes a list item + according to rule #1, #2, or #3, then the result of indenting each line + of *Ls* by 1-3 spaces (the same for each line) also constitutes a + list item with the same contents and attributes. If a line is + empty, then it need not be indented. + +Indented one space: + +```````````````````````````````` example + 1. A paragraph + with two lines. + + indented code + + > A block quote. +. +
      +
    1. +

      A paragraph +with two lines.

      +
      indented code
      +
      +
      +

      A block quote.

      +
      +
    2. +
    +```````````````````````````````` + + +Indented two spaces: + +```````````````````````````````` example + 1. A paragraph + with two lines. + + indented code + + > A block quote. +. +
      +
    1. +

      A paragraph +with two lines.

      +
      indented code
      +
      +
      +

      A block quote.

      +
      +
    2. +
    +```````````````````````````````` + + +Indented three spaces: + +```````````````````````````````` example + 1. A paragraph + with two lines. + + indented code + + > A block quote. +. +
      +
    1. +

      A paragraph +with two lines.

      +
      indented code
      +
      +
      +

      A block quote.

      +
      +
    2. +
    +```````````````````````````````` + + +Four spaces indent gives a code block: + +```````````````````````````````` example + 1. A paragraph + with two lines. + + indented code + + > A block quote. +. +
    1.  A paragraph
    +    with two lines.
    +
    +        indented code
    +
    +    > A block quote.
    +
    +```````````````````````````````` + + + +5. **Laziness.** If a string of lines *Ls* constitute a [list + item](#list-items) with contents *Bs*, then the result of deleting + some or all of the indentation from one or more lines in which the + next [non-whitespace character] after the indentation is + [paragraph continuation text] is a + list item with the same contents and attributes. The unindented + lines are called + [lazy continuation line](@)s. + +Here is an example with [lazy continuation lines]: + +```````````````````````````````` example + 1. A paragraph +with two lines. + + indented code + + > A block quote. +. +
      +
    1. +

      A paragraph +with two lines.

      +
      indented code
      +
      +
      +

      A block quote.

      +
      +
    2. +
    +```````````````````````````````` + + +Indentation can be partially deleted: + +```````````````````````````````` example + 1. A paragraph + with two lines. +. +
      +
    1. A paragraph +with two lines.
    2. +
    +```````````````````````````````` + + +These examples show how laziness can work in nested structures: + +```````````````````````````````` example +> 1. > Blockquote +continued here. +. +
    +
      +
    1. +
      +

      Blockquote +continued here.

      +
      +
    2. +
    +
    +```````````````````````````````` + + +```````````````````````````````` example +> 1. > Blockquote +> continued here. +. +
    +
      +
    1. +
      +

      Blockquote +continued here.

      +
      +
    2. +
    +
    +```````````````````````````````` + + + +6. **That's all.** Nothing that is not counted as a list item by rules + #1--5 counts as a [list item](#list-items). + +The rules for sublists follow from the general rules +[above][List items]. A sublist must be indented the same number +of spaces a paragraph would need to be in order to be included +in the list item. + +So, in this case we need two spaces indent: + +```````````````````````````````` example +- foo + - bar + - baz + - boo +. +
      +
    • foo +
        +
      • bar +
          +
        • baz +
            +
          • boo
          • +
          +
        • +
        +
      • +
      +
    • +
    +```````````````````````````````` + + +One is not enough: + +```````````````````````````````` example +- foo + - bar + - baz + - boo +. +
      +
    • foo
    • +
    • bar
    • +
    • baz
    • +
    • boo
    • +
    +```````````````````````````````` + + +Here we need four, because the list marker is wider: + +```````````````````````````````` example +10) foo + - bar +. +
      +
    1. foo +
        +
      • bar
      • +
      +
    2. +
    +```````````````````````````````` + + +Three is not enough: + +```````````````````````````````` example +10) foo + - bar +. +
      +
    1. foo
    2. +
    +
      +
    • bar
    • +
    +```````````````````````````````` + + +A list may be the first block in a list item: + +```````````````````````````````` example +- - foo +. +
      +
    • +
        +
      • foo
      • +
      +
    • +
    +```````````````````````````````` + + +```````````````````````````````` example +1. - 2. foo +. +
      +
    1. +
        +
      • +
          +
        1. foo
        2. +
        +
      • +
      +
    2. +
    +```````````````````````````````` + + +A list item can contain a heading: + +```````````````````````````````` example +- # Foo +- Bar + --- + baz +. +
      +
    • +

      Foo

      +
    • +
    • +

      Bar

      +baz
    • +
    +```````````````````````````````` + + +### Motivation + +John Gruber's Markdown spec says the following about list items: + +1. "List markers typically start at the left margin, but may be indented + by up to three spaces. List markers must be followed by one or more + spaces or a tab." + +2. "To make lists look nice, you can wrap items with hanging indents.... + But if you don't want to, you don't have to." + +3. "List items may consist of multiple paragraphs. Each subsequent + paragraph in a list item must be indented by either 4 spaces or one + tab." + +4. "It looks nice if you indent every line of the subsequent paragraphs, + but here again, Markdown will allow you to be lazy." + +5. "To put a blockquote within a list item, the blockquote's `>` + delimiters need to be indented." + +6. "To put a code block within a list item, the code block needs to be + indented twice — 8 spaces or two tabs." + +These rules specify that a paragraph under a list item must be indented +four spaces (presumably, from the left margin, rather than the start of +the list marker, but this is not said), and that code under a list item +must be indented eight spaces instead of the usual four. They also say +that a block quote must be indented, but not by how much; however, the +example given has four spaces indentation. Although nothing is said +about other kinds of block-level content, it is certainly reasonable to +infer that *all* block elements under a list item, including other +lists, must be indented four spaces. This principle has been called the +*four-space rule*. + +The four-space rule is clear and principled, and if the reference +implementation `Markdown.pl` had followed it, it probably would have +become the standard. However, `Markdown.pl` allowed paragraphs and +sublists to start with only two spaces indentation, at least on the +outer level. Worse, its behavior was inconsistent: a sublist of an +outer-level list needed two spaces indentation, but a sublist of this +sublist needed three spaces. It is not surprising, then, that different +implementations of Markdown have developed very different rules for +determining what comes under a list item. (Pandoc and python-Markdown, +for example, stuck with Gruber's syntax description and the four-space +rule, while discount, redcarpet, marked, PHP Markdown, and others +followed `Markdown.pl`'s behavior more closely.) + +Unfortunately, given the divergences between implementations, there +is no way to give a spec for list items that will be guaranteed not +to break any existing documents. However, the spec given here should +correctly handle lists formatted with either the four-space rule or +the more forgiving `Markdown.pl` behavior, provided they are laid out +in a way that is natural for a human to read. + +The strategy here is to let the width and indentation of the list marker +determine the indentation necessary for blocks to fall under the list +item, rather than having a fixed and arbitrary number. The writer can +think of the body of the list item as a unit which gets indented to the +right enough to fit the list marker (and any indentation on the list +marker). (The laziness rule, #5, then allows continuation lines to be +unindented if needed.) + +This rule is superior, we claim, to any rule requiring a fixed level of +indentation from the margin. The four-space rule is clear but +unnatural. It is quite unintuitive that + +``` markdown +- foo + + bar + + - baz +``` + +should be parsed as two lists with an intervening paragraph, + +``` html +
      +
    • foo
    • +
    +

    bar

    +
      +
    • baz
    • +
    +``` + +as the four-space rule demands, rather than a single list, + +``` html +
      +
    • +

      foo

      +

      bar

      +
        +
      • baz
      • +
      +
    • +
    +``` + +The choice of four spaces is arbitrary. It can be learned, but it is +not likely to be guessed, and it trips up beginners regularly. + +Would it help to adopt a two-space rule? The problem is that such +a rule, together with the rule allowing 1--3 spaces indentation of the +initial list marker, allows text that is indented *less than* the +original list marker to be included in the list item. For example, +`Markdown.pl` parses + +``` markdown + - one + + two +``` + +as a single list item, with `two` a continuation paragraph: + +``` html +
      +
    • +

      one

      +

      two

      +
    • +
    +``` + +and similarly + +``` markdown +> - one +> +> two +``` + +as + +``` html +
    +
      +
    • +

      one

      +

      two

      +
    • +
    +
    +``` + +This is extremely unintuitive. + +Rather than requiring a fixed indent from the margin, we could require +a fixed indent (say, two spaces, or even one space) from the list marker (which +may itself be indented). This proposal would remove the last anomaly +discussed. Unlike the spec presented above, it would count the following +as a list item with a subparagraph, even though the paragraph `bar` +is not indented as far as the first paragraph `foo`: + +``` markdown + 10. foo + + bar +``` + +Arguably this text does read like a list item with `bar` as a subparagraph, +which may count in favor of the proposal. However, on this proposal indented +code would have to be indented six spaces after the list marker. And this +would break a lot of existing Markdown, which has the pattern: + +``` markdown +1. foo + + indented code +``` + +where the code is indented eight spaces. The spec above, by contrast, will +parse this text as expected, since the code block's indentation is measured +from the beginning of `foo`. + +The one case that needs special treatment is a list item that *starts* +with indented code. How much indentation is required in that case, since +we don't have a "first paragraph" to measure from? Rule #2 simply stipulates +that in such cases, we require one space indentation from the list marker +(and then the normal four spaces for the indented code). This will match the +four-space rule in cases where the list marker plus its initial indentation +takes four spaces (a common case), but diverge in other cases. + +
    + +## Task list items (extension) + +GFM enables the `tasklist` extension, where an additional processing step is +performed on [list items]. + +A [task list item](@) is a [list item][list items] where the first block in it +is a paragraph which begins with a [task list item marker] and at least one +whitespace character before any other content. + +A [task list item marker](@) consists of an optional number of spaces, a left +bracket (`[`), either a whitespace character or the letter `x` in either +lowercase or uppercase, and then a right bracket (`]`). + +When rendered, the [task list item marker] is replaced with a semantic checkbox element; +in an HTML output, this would be an `` element. + +If the character between the brackets is a whitespace character, the checkbox +is unchecked. Otherwise, the checkbox is checked. + +This spec does not define how the checkbox elements are interacted with: in practice, +implementors are free to render the checkboxes as disabled or inmutable elements, +or they may dynamically handle dynamic interactions (i.e. checking, unchecking) in +the final rendered document. + +```````````````````````````````` example disabled +- [ ] foo +- [x] bar +. +
      +
    • foo
    • +
    • bar
    • +
    +```````````````````````````````` + +Task lists can be arbitrarily nested: + +```````````````````````````````` example disabled +- [x] foo + - [ ] bar + - [x] baz +- [ ] bim +. +
      +
    • foo +
        +
      • bar
      • +
      • baz
      • +
      +
    • +
    • bim
    • +
    +```````````````````````````````` + +
    + +## Lists + +A [list](@) is a sequence of one or more +list items [of the same type]. The list items +may be separated by any number of blank lines. + +Two list items are [of the same type](@) +if they begin with a [list marker] of the same type. +Two list markers are of the +same type if (a) they are bullet list markers using the same character +(`-`, `+`, or `*`) or (b) they are ordered list numbers with the same +delimiter (either `.` or `)`). + +A list is an [ordered list](@) +if its constituent list items begin with +[ordered list markers], and a +[bullet list](@) if its constituent list +items begin with [bullet list markers]. + +The [start number](@) +of an [ordered list] is determined by the list number of +its initial list item. The numbers of subsequent list items are +disregarded. + +A list is [loose](@) if any of its constituent +list items are separated by blank lines, or if any of its constituent +list items directly contain two block-level elements with a blank line +between them. Otherwise a list is [tight](@). +(The difference in HTML output is that paragraphs in a loose list are +wrapped in `

    ` tags, while paragraphs in a tight list are not.) + +Changing the bullet or ordered list delimiter starts a new list: + +```````````````````````````````` example +- foo +- bar ++ baz +. +

      +
    • foo
    • +
    • bar
    • +
    +
      +
    • baz
    • +
    +```````````````````````````````` + + +```````````````````````````````` example +1. foo +2. bar +3) baz +. +
      +
    1. foo
    2. +
    3. bar
    4. +
    +
      +
    1. baz
    2. +
    +```````````````````````````````` + + +In CommonMark, a list can interrupt a paragraph. That is, +no blank line is needed to separate a paragraph from a following +list: + +```````````````````````````````` example +Foo +- bar +- baz +. +

    Foo

    +
      +
    • bar
    • +
    • baz
    • +
    +```````````````````````````````` + +`Markdown.pl` does not allow this, through fear of triggering a list +via a numeral in a hard-wrapped line: + +``` markdown +The number of windows in my house is +14. The number of doors is 6. +``` + +Oddly, though, `Markdown.pl` *does* allow a blockquote to +interrupt a paragraph, even though the same considerations might +apply. + +In CommonMark, we do allow lists to interrupt paragraphs, for +two reasons. First, it is natural and not uncommon for people +to start lists without blank lines: + +``` markdown +I need to buy +- new shoes +- a coat +- a plane ticket +``` + +Second, we are attracted to a + +> [principle of uniformity](@): +> if a chunk of text has a certain +> meaning, it will continue to have the same meaning when put into a +> container block (such as a list item or blockquote). + +(Indeed, the spec for [list items] and [block quotes] presupposes +this principle.) This principle implies that if + +``` markdown + * I need to buy + - new shoes + - a coat + - a plane ticket +``` + +is a list item containing a paragraph followed by a nested sublist, +as all Markdown implementations agree it is (though the paragraph +may be rendered without `

    ` tags, since the list is "tight"), +then + +``` markdown +I need to buy +- new shoes +- a coat +- a plane ticket +``` + +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) +takes a different approach, requiring blank lines before lists +even inside other list items.) + +In order to solve of unwanted lists in paragraphs with +hard-wrapped numerals, we allow only lists starting with `1` to +interrupt paragraphs. Thus, + +```````````````````````````````` example +The number of windows in my house is +14. The number of doors is 6. +. +

    The number of windows in my house is +14. The number of doors is 6.

    +```````````````````````````````` + +We may still get an unintended result in cases like + +```````````````````````````````` example +The number of windows in my house is +1. The number of doors is 6. +. +

    The number of windows in my house is

    +
      +
    1. The number of doors is 6.
    2. +
    +```````````````````````````````` + +but this rule should prevent most spurious list captures. + +There can be any number of blank lines between items: + +```````````````````````````````` example +- foo + +- bar + + +- baz +. +
      +
    • +

      foo

      +
    • +
    • +

      bar

      +
    • +
    • +

      baz

      +
    • +
    +```````````````````````````````` + +```````````````````````````````` example +- foo + - bar + - baz + + + bim +. +
      +
    • foo +
        +
      • bar +
          +
        • +

          baz

          +

          bim

          +
        • +
        +
      • +
      +
    • +
    +```````````````````````````````` + + +To separate consecutive lists of the same type, or to separate a +list from an indented code block that would otherwise be parsed +as a subparagraph of the final list item, you can insert a blank HTML +comment: + +```````````````````````````````` example +- foo +- bar + + + +- baz +- bim +. +
      +
    • foo
    • +
    • bar
    • +
    + +
      +
    • baz
    • +
    • bim
    • +
    +```````````````````````````````` + + +```````````````````````````````` example +- foo + + notcode + +- foo + + + + code +. +
      +
    • +

      foo

      +

      notcode

      +
    • +
    • +

      foo

      +
    • +
    + +
    code
    +
    +```````````````````````````````` + + +List items need not be indented to the same level. The following +list items will be treated as items at the same list level, +since none is indented enough to belong to the previous list +item: + +```````````````````````````````` example +- a + - b + - c + - d + - e + - f +- g +. +
      +
    • a
    • +
    • b
    • +
    • c
    • +
    • d
    • +
    • e
    • +
    • f
    • +
    • g
    • +
    +```````````````````````````````` + + +```````````````````````````````` example +1. a + + 2. b + + 3. c +. +
      +
    1. +

      a

      +
    2. +
    3. +

      b

      +
    4. +
    5. +

      c

      +
    6. +
    +```````````````````````````````` + +Note, however, that list items may not be indented more than +three spaces. Here `- e` is treated as a paragraph continuation +line, because it is indented more than three spaces: + +```````````````````````````````` example +- a + - b + - c + - d + - e +. +
      +
    • a
    • +
    • b
    • +
    • c
    • +
    • d +- e
    • +
    +```````````````````````````````` + +And here, `3. c` is treated as in indented code block, +because it is indented four spaces and preceded by a +blank line. + +```````````````````````````````` example +1. a + + 2. b + + 3. c +. +
      +
    1. +

      a

      +
    2. +
    3. +

      b

      +
    4. +
    +
    3. c
    +
    +```````````````````````````````` + + +This is a loose list, because there is a blank line between +two of the list items: + +```````````````````````````````` example +- a +- b + +- c +. +
      +
    • +

      a

      +
    • +
    • +

      b

      +
    • +
    • +

      c

      +
    • +
    +```````````````````````````````` + + +So is this, with a empty second item: + +```````````````````````````````` example +* a +* + +* c +. +
      +
    • +

      a

      +
    • +
    • +
    • +

      c

      +
    • +
    +```````````````````````````````` + + +These are loose lists, even though there is no space between the items, +because one of the items directly contains two block-level elements +with a blank line between them: + +```````````````````````````````` example +- a +- b + + c +- d +. +
      +
    • +

      a

      +
    • +
    • +

      b

      +

      c

      +
    • +
    • +

      d

      +
    • +
    +```````````````````````````````` + + +```````````````````````````````` example +- a +- b + + [ref]: /url +- d +. +
      +
    • +

      a

      +
    • +
    • +

      b

      +
    • +
    • +

      d

      +
    • +
    +```````````````````````````````` + + +This is a tight list, because the blank lines are in a code block: + +```````````````````````````````` example +- a +- ``` + b + + + ``` +- c +. +
      +
    • a
    • +
    • +
      b
      +
      +
      +
      +
    • +
    • c
    • +
    +```````````````````````````````` + + +This is a tight list, because the blank line is between two +paragraphs of a sublist. So the sublist is loose while +the outer list is tight: + +```````````````````````````````` example +- a + - b + + c +- d +. +
      +
    • a +
        +
      • +

        b

        +

        c

        +
      • +
      +
    • +
    • d
    • +
    +```````````````````````````````` + + +This is a tight list, because the blank line is inside the +block quote: + +```````````````````````````````` example +* a + > b + > +* c +. +
      +
    • a +
      +

      b

      +
      +
    • +
    • c
    • +
    +```````````````````````````````` + + +This list is tight, because the consecutive block elements +are not separated by blank lines: + +```````````````````````````````` example +- a + > b + ``` + c + ``` +- d +. +
      +
    • a +
      +

      b

      +
      +
      c
      +
      +
    • +
    • d
    • +
    +```````````````````````````````` + + +A single-paragraph list is tight: + +```````````````````````````````` example +- a +. +
      +
    • a
    • +
    +```````````````````````````````` + + +```````````````````````````````` example +- a + - b +. +
      +
    • a +
        +
      • b
      • +
      +
    • +
    +```````````````````````````````` + + +This list is loose, because of the blank line between the +two block elements in the list item: + +```````````````````````````````` example +1. ``` + foo + ``` + + bar +. +
      +
    1. +
      foo
      +
      +

      bar

      +
    2. +
    +```````````````````````````````` + + +Here the outer list is loose, the inner list tight: + +```````````````````````````````` example +* foo + * bar + + baz +. +
      +
    • +

      foo

      +
        +
      • bar
      • +
      +

      baz

      +
    • +
    +```````````````````````````````` + + +```````````````````````````````` example +- a + - b + - c + +- d + - e + - f +. +
      +
    • +

      a

      +
        +
      • b
      • +
      • c
      • +
      +
    • +
    • +

      d

      +
        +
      • e
      • +
      • f
      • +
      +
    • +
    +```````````````````````````````` + + +# Inlines + +Inlines are parsed sequentially from the beginning of the character +stream to the end (left to right, in left-to-right languages). +Thus, for example, in + +```````````````````````````````` example +`hi`lo` +. +

    hilo`

    +```````````````````````````````` + +`hi` is parsed as code, leaving the backtick at the end as a literal +backtick. + + +## Backslash escapes + +Any ASCII punctuation character may be backslash-escaped: + +```````````````````````````````` example +\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~ +. +

    !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    +```````````````````````````````` + + +Backslashes before other characters are treated as literal +backslashes: + +```````````````````````````````` example +\→\A\a\ \3\φ\« +. +

    \→\A\a\ \3\φ\«

    +```````````````````````````````` + + +Escaped characters are treated as regular characters and do +not have their usual Markdown meanings: + +```````````````````````````````` example +\*not emphasized* +\
    not a tag +\[not a link](/foo) +\`not code` +1\. not a list +\* not a list +\# not a heading +\[foo]: /url "not a reference" +\ö not a character entity +. +

    *not emphasized* +<br/> not a tag +[not a link](/foo) +`not code` +1. not a list +* not a list +# not a heading +[foo]: /url "not a reference" +&ouml; not a character entity

    +```````````````````````````````` + + +If a backslash is itself escaped, the following character is not: + +```````````````````````````````` example +\\*emphasis* +. +

    \emphasis

    +```````````````````````````````` + + +A backslash at the end of the line is a [hard line break]: + +```````````````````````````````` example +foo\ +bar +. +

    foo
    +bar

    +```````````````````````````````` + + +Backslash escapes do not work in code blocks, code spans, autolinks, or +raw HTML: + +```````````````````````````````` example +`` \[\` `` +. +

    \[\`

    +```````````````````````````````` + + +```````````````````````````````` example + \[\] +. +
    \[\]
    +
    +```````````````````````````````` + + +```````````````````````````````` example +~~~ +\[\] +~~~ +. +
    \[\]
    +
    +```````````````````````````````` + + +```````````````````````````````` example + +. +

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

    +```````````````````````````````` + + +```````````````````````````````` example + +. + +```````````````````````````````` + + +But they work in all other contexts, including URLs and link titles, +link references, and [info strings] in [fenced code blocks]: + +```````````````````````````````` example +[foo](/bar\* "ti\*tle") +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +[foo] + +[foo]: /bar\* "ti\*tle" +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +``` foo\+bar +foo +``` +. +
    foo
    +
    +```````````````````````````````` + + + +## Entity and numeric character references + +Valid HTML entity references and numeric character references +can be used in place of the corresponding Unicode character, +with the following exceptions: + +- Entity and character references are not recognized in code + blocks and code spans. + +- Entity and character references cannot stand in place of + special characters that define structural elements in + CommonMark. For example, although `*` can be used + in place of a literal `*` character, `*` cannot replace + `*` in emphasis delimiters, bullet list markers, or thematic + breaks. + +Conforming CommonMark parsers need not store information about +whether a particular character was represented in the source +using a Unicode character or an entity reference. + +[Entity references](@) consist of `&` + any of the valid +HTML5 entity names + `;`. The +document +is used as an authoritative source for the valid entity +references and their corresponding code points. + +```````````````````````````````` example +  & © Æ Ď +¾ ℋ ⅆ +∲ ≧̸ +. +

      & © Æ Ď +¾ ℋ ⅆ +∲ ≧̸

    +```````````````````````````````` + + +[Decimal numeric character +references](@) +consist of `&#` + a string of 1--7 arabic digits + `;`. A +numeric character reference is parsed as the corresponding +Unicode character. Invalid Unicode code points will be replaced by +the REPLACEMENT CHARACTER (`U+FFFD`). For security reasons, +the code point `U+0000` will also be replaced by `U+FFFD`. + +```````````````````````````````` example +# Ӓ Ϡ � +. +

    # Ӓ Ϡ �

    +```````````````````````````````` + + +[Hexadecimal numeric character +references](@) consist of `&#` + +either `X` or `x` + a string of 1-6 hexadecimal digits + `;`. +They too are parsed as the corresponding Unicode character (this +time specified with a hexadecimal numeral instead of decimal). + +```````````````````````````````` example +" ആ ಫ +. +

    " ആ ಫ

    +```````````````````````````````` + + +Here are some nonentities: + +```````````````````````````````` example +  &x; &#; &#x; +� +&#abcdef0; +&ThisIsNotDefined; &hi?; +. +

    &nbsp &x; &#; &#x; +&#987654321; +&#abcdef0; +&ThisIsNotDefined; &hi?;

    +```````````````````````````````` + + +Although HTML5 does accept some entity references +without a trailing semicolon (such as `©`), these are not +recognized here, because it makes the grammar too ambiguous: + +```````````````````````````````` example +© +. +

    &copy

    +```````````````````````````````` + + +Strings that are not on the list of HTML5 named entities are not +recognized as entity references either: + +```````````````````````````````` example +&MadeUpEntity; +. +

    &MadeUpEntity;

    +```````````````````````````````` + + +Entity and numeric character references are recognized in any +context besides code spans or code blocks, including +URLs, [link titles], and [fenced code block][] [info strings]: + +```````````````````````````````` example + +. + +```````````````````````````````` + + +```````````````````````````````` example +[foo](/föö "föö") +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +[foo] + +[foo]: /föö "föö" +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +``` föö +foo +``` +. +
    foo
    +
    +```````````````````````````````` + + +Entity and numeric character references are treated as literal +text in code spans and code blocks: + +```````````````````````````````` example +`föö` +. +

    f&ouml;&ouml;

    +```````````````````````````````` + + +```````````````````````````````` example + föfö +. +
    f&ouml;f&ouml;
    +
    +```````````````````````````````` + + +Entity and numeric character references cannot be used +in place of symbols indicating structure in CommonMark +documents. + +```````````````````````````````` example +*foo* +*foo* +. +

    *foo* +foo

    +```````````````````````````````` + +```````````````````````````````` example +* foo + +* foo +. +

    * foo

    +
      +
    • foo
    • +
    +```````````````````````````````` + +```````````````````````````````` example +foo bar +. +

    foo + +bar

    +```````````````````````````````` + +```````````````````````````````` example + foo +. +

    →foo

    +```````````````````````````````` + + +```````````````````````````````` example +[a](url "tit") +. +

    [a](url "tit")

    +```````````````````````````````` + + +## Code spans + +A [backtick string](@) +is a string of one or more backtick characters (`` ` ``) that is neither +preceded nor followed by a backtick. + +A [code span](@) begins with a backtick string and ends with +a backtick string of equal length. The contents of the code span are +the characters between the two backtick strings, normalized in the +following ways: + +- First, [line endings] are converted to [spaces]. +- If the resulting string both begins *and* ends with a [space] + character, but does not consist entirely of [space] + characters, a single [space] character is removed from the + front and back. This allows you to include code that begins + or ends with backtick characters, which must be separated by + whitespace from the opening or closing backtick strings. + +This is a simple code span: + +```````````````````````````````` example +`foo` +. +

    foo

    +```````````````````````````````` + + +Here two backticks are used, because the code contains a backtick. +This example also illustrates stripping of a single leading and +trailing space: + +```````````````````````````````` example +`` foo ` bar `` +. +

    foo ` bar

    +```````````````````````````````` + + +This example shows the motivation for stripping leading and trailing +spaces: + +```````````````````````````````` example +` `` ` +. +

    ``

    +```````````````````````````````` + +Note that only *one* space is stripped: + +```````````````````````````````` example +` `` ` +. +

    ``

    +```````````````````````````````` + +The stripping only happens if the space is on both +sides of the string: + +```````````````````````````````` example +` a` +. +

    a

    +```````````````````````````````` + +Only [spaces], and not [unicode whitespace] in general, are +stripped in this way: + +```````````````````````````````` example +` b ` +. +

     b 

    +```````````````````````````````` + +No stripping occurs if the code span contains only spaces: + +```````````````````````````````` example +` ` +` ` +. +

      +

    +```````````````````````````````` + + +[Line endings] are treated like spaces: + +```````````````````````````````` example +`` +foo +bar +baz +`` +. +

    foo bar baz

    +```````````````````````````````` + +```````````````````````````````` example +`` +foo +`` +. +

    foo

    +```````````````````````````````` + + +Interior spaces are not collapsed: + +```````````````````````````````` example +`foo bar +baz` +. +

    foo bar baz

    +```````````````````````````````` + +Note that browsers will typically collapse consecutive spaces +when rendering `` elements, so it is recommended that +the following CSS be used: + + code{white-space: pre-wrap;} + + +Note that backslash escapes do not work in code spans. All backslashes +are treated literally: + +```````````````````````````````` example +`foo\`bar` +. +

    foo\bar`

    +```````````````````````````````` + + +Backslash escapes are never needed, because one can always choose a +string of *n* backtick characters as delimiters, where the code does +not contain any strings of exactly *n* backtick characters. + +```````````````````````````````` example +``foo`bar`` +. +

    foo`bar

    +```````````````````````````````` + +```````````````````````````````` example +` foo `` bar ` +. +

    foo `` bar

    +```````````````````````````````` + + +Code span backticks have higher precedence than any other inline +constructs except HTML tags and autolinks. Thus, for example, this is +not parsed as emphasized text, since the second `*` is part of a code +span: + +```````````````````````````````` example +*foo`*` +. +

    *foo*

    +```````````````````````````````` + + +And this is not parsed as a link: + +```````````````````````````````` example +[not a `link](/foo`) +. +

    [not a link](/foo)

    +```````````````````````````````` + + +Code spans, HTML tags, and autolinks have the same precedence. +Thus, this is code: + +```````````````````````````````` example +`` +. +

    <a href="">`

    +```````````````````````````````` + + +But this is an HTML tag: + +```````````````````````````````` example +
    ` +. +

    `

    +```````````````````````````````` + + +And this is code: + +```````````````````````````````` example +`` +. +

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

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

    http://foo.bar.`baz`

    +```````````````````````````````` + + +When a backtick string is not closed by a matching backtick string, +we just have literal backticks: + +```````````````````````````````` example +```foo`` +. +

    ```foo``

    +```````````````````````````````` + + +```````````````````````````````` example +`foo +. +

    `foo

    +```````````````````````````````` + +The following case also illustrates the need for opening and +closing backtick strings to be equal in length: + +```````````````````````````````` example +`foo``bar`` +. +

    `foobar

    +```````````````````````````````` + + +## Emphasis and strong emphasis + +John Gruber's original [Markdown syntax +description](http://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 +> `` tag; double `*`'s or `_`'s will be wrapped with an HTML `` +> tag. + +This is enough for most users, but these rules leave much undecided, +especially when it comes to nested emphasis. The original +`Markdown.pl` test suite makes it clear that triple `***` and +`___` delimiters can be used for strong emphasis, and most +implementations have also allowed the following patterns: + +``` markdown +***strong emph*** +***strong** in emph* +***emph* in strong** +**in strong *emph*** +*in emph **strong*** +``` + +The following patterns are less widely supported, but the intent +is clear and they are useful (especially in contexts like bibliography +entries): + +``` markdown +*emph *with emph* in it* +**strong **with strong** in it** +``` + +Many implementations have also restricted intraword emphasis to +the `*` forms, to avoid unwanted emphasis in words containing +internal underscores. (It is best practice to put these in code +spans, but users often do not.) + +``` markdown +internal emphasis: foo*bar*baz +no emphasis: foo_bar_baz +``` + +The rules given below capture all of these patterns, while allowing +for efficient parsing strategies that do not backtrack. + +First, some definitions. A [delimiter run](@) is either +a sequence of one or more `*` characters that is not preceded or +followed by a non-backslash-escaped `*` character, or a sequence +of one or more `_` characters that is not preceded or followed by +a non-backslash-escaped `_` character. + +A [left-flanking delimiter run](@) is +a [delimiter run] that is (1) not followed by [Unicode whitespace], +and either (2a) not followed by a [punctuation character], or +(2b) followed by a [punctuation character] and +preceded by [Unicode whitespace] or a [punctuation character]. +For purposes of this definition, the beginning and the end of +the line count as Unicode whitespace. + +A [right-flanking delimiter run](@) is +a [delimiter run] that is (1) not preceded by [Unicode whitespace], +and either (2a) not preceded by a [punctuation character], or +(2b) preceded by a [punctuation character] and +followed by [Unicode whitespace] or a [punctuation character]. +For purposes of this definition, the beginning and the end of +the line count as Unicode whitespace. + +Here are some examples of delimiter runs. + + - left-flanking but not right-flanking: + + ``` + ***abc + _abc + **"abc" + _"abc" + ``` + + - right-flanking but not left-flanking: + + ``` + abc*** + abc_ + "abc"** + "abc"_ + ``` + + - Both left and right-flanking: + + ``` + abc***def + "abc"_"def" + ``` + + - Neither left nor right-flanking: + + ``` + abc *** def + a _ b + ``` + +(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 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.) + +The following rules define emphasis and strong emphasis: + +1. A single `*` character [can open emphasis](@) + iff (if and only if) it is part of a [left-flanking delimiter run]. + +2. A single `_` character [can open emphasis] iff + it is part of a [left-flanking delimiter run] + and either (a) not part of a [right-flanking delimiter run] + or (b) part of a [right-flanking delimiter run] + preceded by punctuation. + +3. A single `*` character [can close emphasis](@) + iff it is part of a [right-flanking delimiter run]. + +4. A single `_` character [can close emphasis] iff + it is part of a [right-flanking delimiter run] + and either (a) not part of a [left-flanking delimiter run] + or (b) part of a [left-flanking delimiter run] + followed by punctuation. + +5. A double `**` [can open strong emphasis](@) + iff it is part of a [left-flanking delimiter run]. + +6. A double `__` [can open strong emphasis] iff + it is part of a [left-flanking delimiter run] + and either (a) not part of a [right-flanking delimiter run] + or (b) part of a [right-flanking delimiter run] + preceded by punctuation. + +7. A double `**` [can close strong emphasis](@) + iff it is part of a [right-flanking delimiter run]. + +8. A double `__` [can close strong emphasis] iff + it is part of a [right-flanking delimiter run] + and either (a) not part of a [left-flanking delimiter run] + or (b) part of a [left-flanking delimiter run] + followed by punctuation. + +9. Emphasis begins with a delimiter that [can open emphasis] and ends + with a delimiter that [can close emphasis], and that uses the same + character (`_` or `*`) as the opening delimiter. The + opening and closing delimiters must belong to separate + [delimiter runs]. If one of the delimiters can both + open and close emphasis, then the sum of the lengths of the + delimiter runs containing the opening and closing delimiters + must not be a multiple of 3 unless both lengths are + multiples of 3. + +10. Strong emphasis begins with a delimiter that + [can open strong emphasis] and ends with a delimiter that + [can close strong emphasis], and that uses the same character + (`_` or `*`) as the opening delimiter. The + opening and closing delimiters must belong to separate + [delimiter runs]. If one of the delimiters can both open + and close strong emphasis, then the sum of the lengths of + the delimiter runs containing the opening and closing + delimiters must not be a multiple of 3 unless both lengths + are multiples of 3. + +11. A literal `*` character cannot occur at the beginning or end of + `*`-delimited emphasis or `**`-delimited strong emphasis, unless it + is backslash-escaped. + +12. A literal `_` character cannot occur at the beginning or end of + `_`-delimited emphasis or `__`-delimited strong emphasis, unless it + is backslash-escaped. + +Where rules 1--12 above are compatible with multiple parsings, +the following principles resolve ambiguity: + +13. The number of nestings should be minimized. Thus, for example, + an interpretation `...` is always preferred to + `...`. + +14. An interpretation `...` is always + preferred to `...`. + +15. When two potential emphasis or strong emphasis spans overlap, + so that the second begins before the first ends and ends after + the first ends, the first takes precedence. Thus, for example, + `*foo _bar* baz_` is parsed as `foo _bar baz_` rather + than `*foo bar* baz`. + +16. When there are two potential emphasis or strong emphasis spans + with the same closing delimiter, the shorter one (the one that + opens later) takes precedence. Thus, for example, + `**foo **bar baz**` is parsed as `**foo bar baz` + rather than `foo **bar baz`. + +17. Inline code spans, links, images, and HTML tags group more tightly + than emphasis. So, when there is a choice between an interpretation + that contains one of these elements and one that does not, the + former always wins. Thus, for example, `*[foo*](bar)` is + parsed as `*foo*` rather than as + `[foo](bar)`. + +These rules can be illustrated through a series of examples. + +Rule 1: + +```````````````````````````````` example +*foo bar* +. +

    foo bar

    +```````````````````````````````` + + +This is not emphasis, because the opening `*` is followed by +whitespace, and hence not part of a [left-flanking delimiter run]: + +```````````````````````````````` example +a * foo bar* +. +

    a * foo bar*

    +```````````````````````````````` + + +This is not emphasis, because the opening `*` is preceded +by an alphanumeric and followed by punctuation, and hence +not part of a [left-flanking delimiter run]: + +```````````````````````````````` example +a*"foo"* +. +

    a*"foo"*

    +```````````````````````````````` + + +Unicode nonbreaking spaces count as whitespace, too: + +```````````````````````````````` example +* a * +. +

    * a *

    +```````````````````````````````` + + +Intraword emphasis with `*` is permitted: + +```````````````````````````````` example +foo*bar* +. +

    foobar

    +```````````````````````````````` + + +```````````````````````````````` example +5*6*78 +. +

    5678

    +```````````````````````````````` + + +Rule 2: + +```````````````````````````````` example +_foo bar_ +. +

    foo bar

    +```````````````````````````````` + + +This is not emphasis, because the opening `_` is followed by +whitespace: + +```````````````````````````````` example +_ foo bar_ +. +

    _ foo bar_

    +```````````````````````````````` + + +This is not emphasis, because the opening `_` is preceded +by an alphanumeric and followed by punctuation: + +```````````````````````````````` example +a_"foo"_ +. +

    a_"foo"_

    +```````````````````````````````` + + +Emphasis with `_` is not allowed inside words: + +```````````````````````````````` example +foo_bar_ +. +

    foo_bar_

    +```````````````````````````````` + + +```````````````````````````````` example +5_6_78 +. +

    5_6_78

    +```````````````````````````````` + + +```````````````````````````````` example +пристаням_стремятся_ +. +

    пристаням_стремятся_

    +```````````````````````````````` + + +Here `_` does not generate emphasis, because the first delimiter run +is right-flanking and the second left-flanking: + +```````````````````````````````` example +aa_"bb"_cc +. +

    aa_"bb"_cc

    +```````````````````````````````` + + +This is emphasis, even though the opening delimiter is +both left- and right-flanking, because it is preceded by +punctuation: + +```````````````````````````````` example +foo-_(bar)_ +. +

    foo-(bar)

    +```````````````````````````````` + + +Rule 3: + +This is not emphasis, because the closing delimiter does +not match the opening delimiter: + +```````````````````````````````` example +_foo* +. +

    _foo*

    +```````````````````````````````` + + +This is not emphasis, because the closing `*` is preceded by +whitespace: + +```````````````````````````````` example +*foo bar * +. +

    *foo bar *

    +```````````````````````````````` + + +A newline also counts as whitespace: + +```````````````````````````````` example +*foo bar +* +. +

    *foo bar +*

    +```````````````````````````````` + + +This is not emphasis, because the second `*` is +preceded by punctuation and followed by an alphanumeric +(hence it is not part of a [right-flanking delimiter run]: + +```````````````````````````````` example +*(*foo) +. +

    *(*foo)

    +```````````````````````````````` + + +The point of this restriction is more easily appreciated +with this example: + +```````````````````````````````` example +*(*foo*)* +. +

    (foo)

    +```````````````````````````````` + + +Intraword emphasis with `*` is allowed: + +```````````````````````````````` example +*foo*bar +. +

    foobar

    +```````````````````````````````` + + + +Rule 4: + +This is not emphasis, because the closing `_` is preceded by +whitespace: + +```````````````````````````````` example +_foo bar _ +. +

    _foo bar _

    +```````````````````````````````` + + +This is not emphasis, because the second `_` is +preceded by punctuation and followed by an alphanumeric: + +```````````````````````````````` example +_(_foo) +. +

    _(_foo)

    +```````````````````````````````` + + +This is emphasis within emphasis: + +```````````````````````````````` example +_(_foo_)_ +. +

    (foo)

    +```````````````````````````````` + + +Intraword emphasis is disallowed for `_`: + +```````````````````````````````` example +_foo_bar +. +

    _foo_bar

    +```````````````````````````````` + + +```````````````````````````````` example +_пристаням_стремятся +. +

    _пристаням_стремятся

    +```````````````````````````````` + + +```````````````````````````````` example +_foo_bar_baz_ +. +

    foo_bar_baz

    +```````````````````````````````` + + +This is emphasis, even though the closing delimiter is +both left- and right-flanking, because it is followed by +punctuation: + +```````````````````````````````` example +_(bar)_. +. +

    (bar).

    +```````````````````````````````` + + +Rule 5: + +```````````````````````````````` example +**foo bar** +. +

    foo bar

    +```````````````````````````````` + + +This is not strong emphasis, because the opening delimiter is +followed by whitespace: + +```````````````````````````````` example +** foo bar** +. +

    ** foo bar**

    +```````````````````````````````` + + +This is not strong emphasis, because the opening `**` is preceded +by an alphanumeric and followed by punctuation, and hence +not part of a [left-flanking delimiter run]: + +```````````````````````````````` example +a**"foo"** +. +

    a**"foo"**

    +```````````````````````````````` + + +Intraword strong emphasis with `**` is permitted: + +```````````````````````````````` example +foo**bar** +. +

    foobar

    +```````````````````````````````` + + +Rule 6: + +```````````````````````````````` example +__foo bar__ +. +

    foo bar

    +```````````````````````````````` + + +This is not strong emphasis, because the opening delimiter is +followed by whitespace: + +```````````````````````````````` example +__ foo bar__ +. +

    __ foo bar__

    +```````````````````````````````` + + +A newline counts as whitespace: +```````````````````````````````` example +__ +foo bar__ +. +

    __ +foo bar__

    +```````````````````````````````` + + +This is not strong emphasis, because the opening `__` is preceded +by an alphanumeric and followed by punctuation: + +```````````````````````````````` example +a__"foo"__ +. +

    a__"foo"__

    +```````````````````````````````` + + +Intraword strong emphasis is forbidden with `__`: + +```````````````````````````````` example +foo__bar__ +. +

    foo__bar__

    +```````````````````````````````` + + +```````````````````````````````` example +5__6__78 +. +

    5__6__78

    +```````````````````````````````` + + +```````````````````````````````` example +пристаням__стремятся__ +. +

    пристаням__стремятся__

    +```````````````````````````````` + + +```````````````````````````````` example +__foo, __bar__, baz__ +. +

    foo, bar, baz

    +```````````````````````````````` + + +This is strong emphasis, even though the opening delimiter is +both left- and right-flanking, because it is preceded by +punctuation: + +```````````````````````````````` example +foo-__(bar)__ +. +

    foo-(bar)

    +```````````````````````````````` + + + +Rule 7: + +This is not strong emphasis, because the closing delimiter is preceded +by whitespace: + +```````````````````````````````` example +**foo bar ** +. +

    **foo bar **

    +```````````````````````````````` + + +(Nor can it be interpreted as an emphasized `*foo bar *`, because of +Rule 11.) + +This is not strong emphasis, because the second `**` is +preceded by punctuation and followed by an alphanumeric: + +```````````````````````````````` example +**(**foo) +. +

    **(**foo)

    +```````````````````````````````` + + +The point of this restriction is more easily appreciated +with these examples: + +```````````````````````````````` example +*(**foo**)* +. +

    (foo)

    +```````````````````````````````` + + +```````````````````````````````` example +**Gomphocarpus (*Gomphocarpus physocarpus*, syn. +*Asclepias physocarpa*)** +. +

    Gomphocarpus (Gomphocarpus physocarpus, syn. +Asclepias physocarpa)

    +```````````````````````````````` + + +```````````````````````````````` example +**foo "*bar*" foo** +. +

    foo "bar" foo

    +```````````````````````````````` + + +Intraword emphasis: + +```````````````````````````````` example +**foo**bar +. +

    foobar

    +```````````````````````````````` + + +Rule 8: + +This is not strong emphasis, because the closing delimiter is +preceded by whitespace: + +```````````````````````````````` example +__foo bar __ +. +

    __foo bar __

    +```````````````````````````````` + + +This is not strong emphasis, because the second `__` is +preceded by punctuation and followed by an alphanumeric: + +```````````````````````````````` example +__(__foo) +. +

    __(__foo)

    +```````````````````````````````` + + +The point of this restriction is more easily appreciated +with this example: + +```````````````````````````````` example +_(__foo__)_ +. +

    (foo)

    +```````````````````````````````` + + +Intraword strong emphasis is forbidden with `__`: + +```````````````````````````````` example +__foo__bar +. +

    __foo__bar

    +```````````````````````````````` + + +```````````````````````````````` example +__пристаням__стремятся +. +

    __пристаням__стремятся

    +```````````````````````````````` + + +```````````````````````````````` example +__foo__bar__baz__ +. +

    foo__bar__baz

    +```````````````````````````````` + + +This is strong emphasis, even though the closing delimiter is +both left- and right-flanking, because it is followed by +punctuation: + +```````````````````````````````` example +__(bar)__. +. +

    (bar).

    +```````````````````````````````` + + +Rule 9: + +Any nonempty sequence of inline elements can be the contents of an +emphasized span. + +```````````````````````````````` example +*foo [bar](/url)* +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +*foo +bar* +. +

    foo +bar

    +```````````````````````````````` + + +In particular, emphasis and strong emphasis can be nested +inside emphasis: + +```````````````````````````````` example +_foo __bar__ baz_ +. +

    foo bar baz

    +```````````````````````````````` + + +```````````````````````````````` example +_foo _bar_ baz_ +. +

    foo bar baz

    +```````````````````````````````` + + +```````````````````````````````` example +__foo_ bar_ +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +*foo *bar** +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +*foo **bar** baz* +. +

    foo bar baz

    +```````````````````````````````` + +```````````````````````````````` example +*foo**bar**baz* +. +

    foobarbaz

    +```````````````````````````````` + +Note that in the preceding case, the interpretation + +``` markdown +

    foobarbaz

    +``` + + +is precluded by the condition that a delimiter that +can both open and close (like the `*` after `foo`) +cannot form emphasis if the sum of the lengths of +the delimiter runs containing the opening and +closing delimiters is a multiple of 3 unless +both lengths are multiples of 3. + + +For the same reason, we don't get two consecutive +emphasis sections in this example: + +```````````````````````````````` example +*foo**bar* +. +

    foo**bar

    +```````````````````````````````` + + +The same condition ensures that the following +cases are all strong emphasis nested inside +emphasis, even when the interior spaces are +omitted: + + +```````````````````````````````` example +***foo** bar* +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +*foo **bar*** +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +*foo**bar*** +. +

    foobar

    +```````````````````````````````` + + +When the lengths of the interior closing and opening +delimiter runs are *both* multiples of 3, though, +they can match to create emphasis: + +```````````````````````````````` example +foo***bar***baz +. +

    foobarbaz

    +```````````````````````````````` + +```````````````````````````````` example +foo******bar*********baz +. +

    foobar***baz

    +```````````````````````````````` + + +Indefinite levels of nesting are possible: + +```````````````````````````````` example +*foo **bar *baz* bim** bop* +. +

    foo bar baz bim bop

    +```````````````````````````````` + + +```````````````````````````````` example +*foo [*bar*](/url)* +. +

    foo bar

    +```````````````````````````````` + + +There can be no empty emphasis or strong emphasis: + +```````````````````````````````` example +** is not an empty emphasis +. +

    ** is not an empty emphasis

    +```````````````````````````````` + + +```````````````````````````````` example +**** is not an empty strong emphasis +. +

    **** is not an empty strong emphasis

    +```````````````````````````````` + + + +Rule 10: + +Any nonempty sequence of inline elements can be the contents of an +strongly emphasized span. + +```````````````````````````````` example +**foo [bar](/url)** +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +**foo +bar** +. +

    foo +bar

    +```````````````````````````````` + + +In particular, emphasis and strong emphasis can be nested +inside strong emphasis: + +```````````````````````````````` example +__foo _bar_ baz__ +. +

    foo bar baz

    +```````````````````````````````` + + +```````````````````````````````` example +__foo __bar__ baz__ +. +

    foo bar baz

    +```````````````````````````````` + + +```````````````````````````````` example +____foo__ bar__ +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +**foo **bar**** +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +**foo *bar* baz** +. +

    foo bar baz

    +```````````````````````````````` + + +```````````````````````````````` example +**foo*bar*baz** +. +

    foobarbaz

    +```````````````````````````````` + + +```````````````````````````````` example +***foo* bar** +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +**foo *bar*** +. +

    foo bar

    +```````````````````````````````` + + +Indefinite levels of nesting are possible: + +```````````````````````````````` example +**foo *bar **baz** +bim* bop** +. +

    foo bar baz +bim bop

    +```````````````````````````````` + + +```````````````````````````````` example +**foo [*bar*](/url)** +. +

    foo bar

    +```````````````````````````````` + + +There can be no empty emphasis or strong emphasis: + +```````````````````````````````` example +__ is not an empty emphasis +. +

    __ is not an empty emphasis

    +```````````````````````````````` + + +```````````````````````````````` example +____ is not an empty strong emphasis +. +

    ____ is not an empty strong emphasis

    +```````````````````````````````` + + + +Rule 11: + +```````````````````````````````` example +foo *** +. +

    foo ***

    +```````````````````````````````` + + +```````````````````````````````` example +foo *\** +. +

    foo *

    +```````````````````````````````` + + +```````````````````````````````` example +foo *_* +. +

    foo _

    +```````````````````````````````` + + +```````````````````````````````` example +foo ***** +. +

    foo *****

    +```````````````````````````````` + + +```````````````````````````````` example +foo **\*** +. +

    foo *

    +```````````````````````````````` + + +```````````````````````````````` example +foo **_** +. +

    foo _

    +```````````````````````````````` + + +Note that when delimiters do not match evenly, Rule 11 determines +that the excess literal `*` characters will appear outside of the +emphasis, rather than inside it: + +```````````````````````````````` example +**foo* +. +

    *foo

    +```````````````````````````````` + + +```````````````````````````````` example +*foo** +. +

    foo*

    +```````````````````````````````` + + +```````````````````````````````` example +***foo** +. +

    *foo

    +```````````````````````````````` + + +```````````````````````````````` example +****foo* +. +

    ***foo

    +```````````````````````````````` + + +```````````````````````````````` example +**foo*** +. +

    foo*

    +```````````````````````````````` + + +```````````````````````````````` example +*foo**** +. +

    foo***

    +```````````````````````````````` + + + +Rule 12: + +```````````````````````````````` example +foo ___ +. +

    foo ___

    +```````````````````````````````` + + +```````````````````````````````` example +foo _\__ +. +

    foo _

    +```````````````````````````````` + + +```````````````````````````````` example +foo _*_ +. +

    foo *

    +```````````````````````````````` + + +```````````````````````````````` example +foo _____ +. +

    foo _____

    +```````````````````````````````` + + +```````````````````````````````` example +foo __\___ +. +

    foo _

    +```````````````````````````````` + + +```````````````````````````````` example +foo __*__ +. +

    foo *

    +```````````````````````````````` + + +```````````````````````````````` example +__foo_ +. +

    _foo

    +```````````````````````````````` + + +Note that when delimiters do not match evenly, Rule 12 determines +that the excess literal `_` characters will appear outside of the +emphasis, rather than inside it: + +```````````````````````````````` example +_foo__ +. +

    foo_

    +```````````````````````````````` + + +```````````````````````````````` example +___foo__ +. +

    _foo

    +```````````````````````````````` + + +```````````````````````````````` example +____foo_ +. +

    ___foo

    +```````````````````````````````` + + +```````````````````````````````` example +__foo___ +. +

    foo_

    +```````````````````````````````` + + +```````````````````````````````` example +_foo____ +. +

    foo___

    +```````````````````````````````` + + +Rule 13 implies that if you want emphasis nested directly inside +emphasis, you must use different delimiters: + +```````````````````````````````` example +**foo** +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +*_foo_* +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +__foo__ +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +_*foo*_ +. +

    foo

    +```````````````````````````````` + + +However, strong emphasis within strong emphasis is possible without +switching delimiters: + +```````````````````````````````` example +****foo**** +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +____foo____ +. +

    foo

    +```````````````````````````````` + + + +Rule 13 can be applied to arbitrarily long sequences of +delimiters: + +```````````````````````````````` example +******foo****** +. +

    foo

    +```````````````````````````````` + + +Rule 14: + +```````````````````````````````` example +***foo*** +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +_____foo_____ +. +

    foo

    +```````````````````````````````` + + +Rule 15: + +```````````````````````````````` example +*foo _bar* baz_ +. +

    foo _bar baz_

    +```````````````````````````````` + + +```````````````````````````````` example +*foo __bar *baz bim__ bam* +. +

    foo bar *baz bim bam

    +```````````````````````````````` + + +Rule 16: + +```````````````````````````````` example +**foo **bar baz** +. +

    **foo bar baz

    +```````````````````````````````` + + +```````````````````````````````` example +*foo *bar baz* +. +

    *foo bar baz

    +```````````````````````````````` + + +Rule 17: + +```````````````````````````````` example +*[bar*](/url) +. +

    *bar*

    +```````````````````````````````` + + +```````````````````````````````` example +_foo [bar_](/url) +. +

    _foo bar_

    +```````````````````````````````` + + +```````````````````````````````` example +* +. +

    *

    +```````````````````````````````` + + +```````````````````````````````` example +** +. +

    **

    +```````````````````````````````` + + +```````````````````````````````` example +__ +. +

    __

    +```````````````````````````````` + + +```````````````````````````````` example +*a `*`* +. +

    a *

    +```````````````````````````````` + + +```````````````````````````````` example +_a `_`_ +. +

    a _

    +```````````````````````````````` + + +```````````````````````````````` example +**a +. +

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

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

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

    +```````````````````````````````` + + +
    + +## Strikethrough (extension) + +GFM enables the `strikethrough` extension, where an additional emphasis type is +available. + +Strikethrough text is any text wrapped in two tildes (`~`). + +```````````````````````````````` example strikethrough +~~Hi~~ Hello, world! +. +

    Hi Hello, world!

    +```````````````````````````````` + +As with regular emphasis delimiters, a new paragraph will cause strikethrough +parsing to cease: + +```````````````````````````````` example strikethrough +This ~~has a + +new paragraph~~. +. +

    This ~~has a

    +

    new paragraph~~.

    +```````````````````````````````` + +
    + +## Links + +A link contains [link text] (the visible text), a [link destination] +(the URI that is the link destination), and optionally a [link title]. +There are two basic kinds of links in Markdown. In [inline links] the +destination and title are given immediately after the link text. In +[reference links] the destination and title are defined elsewhere in +the document. + +A [link text](@) consists of a sequence of zero or more +inline elements enclosed by square brackets (`[` and `]`). The +following rules apply: + +- Links may not contain other links, at any level of nesting. If + multiple otherwise valid link definitions appear nested inside each + other, the inner-most definition is used. + +- Brackets are allowed in the [link text] only if (a) they + are backslash-escaped or (b) they appear as a matched pair of brackets, + with an open bracket `[`, a sequence of zero or more inlines, and + a close bracket `]`. + +- Backtick [code spans], [autolinks], and raw [HTML tags] bind more tightly + than the brackets in link text. Thus, for example, + `` [foo`]` `` could not be a link text, since the second `]` + is part of a code span. + +- The brackets in link text bind more tightly than markers for + [emphasis and strong emphasis]. Thus, for example, `*[foo*](url)` is a link. + +A [link destination](@) consists of either + +- a sequence of zero or more characters between an opening `<` and a + closing `>` that contains no line breaks or unescaped + `<` or `>` characters, or + +- a nonempty sequence of characters that does not start with + `<`, does not include ASCII space or control characters, and + includes parentheses only if (a) they are backslash-escaped or + (b) they are part of a balanced pair of unescaped parentheses. + (Implementations may impose limits on parentheses nesting to + avoid performance issues, but at least three levels of nesting + should be supported.) + +A [link title](@) consists of either + +- a sequence of zero or more characters between straight double-quote + characters (`"`), including a `"` character only if it is + backslash-escaped, or + +- a sequence of zero or more characters between straight single-quote + characters (`'`), including a `'` character only if it is + backslash-escaped, or + +- a sequence of zero or more characters between matching parentheses + (`(...)`), including a `(` or `)` character only if it is + backslash-escaped. + +Although [link titles] may span multiple lines, they may not contain +a [blank line]. + +An [inline link](@) consists of a [link text] followed immediately +by a left parenthesis `(`, optional [whitespace], an optional +[link destination], an optional [link title] separated from the link +destination by [whitespace], optional [whitespace], and a right +parenthesis `)`. The link's text consists of the inlines contained +in the [link text] (excluding the enclosing square brackets). +The link's URI consists of the link destination, excluding enclosing +`<...>` if present, with backslash-escapes in effect as described +above. The link's title consists of the link title, excluding its +enclosing delimiters, with backslash-escapes in effect as described +above. + +Here is a simple inline link: + +```````````````````````````````` example +[link](/uri "title") +. +

    link

    +```````````````````````````````` + + +The title may be omitted: + +```````````````````````````````` example +[link](/uri) +. +

    link

    +```````````````````````````````` + + +Both the title and the destination may be omitted: + +```````````````````````````````` example +[link]() +. +

    link

    +```````````````````````````````` + + +```````````````````````````````` example +[link](<>) +. +

    link

    +```````````````````````````````` + +The destination can only contain spaces if it is +enclosed in pointy brackets: + +```````````````````````````````` example +[link](/my uri) +. +

    [link](/my uri)

    +```````````````````````````````` + +```````````````````````````````` example +[link](
    ) +. +

    link

    +```````````````````````````````` + +The destination cannot contain line breaks, +even if enclosed in pointy brackets: + +```````````````````````````````` example +[link](foo +bar) +. +

    [link](foo +bar)

    +```````````````````````````````` + +```````````````````````````````` example +[link]() +. +

    [link]()

    +```````````````````````````````` + +The destination can contain `)` if it is enclosed +in pointy brackets: + +```````````````````````````````` example +[a]() +. +

    a

    +```````````````````````````````` + +Pointy brackets that enclose links must be unescaped: + +```````````````````````````````` example +[link]() +. +

    [link](<foo>)

    +```````````````````````````````` + +These are not links, because the opening pointy bracket +is not matched properly: + +```````````````````````````````` example +[a]( +[a](c) +. +

    [a](<b)c +[a](<b)c> +[a](c)

    +```````````````````````````````` + +Parentheses inside the link destination may be escaped: + +```````````````````````````````` example +[link](\(foo\)) +. +

    link

    +```````````````````````````````` + +Any number of parentheses are allowed without escaping, as long as they are +balanced: + +```````````````````````````````` example +[link](foo(and(bar))) +. +

    link

    +```````````````````````````````` + +However, if you have unbalanced parentheses, you need to escape or use the +`<...>` form: + +```````````````````````````````` example +[link](foo\(and\(bar\)) +. +

    link

    +```````````````````````````````` + + +```````````````````````````````` example +[link]() +. +

    link

    +```````````````````````````````` + + +Parentheses and other symbols can also be escaped, as usual +in Markdown: + +```````````````````````````````` example +[link](foo\)\:) +. +

    link

    +```````````````````````````````` + + +A link can contain fragment identifiers and queries: + +```````````````````````````````` example +[link](#fragment) + +[link](http://example.com#fragment) + +[link](http://example.com?foo=3#frag) +. +

    link

    +

    link

    +

    link

    +```````````````````````````````` + + +Note that a backslash before a non-escapable character is +just a backslash: + +```````````````````````````````` example +[link](foo\bar) +. +

    link

    +```````````````````````````````` + + +URL-escaping should be left alone inside the destination, as all +URL-escaped characters are also valid URL characters. Entity and +numerical character references in the destination will be parsed +into the corresponding Unicode code points, as usual. These may +be optionally URL-escaped when written as HTML, but this spec +does not enforce any particular policy for rendering URLs in +HTML or other formats. Renderers may make different decisions +about how to escape or normalize URLs in the output. + +```````````````````````````````` example +[link](foo%20bä) +. +

    link

    +```````````````````````````````` + + +Note that, because titles can often be parsed as destinations, +if you try to omit the destination and keep the title, you'll +get unexpected results: + +```````````````````````````````` example +[link]("title") +. +

    link

    +```````````````````````````````` + + +Titles may be in single quotes, double quotes, or parentheses: + +```````````````````````````````` example +[link](/url "title") +[link](/url 'title') +[link](/url (title)) +. +

    link +link +link

    +```````````````````````````````` + + +Backslash escapes and entity and numeric character references +may be used in titles: + +```````````````````````````````` example +[link](/url "title \""") +. +

    link

    +```````````````````````````````` + + +Titles must be separated from the link using a [whitespace]. +Other [Unicode whitespace] like non-breaking space doesn't work. + +```````````````````````````````` example +[link](/url "title") +. +

    link

    +```````````````````````````````` + + +Nested balanced quotes are not allowed without escaping: + +```````````````````````````````` example +[link](/url "title "and" title") +. +

    [link](/url "title "and" title")

    +```````````````````````````````` + + +But it is easy to work around this by using a different quote type: + +```````````````````````````````` example +[link](/url 'title "and" title') +. +

    link

    +```````````````````````````````` + + +(Note: `Markdown.pl` did allow double quotes inside a double-quoted +title, and its test suite included a test demonstrating this. +But it is hard to see a good rationale for the extra complexity this +brings, since there are already many ways---backslash escaping, +entity and numeric character references, or using a different +quote type for the enclosing title---to write titles containing +double quotes. `Markdown.pl`'s handling of titles has a number +of other strange features. For example, it allows single-quoted +titles in inline links, but not reference links. And, in +reference links but not inline links, it allows a title to begin +with `"` and end with `)`. `Markdown.pl` 1.0.1 even allows +titles with no closing quotation mark, though 1.0.2b8 does not. +It seems preferable to adopt a simple, rational rule that works +the same way in inline links and link reference definitions.) + +[Whitespace] is allowed around the destination and title: + +```````````````````````````````` example +[link]( /uri + "title" ) +. +

    link

    +```````````````````````````````` + + +But it is not allowed between the link text and the +following parenthesis: + +```````````````````````````````` example +[link] (/uri) +. +

    [link] (/uri)

    +```````````````````````````````` + + +The link text may contain balanced brackets, but not unbalanced ones, +unless they are escaped: + +```````````````````````````````` example +[link [foo [bar]]](/uri) +. +

    link [foo [bar]]

    +```````````````````````````````` + + +```````````````````````````````` example +[link] bar](/uri) +. +

    [link] bar](/uri)

    +```````````````````````````````` + + +```````````````````````````````` example +[link [bar](/uri) +. +

    [link bar

    +```````````````````````````````` + + +```````````````````````````````` example +[link \[bar](/uri) +. +

    link [bar

    +```````````````````````````````` + + +The link text may contain inline content: + +```````````````````````````````` example +[link *foo **bar** `#`*](/uri) +. +

    link foo bar #

    +```````````````````````````````` + + +```````````````````````````````` example +[![moon](moon.jpg)](/uri) +. +

    moon

    +```````````````````````````````` + + +However, links may not contain other links, at any level of nesting. + +```````````````````````````````` example +[foo [bar](/uri)](/uri) +. +

    [foo bar](/uri)

    +```````````````````````````````` + + +```````````````````````````````` example +[foo *[bar [baz](/uri)](/uri)*](/uri) +. +

    [foo [bar baz](/uri)](/uri)

    +```````````````````````````````` + + +```````````````````````````````` example +![[[foo](uri1)](uri2)](uri3) +. +

    [foo](uri2)

    +```````````````````````````````` + + +These cases illustrate the precedence of link text grouping over +emphasis grouping: + +```````````````````````````````` example +*[foo*](/uri) +. +

    *foo*

    +```````````````````````````````` + + +```````````````````````````````` example +[foo *bar](baz*) +. +

    foo *bar

    +```````````````````````````````` + + +Note that brackets that *aren't* part of links do not take +precedence: + +```````````````````````````````` example +*foo [bar* baz] +. +

    foo [bar baz]

    +```````````````````````````````` + + +These cases illustrate the precedence of HTML tags, code spans, +and autolinks over link grouping: + +```````````````````````````````` example +[foo +. +

    [foo

    +```````````````````````````````` + + +```````````````````````````````` example +[foo`](/uri)` +. +

    [foo](/uri)

    +```````````````````````````````` + + +```````````````````````````````` example +[foo +. +

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

    +```````````````````````````````` + + +There are three kinds of [reference link](@)s: +[full](#full-reference-link), [collapsed](#collapsed-reference-link), +and [shortcut](#shortcut-reference-link). + +A [full reference link](@) +consists of a [link text] immediately followed by a [link label] +that [matches] a [link reference definition] elsewhere in the document. + +A [link label](@) begins with a left bracket (`[`) and ends +with the first right bracket (`]`) that is not backslash-escaped. +Between these brackets there must be at least one [non-whitespace character]. +Unescaped square bracket characters are not allowed inside the +opening and closing square brackets of [link labels]. A link +label can have at most 999 characters inside the square +brackets. + +One label [matches](@) +another just in case their normalized forms are equal. To normalize a +label, strip off the opening and closing brackets, +perform the *Unicode case fold*, strip leading and trailing +[whitespace] and collapse consecutive internal +[whitespace] to a single space. If there are multiple +matching reference link definitions, the one that comes first in the +document is used. (It is desirable in such cases to emit a warning.) + +The contents of the first 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]. + +Here is a simple example: + +```````````````````````````````` example +[foo][bar] + +[bar]: /url "title" +. +

    foo

    +```````````````````````````````` + + +The rules for the [link text] are the same as with +[inline links]. Thus: + +The link text may contain balanced brackets, but not unbalanced ones, +unless they are escaped: + +```````````````````````````````` example +[link [foo [bar]]][ref] + +[ref]: /uri +. +

    link [foo [bar]]

    +```````````````````````````````` + + +```````````````````````````````` example +[link \[bar][ref] + +[ref]: /uri +. +

    link [bar

    +```````````````````````````````` + + +The link text may contain inline content: + +```````````````````````````````` example +[link *foo **bar** `#`*][ref] + +[ref]: /uri +. +

    link foo bar #

    +```````````````````````````````` + + +```````````````````````````````` example +[![moon](moon.jpg)][ref] + +[ref]: /uri +. +

    moon

    +```````````````````````````````` + + +However, links may not contain other links, at any level of nesting. + +```````````````````````````````` example +[foo [bar](/uri)][ref] + +[ref]: /uri +. +

    [foo bar]ref

    +```````````````````````````````` + + +```````````````````````````````` example +[foo *bar [baz][ref]*][ref] + +[ref]: /uri +. +

    [foo bar baz]ref

    +```````````````````````````````` + + +(In the examples above, we have two [shortcut reference links] +instead of one [full reference link].) + +The following cases illustrate the precedence of link text grouping over +emphasis grouping: + +```````````````````````````````` example +*[foo*][ref] + +[ref]: /uri +. +

    *foo*

    +```````````````````````````````` + + +```````````````````````````````` example +[foo *bar][ref] + +[ref]: /uri +. +

    foo *bar

    +```````````````````````````````` + + +These cases illustrate the precedence of HTML tags, code spans, +and autolinks over link grouping: + +```````````````````````````````` example +[foo + +[ref]: /uri +. +

    [foo

    +```````````````````````````````` + + +```````````````````````````````` example +[foo`][ref]` + +[ref]: /uri +. +

    [foo][ref]

    +```````````````````````````````` + + +```````````````````````````````` example +[foo + +[ref]: /uri +. +

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

    +```````````````````````````````` + + +Matching is case-insensitive: + +```````````````````````````````` example +[foo][BaR] + +[bar]: /url "title" +. +

    foo

    +```````````````````````````````` + + +Unicode case fold is used: + +```````````````````````````````` example +[Толпой][Толпой] is a Russian word. + +[ТОЛПОЙ]: /url +. +

    Толпой is a Russian word.

    +```````````````````````````````` + + +Consecutive internal [whitespace] is treated as one space for +purposes of determining matching: + +```````````````````````````````` example +[Foo + bar]: /url + +[Baz][Foo bar] +. +

    Baz

    +```````````````````````````````` + + +No [whitespace] is allowed between the [link text] and the +[link label]: + +```````````````````````````````` example +[foo] [bar] + +[bar]: /url "title" +. +

    [foo] bar

    +```````````````````````````````` + + +```````````````````````````````` example +[foo] +[bar] + +[bar]: /url "title" +. +

    [foo] +bar

    +```````````````````````````````` + + +This is a departure from John Gruber's original Markdown syntax +description, which explicitly allows whitespace between the link +text and the link label. It brings reference links in line with +[inline links], which (according to both original Markdown and +this spec) cannot have whitespace after the link text. More +importantly, it prevents inadvertent capture of consecutive +[shortcut reference links]. If whitespace is allowed between the +link text and the link label, then in the following we will have +a single reference link, not two shortcut reference links, as +intended: + +``` markdown +[foo] +[bar] + +[foo]: /url1 +[bar]: /url2 +``` + +(Note that [shortcut reference links] were introduced by Gruber +himself in a beta version of `Markdown.pl`, but never included +in the official syntax description. Without shortcut reference +links, it is harmless to allow space between the link text and +link label; but once shortcut references are introduced, it is +too dangerous to allow this, as it frequently leads to +unintended results.) + +When there are multiple matching [link reference definitions], +the first is used: + +```````````````````````````````` example +[foo]: /url1 + +[foo]: /url2 + +[bar][foo] +. +

    bar

    +```````````````````````````````` + + +Note that matching is performed on normalized strings, not parsed +inline content. So the following does not match, even though the +labels define equivalent inline content: + +```````````````````````````````` example +[bar][foo\!] + +[foo!]: /url +. +

    [bar][foo!]

    +```````````````````````````````` + + +[Link labels] cannot contain brackets, unless they are +backslash-escaped: + +```````````````````````````````` example +[foo][ref[] + +[ref[]: /uri +. +

    [foo][ref[]

    +

    [ref[]: /uri

    +```````````````````````````````` + + +```````````````````````````````` example +[foo][ref[bar]] + +[ref[bar]]: /uri +. +

    [foo][ref[bar]]

    +

    [ref[bar]]: /uri

    +```````````````````````````````` + + +```````````````````````````````` example +[[[foo]]] + +[[[foo]]]: /url +. +

    [[[foo]]]

    +

    [[[foo]]]: /url

    +```````````````````````````````` + + +```````````````````````````````` example +[foo][ref\[] + +[ref\[]: /uri +. +

    foo

    +```````````````````````````````` + + +Note that in this example `]` is not backslash-escaped: + +```````````````````````````````` example +[bar\\]: /uri + +[bar\\] +. +

    bar\

    +```````````````````````````````` + + +A [link label] must contain at least one [non-whitespace character]: + +```````````````````````````````` example +[] + +[]: /uri +. +

    []

    +

    []: /uri

    +```````````````````````````````` + + +```````````````````````````````` example +[ + ] + +[ + ]: /uri +. +

    [ +]

    +

    [ +]: /uri

    +```````````````````````````````` + + +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, +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]`. + +```````````````````````````````` example +[foo][] + +[foo]: /url "title" +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +[*foo* bar][] + +[*foo* bar]: /url "title" +. +

    foo bar

    +```````````````````````````````` + + +The link labels are case-insensitive: + +```````````````````````````````` example +[Foo][] + +[foo]: /url "title" +. +

    Foo

    +```````````````````````````````` + + + +As with full reference links, [whitespace] is not +allowed between the two sets of brackets: + +```````````````````````````````` example +[foo] +[] + +[foo]: /url "title" +. +

    foo +[]

    +```````````````````````````````` + + +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, +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][]`. + +```````````````````````````````` example +[foo] + +[foo]: /url "title" +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +[*foo* bar] + +[*foo* bar]: /url "title" +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +[[*foo* bar]] + +[*foo* bar]: /url "title" +. +

    [foo bar]

    +```````````````````````````````` + + +```````````````````````````````` example +[[bar [foo] + +[foo]: /url +. +

    [[bar foo

    +```````````````````````````````` + + +The link labels are case-insensitive: + +```````````````````````````````` example +[Foo] + +[foo]: /url "title" +. +

    Foo

    +```````````````````````````````` + + +A space after the link text should be preserved: + +```````````````````````````````` example +[foo] bar + +[foo]: /url +. +

    foo bar

    +```````````````````````````````` + + +If you just want bracketed text, you can backslash-escape the +opening bracket to avoid links: + +```````````````````````````````` example +\[foo] + +[foo]: /url "title" +. +

    [foo]

    +```````````````````````````````` + + +Note that this is a link, because a link label ends with the first +following closing bracket: + +```````````````````````````````` example +[foo*]: /url + +*[foo*] +. +

    *foo*

    +```````````````````````````````` + + +Full and compact references take precedence over shortcut +references: + +```````````````````````````````` example +[foo][bar] + +[foo]: /url1 +[bar]: /url2 +. +

    foo

    +```````````````````````````````` + +```````````````````````````````` example +[foo][] + +[foo]: /url1 +. +

    foo

    +```````````````````````````````` + +Inline links also take precedence: + +```````````````````````````````` example +[foo]() + +[foo]: /url1 +. +

    foo

    +```````````````````````````````` + +```````````````````````````````` example +[foo](not a link) + +[foo]: /url1 +. +

    foo(not a link)

    +```````````````````````````````` + +In the following case `[bar][baz]` is parsed as a reference, +`[foo]` as normal text: + +```````````````````````````````` example +[foo][bar][baz] + +[baz]: /url +. +

    [foo]bar

    +```````````````````````````````` + + +Here, though, `[foo][bar]` is parsed as a reference, since +`[bar]` is defined: + +```````````````````````````````` example +[foo][bar][baz] + +[baz]: /url1 +[bar]: /url2 +. +

    foobaz

    +```````````````````````````````` + + +Here `[foo]` is not parsed as a shortcut reference, because it +is followed by a link label (even though `[bar]` is not defined): + +```````````````````````````````` example +[foo][bar][baz] + +[baz]: /url1 +[foo]: /url2 +. +

    [foo]bar

    +```````````````````````````````` + + + +## Images + +Syntax for images is like the syntax for links, with one +difference. Instead of [link text], we have an +[image description](@). The rules for this are the +same as for [link text], except that (a) an +image description starts with `![` rather than `[`, and +(b) an image description may contain links. +An image description has inline elements +as its contents. When an image is rendered to HTML, +this is standardly used as the image's `alt` attribute. + +```````````````````````````````` example +![foo](/url "title") +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +![foo *bar*] + +[foo *bar*]: train.jpg "train & tracks" +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +![foo ![bar](/url)](/url2) +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +![foo [bar](/url)](/url2) +. +

    foo bar

    +```````````````````````````````` + + +Though this spec is concerned with parsing, not rendering, it is +recommended that in rendering to HTML, only the plain string content +of the [image description] be used. Note that in +the above example, the alt attribute's value is `foo bar`, not `foo +[bar](/url)` or `foo bar`. Only the plain string +content is rendered, without formatting. + +```````````````````````````````` example +![foo *bar*][] + +[foo *bar*]: train.jpg "train & tracks" +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +![foo *bar*][foobar] + +[FOOBAR]: train.jpg "train & tracks" +. +

    foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +![foo](train.jpg) +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +My ![foo bar](/path/to/train.jpg "title" ) +. +

    My foo bar

    +```````````````````````````````` + + +```````````````````````````````` example +![foo]() +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +![](/url) +. +

    +```````````````````````````````` + + +Reference-style: + +```````````````````````````````` example +![foo][bar] + +[bar]: /url +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +![foo][bar] + +[BAR]: /url +. +

    foo

    +```````````````````````````````` + + +Collapsed: + +```````````````````````````````` example +![foo][] + +[foo]: /url "title" +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +![*foo* bar][] + +[*foo* bar]: /url "title" +. +

    foo bar

    +```````````````````````````````` + + +The labels are case-insensitive: + +```````````````````````````````` example +![Foo][] + +[foo]: /url "title" +. +

    Foo

    +```````````````````````````````` + + +As with reference links, [whitespace] is not allowed +between the two sets of brackets: + +```````````````````````````````` example +![foo] +[] + +[foo]: /url "title" +. +

    foo +[]

    +```````````````````````````````` + + +Shortcut: + +```````````````````````````````` example +![foo] + +[foo]: /url "title" +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +![*foo* bar] + +[*foo* bar]: /url "title" +. +

    foo bar

    +```````````````````````````````` + + +Note that link labels cannot contain unescaped brackets: + +```````````````````````````````` example +![[foo]] + +[[foo]]: /url "title" +. +

    ![[foo]]

    +

    [[foo]]: /url "title"

    +```````````````````````````````` + + +The link labels are case-insensitive: + +```````````````````````````````` example +![Foo] + +[foo]: /url "title" +. +

    Foo

    +```````````````````````````````` + + +If you just want a literal `!` followed by bracketed text, you can +backslash-escape the opening `[`: + +```````````````````````````````` example +!\[foo] + +[foo]: /url "title" +. +

    ![foo]

    +```````````````````````````````` + + +If you want a link after a literal `!`, backslash-escape the +`!`: + +```````````````````````````````` example +\![foo] + +[foo]: /url "title" +. +

    !foo

    +```````````````````````````````` + + +## Autolinks + +[Autolink](@)s are absolute URIs and email addresses inside +`<` and `>`. They are parsed as links, with the URL or email address +as the link label. + +A [URI autolink](@) consists of `<`, followed by an +[absolute URI] followed by `>`. It is parsed as +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 than ASCII +[whitespace] and control characters, `<`, and `>`. If +the URI includes these characters, they must be percent-encoded +(e.g. `%20` for a space). + +For purposes of this spec, a [scheme](@) is any sequence +of 2--32 characters beginning with an ASCII letter and followed +by any combination of ASCII letters, digits, or the symbols plus +("+"), period ("."), or hyphen ("-"). + +Here are some valid autolinks: + +```````````````````````````````` example + +. +

    http://foo.bar.baz

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

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

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    irc://foo.bar:2233/baz

    +```````````````````````````````` + + +Uppercase is also fine: + +```````````````````````````````` example + +. +

    MAILTO:FOO@BAR.BAZ

    +```````````````````````````````` + + +Note that many strings that count as [absolute URIs] for +purposes of this spec are not valid URIs, because their +schemes are not registered or because of other problems +with their syntax: + +```````````````````````````````` example + +. +

    a+b+c:d

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    made-up-scheme://foo,bar

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    http://../

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    localhost:5001/foo

    +```````````````````````````````` + + +Spaces are not allowed in autolinks: + +```````````````````````````````` example + +. +

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

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

    http://example.com/\[\

    +```````````````````````````````` + + +An [email autolink](@) +consists of `<`, followed by an [email address], +followed by `>`. The link's label is the email address, +and the URL is `mailto:` followed by the email address. + +An [email address](@), +for these purposes, is anything that matches +the [non-normative regex from the HTML5 +spec](https://html.spec.whatwg.org/multipage/forms.html#e-mail-state-(type=email)): + + /^[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])?)*$/ + +Examples of email autolinks: + +```````````````````````````````` example + +. +

    foo@bar.example.com

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    foo+special@Bar.baz-bar0.com

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

    <foo+@bar.example.com>

    +```````````````````````````````` + + +These are not autolinks: + +```````````````````````````````` example +<> +. +

    <>

    +```````````````````````````````` + + +```````````````````````````````` example +< http://foo.bar > +. +

    < http://foo.bar >

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    <m:abc>

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    <foo.bar.baz>

    +```````````````````````````````` + + +```````````````````````````````` example +http://example.com +. +

    http://example.com

    +```````````````````````````````` + + +```````````````````````````````` example +foo@bar.example.com +. +

    foo@bar.example.com

    +```````````````````````````````` + +
    + +## Autolinks (extension) + +GFM enables the `autolink` extension, where autolinks will be recognised in a +greater number of conditions. + +[Autolink]s can also be constructed without requiring the use of `<` and to `>` +to delimit them, although they will be recognized under a smaller set of +circumstances. All such recognized autolinks can only come at the beginning of +a line, after whitespace, or any of the delimiting characters `*`, `_`, `~`, +and `(`. + +An [extended www autolink](@) will be recognized +when the text `www.` is found followed by a [valid domain]. +A [valid domain](@) consists of segments +of alphanumeric characters, underscores (`_`) and hyphens (`-`) +separated by periods (`.`). +There must be at least one period, +and no underscores may be present in the last two segments of the domain. + +The scheme `http` will be inserted automatically: + +```````````````````````````````` example autolink +www.commonmark.org +. +

    www.commonmark.org

    +```````````````````````````````` + +After a [valid domain], zero or more non-space non-`<` characters may follow: + +```````````````````````````````` example autolink +Visit www.commonmark.org/help for more information. +. +

    Visit www.commonmark.org/help for more information.

    +```````````````````````````````` + +We then apply [extended autolink path validation](@) as follows: + +Trailing punctuation (specifically, `?`, `!`, `.`, `,`, `:`, `*`, `_`, and `~`) +will not be considered part of the autolink, though they may be included in the +interior of the link: + +```````````````````````````````` example autolink +Visit www.commonmark.org. + +Visit www.commonmark.org/a.b. +. +

    Visit www.commonmark.org.

    +

    Visit www.commonmark.org/a.b.

    +```````````````````````````````` + +When an autolink ends in `)`, we scan the entire autolink for the total number +of parentheses. If there is a greater number of closing parentheses than +opening ones, we don't consider the unmatched trailing parentheses part of the +autolink, in order to facilitate including an autolink inside a parenthesis: + +```````````````````````````````` example autolink +www.google.com/search?q=Markup+(business) + +www.google.com/search?q=Markup+(business))) + +(www.google.com/search?q=Markup+(business)) + +(www.google.com/search?q=Markup+(business) +. +

    www.google.com/search?q=Markup+(business)

    +

    www.google.com/search?q=Markup+(business)))

    +

    (www.google.com/search?q=Markup+(business))

    +

    (www.google.com/search?q=Markup+(business)

    +```````````````````````````````` + +This check is only done when the link ends in a closing parentheses `)`, so if +the only parentheses are in the interior of the autolink, no special rules are +applied: + +```````````````````````````````` example autolink +www.google.com/search?q=(business))+ok +. +

    www.google.com/search?q=(business))+ok

    +```````````````````````````````` + +If an autolink ends in a semicolon (`;`), we check to see if it appears to +resemble an [entity reference][entity references]; if the preceding text is `&` +followed by one or more alphanumeric characters. If so, it is excluded from +the autolink: + +```````````````````````````````` example autolink +www.google.com/search?q=commonmark&hl=en + +www.google.com/search?q=commonmark&hl; +. +

    www.google.com/search?q=commonmark&hl=en

    +

    www.google.com/search?q=commonmark&hl;

    +```````````````````````````````` + +`<` immediately ends an autolink. + +```````````````````````````````` example autolink +www.commonmark.org/hewww.commonmark.org/he<lp

    +```````````````````````````````` + +An [extended url autolink](@) will be recognised when one of the schemes +`http://`, `https://`, or `ftp://`, followed by a [valid domain], then zero or +more non-space non-`<` characters according to +[extended autolink path validation]: + +```````````````````````````````` example autolink +http://commonmark.org + +(Visit https://encrypted.google.com/search?q=Markup+(business)) + +Anonymous FTP is available at ftp://foo.bar.baz. +. +

    http://commonmark.org

    +

    (Visit https://encrypted.google.com/search?q=Markup+(business))

    +

    Anonymous FTP is available at ftp://foo.bar.baz.

    +```````````````````````````````` + + +An [extended email autolink](@) will be recognised when an email address is +recognised within any text node. Email addresses are recognised according to +the following rules: + +* One ore more characters which are alphanumeric, or `.`, `-`, `_`, or `+`. +* An `@` symbol. +* One or more characters which are alphanumeric, or `-` or `_`, + separated by periods (`.`). + There must be at least one period. + The last character must not be one of `-` or `_`. + +The scheme `mailto:` will automatically be added to the generated link: + +```````````````````````````````` example autolink +foo@bar.baz +. +

    foo@bar.baz

    +```````````````````````````````` + +`+` can occur before the `@`, but not after. + +```````````````````````````````` example autolink +hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is. +. +

    hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is.

    +```````````````````````````````` + +`.`, `-`, and `_` can occur on both sides of the `@`, but only `.` may occur at +the end of the email address, in which case it will not be considered part of +the address: + +```````````````````````````````` example autolink +a.b-c_d@a.b + +a.b-c_d@a.b. + +a.b-c_d@a.b- + +a.b-c_d@a.b_ +. +

    a.b-c_d@a.b

    +

    a.b-c_d@a.b.

    +

    a.b-c_d@a.b-

    +

    a.b-c_d@a.b_

    +```````````````````````````````` + +
    + +## Raw HTML + +Text between `<` and `>` that looks like an HTML tag is parsed as a +raw HTML tag and will be rendered in HTML without escaping. +Tag and attribute names are not limited to current HTML tags, +so custom tags (and even, say, DocBook tags) may be used. + +Here is the grammar for tags: + +A [tag name](@) consists of an ASCII letter +followed by zero or more ASCII letters, digits, or +hyphens (`-`). + +An [attribute](@) consists of [whitespace], +an [attribute name], and an optional +[attribute value specification]. + +An [attribute name](@) +consists of an ASCII letter, `_`, or `:`, followed by zero or more ASCII +letters, digits, `_`, `.`, `:`, or `-`. (Note: This is the XML +specification restricted to ASCII. HTML5 is laxer.) + +An [attribute value specification](@) +consists of optional [whitespace], +a `=` character, optional [whitespace], and an [attribute +value]. + +An [attribute value](@) +consists of an [unquoted attribute value], +a [single-quoted attribute value], or a [double-quoted attribute value]. + +An [unquoted attribute value](@) +is a nonempty string of characters not +including [whitespace], `"`, `'`, `=`, `<`, `>`, or `` ` ``. + +A [single-quoted attribute value](@) +consists of `'`, zero or more +characters not including `'`, and a final `'`. + +A [double-quoted attribute value](@) +consists of `"`, zero or more +characters not including `"`, and a final `"`. + +An [open tag](@) consists of a `<` character, a [tag name], +zero or more [attributes], optional [whitespace], an optional `/` +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).) + +A [processing instruction](@) +consists of the string ``, and the string +`?>`. + +A [declaration](@) consists of the +string ``, and the character `>`. + +A [CDATA section](@) consists of +the string ``, and the string `]]>`. + +An [HTML tag](@) consists of an [open tag], a [closing tag], +an [HTML comment], a [processing instruction], a [declaration], +or a [CDATA section]. + +Here are some simple open tags: + +```````````````````````````````` example + +. +

    +```````````````````````````````` + + +Empty elements: + +```````````````````````````````` example + +. +

    +```````````````````````````````` + + +[Whitespace] is allowed: + +```````````````````````````````` example + +. +

    +```````````````````````````````` + + +With attributes: + +```````````````````````````````` example + +. +

    +```````````````````````````````` + + +Custom tag names can be used: + +```````````````````````````````` example +Foo +. +

    Foo

    +```````````````````````````````` + + +Illegal tag names, not parsed as HTML: + +```````````````````````````````` example +<33> <__> +. +

    <33> <__>

    +```````````````````````````````` + + +Illegal attribute names: + +```````````````````````````````` example +
    +. +

    <a h*#ref="hi">

    +```````````````````````````````` + + +Illegal attribute values: + +```````````````````````````````` example +
    +. +

    </a href="foo">

    +```````````````````````````````` + + +Comments: + +```````````````````````````````` example +foo +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example +foo +. +

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

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

    foo <!--> foo -->

    +

    foo <!-- foo--->

    +```````````````````````````````` + + +Processing instructions: + +```````````````````````````````` example +foo +. +

    foo

    +```````````````````````````````` + + +Declarations: + +```````````````````````````````` example +foo +. +

    foo

    +```````````````````````````````` + + +CDATA sections: + +```````````````````````````````` example +foo &<]]> +. +

    foo &<]]>

    +```````````````````````````````` + + +Entity and numeric character references are preserved in HTML +attributes: + +```````````````````````````````` example +foo
    +. +

    foo

    +```````````````````````````````` + + +Backslash escapes do not work in HTML attributes: + +```````````````````````````````` example +foo +. +

    foo

    +```````````````````````````````` + + +```````````````````````````````` example + +. +

    <a href=""">

    +```````````````````````````````` + + +
    + +## Disallowed Raw HTML (extension) + +GFM enables the `tagfilter` extension, where the following HTML tags will be +filtered when rendering HTML output: + +* `` +* `<textarea>` +* `<style>` +* `<xmp>` +* `<iframe>` +* `<noembed>` +* `<noframes>` +* `<script>` +* `<plaintext>` + +Filtering is done by replacing the leading `<` with the entity `<`. These +tags are chosen in particular as they change how HTML is interpreted in a way +unique to them (i.e. nested HTML is interpreted differently), and this is +usually undesireable in the context of other rendered Markdown content. + +All other HTML tags are left untouched. + +```````````````````````````````` example tagfilter +<strong> <title> <style> <em> + +<blockquote> + <xmp> is disallowed. <XMP> is also disallowed. +</blockquote> +. +<p><strong> <title> <style> <em></p> +<blockquote> + <xmp> is disallowed. <XMP> is also disallowed. +</blockquote> +```````````````````````````````` + +</div> + +## Hard line breaks + +A line break (not in a code span or HTML tag) that is preceded +by two or more spaces and does not occur at the end of a block +is parsed as a [hard line break](@) (rendered +in HTML as a `<br />` tag): + +```````````````````````````````` example +foo +baz +. +<p>foo<br /> +baz</p> +```````````````````````````````` + + +For a more visible alternative, a backslash before the +[line ending] may be used instead of two spaces: + +```````````````````````````````` example +foo\ +baz +. +<p>foo<br /> +baz</p> +```````````````````````````````` + + +More than two spaces can be used: + +```````````````````````````````` example +foo +baz +. +<p>foo<br /> +baz</p> +```````````````````````````````` + + +Leading spaces at the beginning of the next line are ignored: + +```````````````````````````````` example +foo + bar +. +<p>foo<br /> +bar</p> +```````````````````````````````` + + +```````````````````````````````` example +foo\ + bar +. +<p>foo<br /> +bar</p> +```````````````````````````````` + + +Line breaks can occur inside emphasis, links, and other constructs +that allow inline content: + +```````````````````````````````` example +*foo +bar* +. +<p><em>foo<br /> +bar</em></p> +```````````````````````````````` + + +```````````````````````````````` example +*foo\ +bar* +. +<p><em>foo<br /> +bar</em></p> +```````````````````````````````` + + +Line breaks do not occur inside code spans + +```````````````````````````````` example +`code +span` +. +<p><code>code span</code></p> +```````````````````````````````` + + +```````````````````````````````` example +`code\ +span` +. +<p><code>code\ span</code></p> +```````````````````````````````` + + +or HTML tags: + +```````````````````````````````` example +<a href="foo +bar"> +. +<p><a href="foo +bar"></p> +```````````````````````````````` + + +```````````````````````````````` example +<a href="foo\ +bar"> +. +<p><a href="foo\ +bar"></p> +```````````````````````````````` + + +Hard line breaks are for separating inline content within a block. +Neither syntax for hard line breaks works at the end of a paragraph or +other block element: + +```````````````````````````````` example +foo\ +. +<p>foo\</p> +```````````````````````````````` + + +```````````````````````````````` example +foo +. +<p>foo</p> +```````````````````````````````` + + +```````````````````````````````` example +### foo\ +. +<h3>foo\</h3> +```````````````````````````````` + + +```````````````````````````````` example +### foo +. +<h3>foo</h3> +```````````````````````````````` + + +## Soft line breaks + +A regular line break (not in a code span or HTML tag) that is not +preceded by two or more spaces or a backslash is parsed as a +[softbreak](@). (A softbreak may be rendered in HTML either as a +[line ending] or as a space. The result will be the same in +browsers. In the examples here, a [line ending] will be used.) + +```````````````````````````````` example +foo +baz +. +<p>foo +baz</p> +```````````````````````````````` + + +Spaces at the end of the line and beginning of the next line are +removed: + +```````````````````````````````` example +foo + baz +. +<p>foo +baz</p> +```````````````````````````````` + + +A conforming parser may render a soft line break in HTML either as a +line break or as a space. + +A renderer may also provide an option to render soft line breaks +as hard line breaks. + +## Textual content + +Any characters not given an interpretation by the above rules will +be parsed as plain textual content. + +```````````````````````````````` example +hello $.;'there +. +<p>hello $.;'there</p> +```````````````````````````````` + + +```````````````````````````````` example +Foo χρῆν +. +<p>Foo χρῆν</p> +```````````````````````````````` + + +Internal spaces are preserved verbatim: + +```````````````````````````````` example +Multiple spaces +. +<p>Multiple spaces</p> +```````````````````````````````` + + +<!-- END TESTS --> + +# Appendix: A parsing strategy + +In this appendix we describe some features of the parsing strategy +used in the CommonMark reference implementations. + +## Overview + +Parsing has two phases: + +1. In the first phase, lines of input are consumed and the block +structure of the document---its division into paragraphs, block quotes, +list items, and so on---is constructed. Text is assigned to these +blocks but not parsed. Link reference definitions are parsed and a +map of links is constructed. + +2. In the second phase, the raw text contents of paragraphs and headings +are parsed into sequences of Markdown inline elements (strings, +code spans, links, emphasis, and so on), using the map of link +references constructed in phase 1. + +At each point in processing, the document is represented as a tree of +**blocks**. The root of the tree is a `document` block. The `document` +may have any number of other blocks as **children**. These children +may, in turn, have other blocks as children. The last child of a block +is normally considered **open**, meaning that subsequent lines of input +can alter its contents. (Blocks that are not open are **closed**.) +Here, for example, is a possible document tree, with the open blocks +marked by arrows: + +``` tree +-> document + -> block_quote + paragraph + "Lorem ipsum dolor\nsit amet." + -> list (type=bullet tight=true bullet_char=-) + list_item + paragraph + "Qui *quodsi iracundia*" + -> list_item + -> paragraph + "aliquando id" +``` + +## Phase 1: block structure + +Each line that is processed has an effect on this tree. The line is +analyzed and, depending on its contents, the document may be altered +in one or more of the following ways: + +1. One or more open blocks may be closed. +2. One or more new blocks may be created as children of the + last open block. +3. Text may be added to the last (deepest) open block remaining + on the tree. + +Once a line has been incorporated into the tree in this way, +it can be discarded, so input can be read in a stream. + +For each line, we follow this procedure: + +1. First we iterate through the open blocks, starting with the +root document, and descending through last children down to the last +open block. Each block imposes a condition that the line must satisfy +if the block is to remain open. For example, a block quote requires a +`>` character. A paragraph requires a non-blank line. +In this phase we may match all or just some of the open +blocks. But we cannot close unmatched blocks yet, because we may have a +[lazy continuation line]. + +2. Next, after consuming the continuation markers for existing +blocks, we look for new block starts (e.g. `>` for a block quote). +If we encounter a new block start, we close any blocks unmatched +in step 1 before creating the new block as a child of the last +matched block. + +3. Finally, we look at the remainder of the line (after block +markers like `>`, list markers, and indentation have been consumed). +This is text that can be incorporated into the last open +block (a paragraph, code block, heading, or raw HTML). + +Setext headings are formed when we see a line of a paragraph +that is a [setext heading underline]. + +Reference link definitions are detected when a paragraph is closed; +the accumulated text lines are parsed to see if they begin with +one or more reference link definitions. Any remainder becomes a +normal paragraph. + +We can see how this works by considering how the tree above is +generated by four lines of Markdown: + +``` markdown +> Lorem ipsum dolor +sit amet. +> - Qui *quodsi iracundia* +> - aliquando id +``` + +At the outset, our document model is just + +``` tree +-> document +``` + +The first line of our text, + +``` markdown +> Lorem ipsum dolor +``` + +causes a `block_quote` block to be created as a child of our +open `document` block, and a `paragraph` block as a child of +the `block_quote`. Then the text is added to the last open +block, the `paragraph`: + +``` tree +-> document + -> block_quote + -> paragraph + "Lorem ipsum dolor" +``` + +The next line, + +``` markdown +sit amet. +``` + +is a "lazy continuation" of the open `paragraph`, so it gets added +to the paragraph's text: + +``` tree +-> document + -> block_quote + -> paragraph + "Lorem ipsum dolor\nsit amet." +``` + +The third line, + +``` markdown +> - Qui *quodsi iracundia* +``` + +causes the `paragraph` block to be closed, and a new `list` block +opened as a child of the `block_quote`. A `list_item` is also +added as a child of the `list`, and a `paragraph` as a child of +the `list_item`. The text is then added to the new `paragraph`: + +``` tree +-> document + -> block_quote + paragraph + "Lorem ipsum dolor\nsit amet." + -> list (type=bullet tight=true bullet_char=-) + -> list_item + -> paragraph + "Qui *quodsi iracundia*" +``` + +The fourth line, + +``` markdown +> - aliquando id +``` + +causes the `list_item` (and its child the `paragraph`) to be closed, +and a new `list_item` opened up as child of the `list`. A `paragraph` +is added as a child of the new `list_item`, to contain the text. +We thus obtain the final tree: + +``` tree +-> document + -> block_quote + paragraph + "Lorem ipsum dolor\nsit amet." + -> list (type=bullet tight=true bullet_char=-) + list_item + paragraph + "Qui *quodsi iracundia*" + -> list_item + -> paragraph + "aliquando id" +``` + +## Phase 2: inline structure + +Once all of the input has been parsed, all open blocks are closed. + +We then "walk the tree," visiting every node, and parse raw +string contents of paragraphs and headings as inlines. At this +point we have seen all the link reference definitions, so we can +resolve reference links as we go. + +``` tree +document + block_quote + paragraph + str "Lorem ipsum dolor" + softbreak + str "sit amet." + list (type=bullet tight=true bullet_char=-) + list_item + paragraph + str "Qui " + emph + str "quodsi iracundia" + list_item + paragraph + str "aliquando id" +``` + +Notice how the [line ending] in the first paragraph has +been parsed as a `softbreak`, and the asterisks in the first list item +have become an `emph`. + +### An algorithm for parsing nested emphasis and links + +By far the trickiest part of inline parsing is handling emphasis, +strong emphasis, links, and images. This is done using the following +algorithm. + +When we're parsing inlines and we hit either + +- a run of `*` or `_` characters, or +- a `[` or `![` + +we insert a text node with these symbols as its literal content, and we +add a pointer to this text node to the [delimiter stack](@). + +The [delimiter stack] is a doubly linked list. Each +element contains a pointer to a text node, plus information about + +- the type of delimiter (`[`, `![`, `*`, `_`) +- the number of delimiters, +- whether the delimiter is "active" (all are active to start), and +- whether the delimiter is a potential opener, a potential closer, + or both (which depends on what sort of characters precede + and follow the delimiters). + +When we hit a `]` character, we call the *look for link or image* +procedure (see below). + +When we hit the end of the input, we call the *process emphasis* +procedure (see below), with `stack_bottom` = NULL. + +#### *look for link or image* + +Starting at the top of the delimiter stack, we look backwards +through the stack for an opening `[` or `![` delimiter. + +- If we don't find one, we return a literal text node `]`. + +- If we do find one, but it's not *active*, we remove the inactive + 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 + link/image, or shortcut reference link/image. + + + If we don't, then we remove the opening delimiter from the + delimiter stack and return a literal text node `]`. + + + If we do, then + + * We return a link or image node whose children are the inlines + after the text node pointed to by the opening delimiter. + + * We run *process emphasis* on these inlines, with the `[` opener + as `stack_bottom`. + + * We remove the opening delimiter. + + * If we have a link (and not an image), we also set all + `[` delimiters before the opening delimiter to *inactive*. (This + will prevent us from getting links within links.) + +#### *process emphasis* + +Parameter `stack_bottom` sets a lower bound to how far we +descend in the [delimiter stack]. If it is NULL, we can +go all the way to the bottom. Otherwise, we stop before +visiting `stack_bottom`. + +Let `current_position` point to the element on the [delimiter stack] +just above `stack_bottom` (or the first element if `stack_bottom` +is NULL). + +We keep track of the `openers_bottom` for each delimiter +type (`*`, `_`) and each length of the closing delimiter run +(modulo 3). Initialize this to `stack_bottom`. + +Then we repeat the following until we run out of potential +closers: + +- Move `current_position` forward in the delimiter stack (if needed) + until we find the first potential closer with delimiter `*` or `_`. + (This will be the potential closer closest + to the beginning of the input -- the first one in parse order.) + +- Now, look back in the stack (staying above `stack_bottom` and + the `openers_bottom` for this delimiter type) for the + first matching potential opener ("matching" means same delimiter). + +- If one is found: + + + Figure out whether we have emphasis or strong emphasis: + if both closer and opener spans have length >= 2, we have + strong, otherwise regular. + + + Insert an emph or strong emph node accordingly, after + the text node corresponding to the opener. + + + Remove any delimiters between the opener and closer from + the delimiter stack. + + + Remove 1 (for regular emph) or 2 (for strong emph) delimiters + from the opening and closing text nodes. If they become empty + as a result, remove them and remove the corresponding element + of the delimiter stack. If the closing node is removed, reset + `current_position` to the next element in the stack. + +- If none is found: + + + Set `openers_bottom` to the element before `current_position`. + (We know that there are no openers for this kind of closer up to and + including this point, so this puts a lower bound on future searches.) + + + If the closer at `current_position` is not a potential opener, + remove it from the delimiter stack (since we know it can't + be a closer either). + + + Advance `current_position` to the next element in the stack. + +After we're done, we remove all delimiters above `stack_bottom` from the +delimiter stack. + 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 8dc2c1e25..417a66097 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 @@ -4,18 +4,24 @@ public class Example { private final String filename; private final String section; + private final String info; private final int exampleNumber; private final String source; private final String html; - public Example(String filename, String section, int exampleNumber, String source, String html) { + public Example(String filename, String section, String info, int exampleNumber, String source, String html) { this.filename = filename; this.section = section; + this.info = info; this.exampleNumber = exampleNumber; this.source = source; this.html = html; } + public String getInfo() { + return info; + } + public String getSource() { return source; } 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 ad288f2fb..0972a227f 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 @@ -14,12 +14,15 @@ public class ExampleReader { private static final Pattern SECTION_PATTERN = Pattern.compile("#{1,6} *(.*)"); + private static final String EXAMPLE_START_MARKER = "```````````````````````````````` example"; private final InputStream inputStream; private final String filename; private State state = State.BEFORE; private String section; + // The gfm spec has additional text after the example marker for their additions, e.g. "table" + private String info = ""; private StringBuilder source; private StringBuilder html; private int exampleNumber = 0; @@ -70,7 +73,8 @@ private void processLine(String line) { section = matcher.group(1); exampleNumber = 0; } - if (line.equals("```````````````````````````````` example")) { + if (line.startsWith(EXAMPLE_START_MARKER)) { + info = line.substring(EXAMPLE_START_MARKER.length()).trim(); state = State.SOURCE; exampleNumber++; } @@ -87,7 +91,7 @@ private void processLine(String line) { case HTML: if (line.equals("````````````````````````````````")) { state = State.BEFORE; - examples.add(new Example(filename, section, exampleNumber, + examples.add(new Example(filename, section, info, exampleNumber, source.toString(), html.toString())); resetContents(); } else { diff --git a/etc/update-spec.sh b/etc/update-spec.sh index e5276f4b9..c31613e5d 100755 --- a/etc/update-spec.sh +++ b/etc/update-spec.sh @@ -7,6 +7,7 @@ fi version=$1 curl -L "https://raw.githubusercontent.com/commonmark/CommonMark/$version/spec.txt" -o commonmark-test-util/src/main/resources/spec.txt +curl -L "https://raw.githubusercontent.com/github/cmark-gfm/master/test/spec.txt" -o commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt echo "Check cmark and commonmark.js regression.txt:" echo "https://github.com/commonmark/cmark/blob/master/test/regression.txt" From 50b88ae5f4fabd4ed7d09b8b63471e9f1d31c996 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 12 Jul 2019 16:42:10 +1000 Subject: [PATCH 350/815] Add newlines around table cell rendering Doesn't really matter, and easier if it matches what's in the spec. --- .../gfm/tables/internal/TableHtmlNodeRenderer.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 aa393ac15..a1de50aac 100644 --- a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableHtmlNodeRenderer.java @@ -1,17 +1,13 @@ package org.commonmark.ext.gfm.tables.internal; -import java.util.Collections; -import java.util.Map; - -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.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlWriter; +import java.util.Collections; +import java.util.Map; + public class TableHtmlNodeRenderer extends TableNodeRenderer { private final HtmlWriter htmlWriter; @@ -56,9 +52,11 @@ protected void renderRow(TableRow tableRow) { protected void renderCell(TableCell tableCell) { String tagName = tableCell.isHeader() ? "th" : "td"; + htmlWriter.line(); htmlWriter.tag(tagName, getCellAttributes(tableCell, tagName)); renderChildren(tableCell); htmlWriter.tag("/" + tagName); + htmlWriter.line(); } private Map<String, String> getAttributes(Node node, String tagName) { From a4172736d1e1b780ec512b03b87abbd5ed00e6c6 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 12 Jul 2019 17:15:09 +1000 Subject: [PATCH 351/815] Allow lazy continuation lines for tables --- .../ext/gfm/tables/internal/TableBlockParser.java | 9 +++++++-- .../java/org/commonmark/internal/DocumentParser.java | 2 +- .../java/org/commonmark/internal/ParagraphParser.java | 5 +++++ .../commonmark/parser/block/AbstractBlockParser.java | 5 +++++ .../java/org/commonmark/parser/block/BlockParser.java | 11 +++++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 70ed0fa7c..52f523ace 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 @@ -16,8 +16,8 @@ public class TableBlockParser extends AbstractBlockParser { private static Pattern TABLE_HEADER_SEPARATOR = Pattern.compile( // For single column, require at least one pipe, otherwise it's ambiguous with setext headers "\\|" + COL + "\\|?\\s*" + "|" + - COL + "\\|\\s*" + "|" + - "\\|?" + "(?:" + COL + "\\|)+" + COL + "\\|?\\s*"); + COL + "\\|\\s*" + "|" + + "\\|?" + "(?:" + COL + "\\|)+" + COL + "\\|?\\s*"); private final TableBlock block = new TableBlock(); private final List<CharSequence> rowLines = new ArrayList<>(); @@ -29,6 +29,11 @@ private TableBlockParser(CharSequence headerLine) { rowLines.add(headerLine); } + @Override + public boolean canHaveLazyContinuationLines() { + return true; + } + @Override public Block getBlock() { return block; diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 9c186e87d..d1b43a7b4 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -254,7 +254,7 @@ private void incorporateLine(CharSequence ln) { // First check for a lazy paragraph continuation: if (!allClosed && !isBlank() && - getActiveBlockParser() instanceof ParagraphParser) { + getActiveBlockParser().canHaveLazyContinuationLines()) { // lazy paragraph continuation addLine(); diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index fc44cfd57..ee2899a93 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -15,6 +15,11 @@ public class ParagraphParser extends AbstractBlockParser { private final Paragraph block = new Paragraph(); private LinkReferenceDefinitionParser linkReferenceDefinitionParser = new LinkReferenceDefinitionParser(); + @Override + public boolean canHaveLazyContinuationLines() { + return true; + } + @Override public Block getBlock() { return block; 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 d6b30fa68..f806d105c 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java @@ -10,6 +10,11 @@ public boolean isContainer() { return false; } + @Override + public boolean canHaveLazyContinuationLines() { + return false; + } + @Override public boolean canContain(Block childBlock) { return false; 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 851436051..0c903198c 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java @@ -15,6 +15,17 @@ public interface BlockParser { */ boolean isContainer(); + /** + * Return true if the block can have lazy continuation lines. + * <p> + * Lazy continuation lines are lines that were rejected by this {@link #tryContinue(ParserState)} but didn't match + * any other block parsers either. + * <p> + * If true is returned here, those lines will get added via {@link #addLine(CharSequence)}. For false, the block is + * closed instead. + */ + boolean canHaveLazyContinuationLines(); + boolean canContain(Block childBlock); Block getBlock(); From 0cd9c1ae057b39e9eda5ad4334a328a756ba0a5e Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 12:06:06 +1000 Subject: [PATCH 352/815] Adjust other tests for formatting and lazy continuation change --- .../commonmark/ext/gfm/tables/TablesTest.java | 297 ++++++++++++++---- .../ext/gfm/tables/TablesTextContentTest.java | 13 +- 2 files changed, 234 insertions(+), 76 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 00b26c438..104b7d9ba 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 @@ -33,13 +33,19 @@ public void mustHaveHeaderAndSeparator() { public void separatorMustBeOneOrMore() { assertRendering("Abc|Def\n-|-", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody></tbody>\n" + "</table>\n"); assertRendering("Abc|Def\n--|--", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody></tbody>\n" + "</table>\n"); @@ -66,7 +72,10 @@ public void headerMustBeOneLine() { public void oneHeadNoBody() { assertRendering("Abc|Def\n---|---", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody></tbody>\n" + "</table>\n"); @@ -76,7 +85,9 @@ public void oneHeadNoBody() { public void oneColumnOneHeadNoBody() { String expected = "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody></tbody>\n" + "</table>\n"; @@ -94,10 +105,14 @@ public void oneColumnOneHeadNoBody() { public void oneColumnOneHeadOneBody() { String expected = "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"; assertRendering("|Abc\n|---\n|1", expected); @@ -106,25 +121,22 @@ public void oneColumnOneHeadOneBody() { // Pipe required on separator assertRendering("|Abc\n---\n|1", "<h2>|Abc</h2>\n<p>|1</p>\n"); - - // Pipe required on body - assertRendering("|Abc\n|---\n1\n", "<table>\n" + - "<thead>\n" + - "<tr><th>Abc</th></tr>\n" + - "</thead>\n" + - "<tbody></tbody>\n" + - "</table>\n" + - "<p>1</p>\n"); } @Test public void oneHeadOneBody() { assertRendering("Abc|Def\n---|---\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -138,10 +150,16 @@ public void separatorMustNotHaveLessPartsThanHead() { public void padding() { assertRendering(" Abc | Def \n --- | --- \n 1 | 2 ", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -150,10 +168,16 @@ public void padding() { public void paddingWithCodeBlockIndentation() { assertRendering("Abc|Def\n---|---\n 1|2", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -162,10 +186,16 @@ public void paddingWithCodeBlockIndentation() { public void pipesOnOutside() { assertRendering("|Abc|Def|\n|---|---|\n|1|2|", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -174,10 +204,16 @@ public void pipesOnOutside() { public void inlineElements() { assertRendering("*Abc*|Def\n---|---\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th><em>Abc</em></th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th><em>Abc</em></th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -186,10 +222,16 @@ public void inlineElements() { public void escapedPipe() { assertRendering("Abc|Def\n---|---\n1\\|2|20", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1|2</td><td>20</td></tr>\n" + + "<tr>\n" + + "<td>1|2</td>\n" + + "<td>20</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -198,10 +240,16 @@ public void escapedPipe() { public void escapedBackslash() { assertRendering("Abc|Def\n---|---\n1\\\\|2", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1\\</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1\\</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -210,26 +258,44 @@ public void escapedBackslash() { public void alignLeft() { assertRendering("Abc|Def\n:-|-\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"left\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"left\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"left\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"left\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); assertRendering("Abc|Def\n:-|-\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"left\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"left\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"left\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"left\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); assertRendering("Abc|Def\n:---|---\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"left\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"left\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"left\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"left\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -238,26 +304,44 @@ public void alignLeft() { public void alignRight() { assertRendering("Abc|Def\n-:|-\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"right\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"right\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"right\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"right\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); assertRendering("Abc|Def\n--:|--\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"right\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"right\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"right\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"right\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); assertRendering("Abc|Def\n---:|---\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"right\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"right\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"right\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"right\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -266,26 +350,44 @@ public void alignRight() { public void alignCenter() { assertRendering("Abc|Def\n:-:|-\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"center\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"center\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"center\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"center\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); assertRendering("Abc|Def\n:--:|--\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"center\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"center\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"center\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"center\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); assertRendering("Abc|Def\n:---:|---\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"center\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"center\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"center\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"center\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -294,10 +396,16 @@ public void alignCenter() { public void alignCenterSecond() { assertRendering("Abc|Def\n---|:---:\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th align=\"center\">Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th align=\"center\">Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td align=\"center\">2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td align=\"center\">2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -306,10 +414,16 @@ public void alignCenterSecond() { public void alignLeftWithSpaces() { assertRendering("Abc|Def\n :--- |---\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th align=\"left\">Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th align=\"left\">Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"left\">1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td align=\"left\">1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -326,10 +440,16 @@ public void alignmentMarkerMustBeNextToDashes() { public void bodyCanNotHaveMoreColumnsThanHead() { assertRendering("Abc|Def\n---|---\n1|2|3", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -338,10 +458,18 @@ public void bodyCanNotHaveMoreColumnsThanHead() { public void bodyWithFewerColumnsThanHeadResultsInEmptyCells() { assertRendering("Abc|Def|Ghi\n---|---|---\n1|2", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th><th>Ghi</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "<th>Ghi</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td><td></td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "<td></td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -351,26 +479,41 @@ public void insideBlockQuote() { assertRendering("> Abc|Def\n> ---|---\n> 1|2", "<blockquote>\n" + "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n" + "</blockquote>\n"); } @Test - public void tableEndWithoutEmptyLine() { - assertRendering("Abc|Def\n---|---\n1|2\ntable, you are over", "<table>\n" + + public void tableWithLazyContinuationLine() { + assertRendering("Abc|Def\n---|---\n1|2\nlazy", "<table>\n" + "<thead>\n" + - "<tr><th>Abc</th><th>Def</th></tr>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td>1</td><td>2</td></tr>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + + "<tr>\n" + + "<td>lazy</td>\n" + + "<td></td>\n" + + "</tr>\n" + "</tbody>\n" + - "</table>\n" + - "<p>table, you are over</p>\n"); + "</table>\n"); } @Test @@ -381,11 +524,23 @@ public void issue142() { "|**Tap**|ɾ|", "<table>\n" + "<thead>\n" + - "<tr><th align=\"left\"></th><th align=\"center\">Alveolar</th><th align=\"center\">Bilabial</th></tr>\n" + + "<tr>\n" + + "<th align=\"left\"></th>\n" + + "<th align=\"center\">Alveolar</th>\n" + + "<th align=\"center\">Bilabial</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody>\n" + - "<tr><td align=\"left\"><strong>Plosive</strong></td><td align=\"center\">t, d</td><td align=\"center\">b</td></tr>\n" + - "<tr><td align=\"left\"><strong>Tap</strong></td><td align=\"center\">ɾ</td><td align=\"center\"></td></tr>\n" + + "<tr>\n" + + "<td align=\"left\"><strong>Plosive</strong></td>\n" + + "<td align=\"center\">t, d</td>\n" + + "<td align=\"center\">b</td>\n" + + "</tr>\n" + + "<tr>\n" + + "<td align=\"left\"><strong>Tap</strong></td>\n" + + "<td align=\"center\">ɾ</td>\n" + + "<td align=\"center\"></td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n"); } @@ -420,10 +575,16 @@ public void setAttributes(Node node, String tagName, Map<String, String> attribu String rendered = renderer.render(PARSER.parse("Abc|Def\n---|---\n1|2")); assertThat(rendered, is("<table test=\"block\">\n" + "<thead test=\"head\">\n" + - "<tr test=\"row\"><th test=\"cell\">Abc</th><th test=\"cell\">Def</th></tr>\n" + + "<tr test=\"row\">\n" + + "<th test=\"cell\">Abc</th>\n" + + "<th test=\"cell\">Def</th>\n" + + "</tr>\n" + "</thead>\n" + "<tbody test=\"body\">\n" + - "<tr test=\"row\"><td test=\"cell\">1</td><td test=\"cell\">2</td></tr>\n" + + "<tr test=\"row\">\n" + + "<td test=\"cell\">1</td>\n" + + "<td test=\"cell\">2</td>\n" + + "</tr>\n" + "</tbody>\n" + "</table>\n")); } 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 c52bb348b..3059d4fd1 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTextContentTest.java @@ -1,14 +1,14 @@ package org.commonmark.ext.gfm.tables; -import java.util.Collections; -import java.util.Set; - import org.commonmark.Extension; import org.commonmark.parser.Parser; import org.commonmark.renderer.text.TextContentRenderer; 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<Extension> EXTENSIONS = Collections.singleton(TablesExtension.create()); @@ -42,9 +42,6 @@ public void oneColumnOneHeadOneBody() { // Pipe required on separator assertRendering("|Abc\n---\n|1", "|Abc\n|1"); - - // Pipe required on body - assertRendering("|Abc\n|---\n1\n", "Abc\n\n1"); } @Test @@ -136,8 +133,8 @@ public void insideBlockQuote() { } @Test - public void tableEndWithoutEmptyLine() { - assertRendering("Abc|Def\n---|---\n1|2\ntable, you are over", "Abc| Def\n1| 2\n\ntable, you are over"); + public void tableWithLazyContinuationLine() { + assertRendering("Abc|Def\n---|---\n1|2\nlazy", "Abc| Def\n1| 2\nlazy| \n"); } @Override From a2b24078955f5ee4e21e2d6e79e301f9d2bc9d31 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 13:21:57 +1000 Subject: [PATCH 353/815] ext-gfm-tables: Change how escaping works in tables according to spec Spec is not very thorough, so some things are according to the current implementation of GitHub. --- .../gfm/tables/internal/TableBlockParser.java | 37 +++++++-------- .../commonmark/ext/gfm/tables/TablesTest.java | 45 ++++++++++++++++++- .../ext/gfm/tables/TablesTextContentTest.java | 5 --- 3 files changed, 62 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 52f523ace..edfa479d4 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 @@ -117,26 +117,27 @@ private static List<String> split(CharSequence input) { } List<String> cells = new ArrayList<>(); StringBuilder sb = new StringBuilder(); - boolean escape = false; for (int i = 0; i < line.length(); i++) { char c = line.charAt(i); - if (escape) { - escape = false; - sb.append(c); - } else { - switch (c) { - case '\\': - escape = true; - // Removing the escaping '\' is handled by the inline parser later, so add it to cell - sb.append(c); - break; - case '|': - cells.add(sb.toString()); - sb.setLength(0); - break; - default: - sb.append(c); - } + switch (c) { + case '\\': + if (i + 1 < line.length() && line.charAt(i + 1) == '|') { + // Pipe is special for table parsing. An escaped pipe doesn't result in a new cell, but is + // passed down to inline parsing as an unescaped pipe. Note that that applies even for the `\|` + // in an input like `\\|` - in other words, table parsing doesn't support escaping backslashes. + sb.append('|'); + i++; + } else { + // Preserve backslash before other characters or at end of line. + sb.append('\\'); + } + break; + case '|': + cells.add(sb.toString()); + sb.setLength(0); + break; + default: + sb.append(c); } } if (sb.length() > 0) { 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 104b7d9ba..bb404aed9 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 @@ -238,6 +238,9 @@ public void escapedPipe() { @Test public void escapedBackslash() { + // This is a bit weird in the GFM spec IMO. `1\\|2` looks like an escaped backslash, followed by a pipe + // (so two cells). Instead, the `\|` is parsed as an escaped pipe first, so just a single cell. The inline + // parser then gets `1\|2` which renders as `1|2`. assertRendering("Abc|Def\n---|---\n1\\\\|2", "<table>\n" + "<thead>\n" + "<tr>\n" + @@ -247,8 +250,46 @@ public void escapedBackslash() { "</thead>\n" + "<tbody>\n" + "<tr>\n" + - "<td>1\\</td>\n" + - "<td>2</td>\n" + + "<td>1|2</td>\n" + + "<td></td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n"); + } + + @Test + public void escapedOther() { + // This is a tricky one. For \`, we don't want to remove the backslash when we parse the table, otherwise + // inline parsing is wrong. So we have to be careful where we do/don't consume the backslash. + assertRendering("Abc|Def\n---|---\n1|\\`not code`", "<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>`not code`</td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n"); + } + + @Test + public void backslashAtEnd() { + assertRendering("Abc|Def\n---|---\n1|2\\", "<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2\\</td>\n" + "</tr>\n" + "</tbody>\n" + "</table>\n"); 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 3059d4fd1..6d859f1c9 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 @@ -79,11 +79,6 @@ public void escapedPipe() { assertRendering("Abc|Def\n---|---\n1\\|2|20", "Abc| Def\n1|2| 20\n"); } - @Test - public void escapedBackslash() { - assertRendering("Abc|Def\n---|---\n1\\\\|2", "Abc| Def\n1\\| 2\n"); - } - @Test public void alignLeft() { assertRendering("Abc|Def\n:---|---\n1|2", "Abc| Def\n1| 2\n"); From eaddd200f7323a81016f7ec1103bd6d6b1014050 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 14:29:08 +1000 Subject: [PATCH 354/815] ext-gfm-tables: Don't render <tbody> if there's no rows --- .../ext/gfm/tables/internal/TableHtmlNodeRenderer.java | 4 ++++ .../test/java/org/commonmark/ext/gfm/tables/TablesTest.java | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) 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..5b163d464 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 @@ -35,6 +35,10 @@ protected void renderHead(TableHead tableHead) { } protected void renderBody(TableBody tableBody) { + // gfm-spec: If there are no rows in the body, no `<tbody>` is generated in HTML output + if (tableBody.getFirstChild() == null) { + return; + } htmlWriter.line(); htmlWriter.tag("tbody", getAttributes(tableBody, "tbody")); renderChildren(tableBody); 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 bb404aed9..f9e3d878b 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 @@ -38,7 +38,6 @@ public void separatorMustBeOneOrMore() { "<th>Def</th>\n" + "</tr>\n" + "</thead>\n" + - "<tbody></tbody>\n" + "</table>\n"); assertRendering("Abc|Def\n--|--", "<table>\n" + "<thead>\n" + @@ -47,7 +46,6 @@ public void separatorMustBeOneOrMore() { "<th>Def</th>\n" + "</tr>\n" + "</thead>\n" + - "<tbody></tbody>\n" + "</table>\n"); } @@ -77,7 +75,6 @@ public void oneHeadNoBody() { "<th>Def</th>\n" + "</tr>\n" + "</thead>\n" + - "<tbody></tbody>\n" + "</table>\n"); } @@ -89,7 +86,6 @@ public void oneColumnOneHeadNoBody() { "<th>Abc</th>\n" + "</tr>\n" + "</thead>\n" + - "<tbody></tbody>\n" + "</table>\n"; assertRendering("|Abc\n|---\n", expected); assertRendering("|Abc|\n|---|\n", expected); From 0f2d1439bf012311d4c2b6544aa20a3a80fc25a8 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 15:43:48 +1000 Subject: [PATCH 355/815] Make table parsing nicer * Get rid of regex * Don't parse header and separator multiple times --- .../gfm/tables/internal/TableBlockParser.java | 169 ++++++++++++------ .../internal/TableHtmlNodeRenderer.java | 4 - .../commonmark/ext/gfm/tables/TablesTest.java | 21 ++- 3 files changed, 134 insertions(+), 60 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index edfa479d4..112764ba0 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 @@ -8,25 +8,19 @@ import java.util.ArrayList; import java.util.List; -import java.util.regex.Pattern; public class TableBlockParser extends AbstractBlockParser { - private static String COL = "\\s*:?-{1,}:?\\s*"; - private static Pattern TABLE_HEADER_SEPARATOR = Pattern.compile( - // For single column, require at least one pipe, otherwise it's ambiguous with setext headers - "\\|" + COL + "\\|?\\s*" + "|" + - COL + "\\|\\s*" + "|" + - "\\|?" + "(?:" + COL + "\\|)+" + COL + "\\|?\\s*"); - private final TableBlock block = new TableBlock(); - private final List<CharSequence> rowLines = new ArrayList<>(); + private final List<CharSequence> bodyLines = new ArrayList<>(); + private final List<TableCell.Alignment> columns; + private final List<String> headerCells; private boolean nextIsSeparatorLine = true; - private String separatorLine = ""; - private TableBlockParser(CharSequence headerLine) { - rowLines.add(headerLine); + private TableBlockParser(List<TableCell.Alignment> columns, List<String> headerCells) { + this.columns = columns; + this.headerCells = headerCells; } @Override @@ -52,62 +46,58 @@ public BlockContinue tryContinue(ParserState state) { public void addLine(CharSequence line) { if (nextIsSeparatorLine) { nextIsSeparatorLine = false; - separatorLine = line.toString(); } else { - rowLines.add(line); + bodyLines.add(line); } } @Override public void parseInlines(InlineParser inlineParser) { - Node section = new TableHead(); - block.appendChild(section); - - List<TableCell.Alignment> alignments = parseAlignment(separatorLine); + int headerColumns = headerCells.size(); + + Node head = new TableHead(); + block.appendChild(head); + + TableRow headerRow = new TableRow(); + head.appendChild(headerRow); + for (int i = 0; i < headerColumns; i++) { + String cell = headerCells.get(i); + TableCell tableCell = parseCell(cell, i, inlineParser); + tableCell.setHeader(true); + headerRow.appendChild(tableCell); + } - int headerColumns = -1; - boolean header = true; - for (CharSequence rowLine : rowLines) { + Node body = null; + for (CharSequence rowLine : bodyLines) { List<String> cells = split(rowLine); - TableRow tableRow = new TableRow(); - - if (headerColumns == -1) { - headerColumns = cells.size(); - } + TableRow row = new TableRow(); // Body can not have more columns than head for (int i = 0; i < headerColumns; i++) { String cell = i < cells.size() ? cells.get(i) : ""; - TableCell.Alignment alignment = i < alignments.size() ? alignments.get(i) : null; - TableCell tableCell = new TableCell(); - tableCell.setHeader(header); - tableCell.setAlignment(alignment); - inlineParser.parse(cell.trim(), tableCell); - tableRow.appendChild(tableCell); + TableCell tableCell = parseCell(cell, i, inlineParser); + row.appendChild(tableCell); } - section.appendChild(tableRow); - - if (header) { - // Format allows only one row in head - header = false; - section = new TableBody(); - block.appendChild(section); + if (body == null) { + // It's valid to have a table without body. In that case, don't add an empty TableBody node. + body = new TableBody(); + block.appendChild(body); } + body.appendChild(row); } } - private static List<TableCell.Alignment> parseAlignment(String separatorLine) { - List<String> parts = split(separatorLine); - List<TableCell.Alignment> alignments = new ArrayList<>(); - for (String part : parts) { - String trimmed = part.trim(); - boolean left = trimmed.startsWith(":"); - boolean right = trimmed.endsWith(":"); - TableCell.Alignment alignment = getAlignment(left, right); - alignments.add(alignment); + private TableCell parseCell(String cell, int column, InlineParser inlineParser) { + TableCell tableCell = new TableCell(); + + if (column < columns.size()) { + tableCell.setAlignment(columns.get(column)); } - return alignments; + + inlineParser.parse(cell.trim(), tableCell); + + return tableCell; } private static List<String> split(CharSequence input) { @@ -146,6 +136,77 @@ private static List<String> split(CharSequence input) { return cells; } + // Examples of valid separators: + // + // |- + // -| + // |-| + // -|- + // |-|-| + // --- | --- + private static List<TableCell.Alignment> parseSeparator(CharSequence s) { + List<TableCell.Alignment> columns = new ArrayList<>(); + int pipes = 0; + boolean valid = false; + int i = 0; + while (i < s.length()) { + char c = s.charAt(i); + switch (c) { + case '|': + i++; + pipes++; + if (pipes > 1) { + // More than one adjacent pipe not allowed + return null; + } + // Need at lest one pipe, even for a one column table + valid = true; + break; + case '-': + case ':': + if (pipes == 0 && !columns.isEmpty()) { + // Need a pipe after the first column (first column doesn't need to start with one) + return null; + } + boolean left = false; + boolean right = false; + if (c == ':') { + left = true; + i++; + } + boolean haveDash = false; + while (i < s.length() && s.charAt(i) == '-') { + i++; + haveDash = true; + } + if (!haveDash) { + // Need at least one dash + return null; + } + if (i < s.length() && s.charAt(i) == ':') { + right = true; + i++; + } + columns.add(getAlignment(left, right)); + // Next, need another pipe + pipes = 0; + break; + case ' ': + case '\t': + // White space is allowed between pipes and columns + i++; + break; + default: + // Any other character is invalid + return null; + } + } + if (!valid) { + return null; + } + return columns; + } + private static TableCell.Alignment getAlignment(boolean left, boolean right) { if (left && right) { return TableCell.Alignment.CENTER; @@ -166,11 +227,11 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar CharSequence paragraph = matchedBlockParser.getParagraphContent(); if (paragraph != null && paragraph.toString().contains("|") && !paragraph.toString().contains("\n")) { CharSequence separatorLine = line.subSequence(state.getIndex(), line.length()); - if (TABLE_HEADER_SEPARATOR.matcher(separatorLine).matches()) { - List<String> headParts = split(paragraph); - List<String> separatorParts = split(separatorLine); - if (separatorParts.size() >= headParts.size()) { - return BlockStart.of(new TableBlockParser(paragraph)) + List<TableCell.Alignment> columns = parseSeparator(separatorLine); + if (columns != null && !columns.isEmpty()) { + List<String> headerCells = split(paragraph); + if (columns.size() >= headerCells.size()) { + return BlockStart.of(new TableBlockParser(columns, headerCells)) .atIndex(state.getIndex()) .replaceActiveBlockParser(); } 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 5b163d464..a1de50aac 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 @@ -35,10 +35,6 @@ protected void renderHead(TableHead tableHead) { } protected void renderBody(TableBody tableBody) { - // gfm-spec: If there are no rows in the body, no `<tbody>` is generated in HTML output - if (tableBody.getFirstChild() == null) { - return; - } htmlWriter.line(); htmlWriter.tag("tbody", getAttributes(tableBody, "tbody")); renderChildren(tableBody); 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 f9e3d878b..563ae8c18 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 @@ -57,8 +57,25 @@ public void separatorMustNotContainInvalidChars() { } @Test - public void separatorCanNotHaveLeadingSpaceThenPipe() { - assertRendering("Abc|Def\n |---|---", "<p>Abc|Def\n|---|---</p>\n"); + public void separatorCanHaveLeadingSpaceThenPipe() { + assertRendering("Abc|Def\n |---|---", "<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + + "</thead>\n" + + "</table>\n"); + } + + @Test + public void separatorCanNotHaveAdjacentPipes() { + assertRendering("Abc|Def\n---||---", "<p>Abc|Def\n---||---</p>\n"); + } + + @Test + public void separatorNeedsPipes() { + assertRendering("Abc|Def\n|--- ---", "<p>Abc|Def\n|--- ---</p>\n"); } @Test From 58fdd8fdf5e0020fffb79ec76abb2284568e3cea Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 16:11:36 +1000 Subject: [PATCH 356/815] Add CHANGELOG entry for tables extension changes --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57b73ef80..cf3cd17bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,13 @@ with the exception that 0.x versions can break between minor versions. ### Changed - Link reference definition parsing has been changed according to the spec: Definitions can now be in setext headings too. +- Tables extension: Changes to match GitHub implementation: + - Escaping now only considers pipe characters when parsing tables: + `\|` results in a literal `|` instead of a column, everything else + is passed through to inline parsing. + - Table body can now contain lazy continuation lines (without `|`). + An empty line or another block is needed to interrupt the table. + - For tables without a body, `<tbody>` is no longer rendered in HTML - Check non-null arguments early and provide a nicer message ## [0.12.1] - 2018-11-13 From 00619e1fd99fa61776b4fc32d335dfe000acadfc Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 16:15:24 +1000 Subject: [PATCH 357/815] Link to GFM spec --- CHANGELOG.md | 1 + .../org/commonmark/ext/gfm/tables/TablesExtension.java | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf3cd17bd..c4372e908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ with the exception that 0.x versions can break between minor versions. - Table body can now contain lazy continuation lines (without `|`). An empty line or another block is needed to interrupt the table. - For tables without a body, `<tbody>` is no longer rendered in HTML + - See https://github.github.com/gfm/#tables-extension- for details - Check non-null arguments early and provide a nicer message ## [0.12.1] - 2018-11-13 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 6ce6614e9..5707b0f14 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 @@ -4,11 +4,11 @@ import org.commonmark.ext.gfm.tables.internal.TableBlockParser; import org.commonmark.ext.gfm.tables.internal.TableHtmlNodeRenderer; import org.commonmark.ext.gfm.tables.internal.TableTextContentNodeRenderer; -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.text.TextContentNodeRendererContext; import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentRenderer; @@ -23,6 +23,8 @@ * <p> * The parsed tables are turned into {@link TableBlock} blocks. * </p> + * + * @see <a href="https://github.github.com/gfm/#tables-extension-">Tables (extension) in GitHub Flavored Markdown Spec</a> */ public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, TextContentRenderer.TextContentRendererExtension { From 78fa3308f4734a2804262303a6acc2c45687f60d Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 18:38:48 +1000 Subject: [PATCH 358/815] Prepare CHANGELOG for next version --- CHANGELOG.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4372e908..13b4ad9e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,15 @@ with the exception that 0.x versions can break between minor versions. - `InlineParserContext.getLinkReferenceDefinition` was added to allow custom inline parsers to look up definitions for reference links. ### Changed -- Link reference definition parsing has been changed according to the - spec: Definitions can now be in setext headings too. +- Update to CommonMark spec 0.29 (#156): + - Change how newlines/spaces are handled in inline code + - Info strings for tilde code blocks can contain backticks and tildes + - Allow spaces inside link destinations in pointy brackets + - Disallow link destination beginning with `<` unless it is inside `<..>` + - Disallow unescaped '(' in link title in parens + - Disallow indenting list item marker by more than 3 spaces + - No longer treat `<meta>` as a block tag + - Link reference definitions can now be in setext headings too - Tables extension: Changes to match GitHub implementation: - Escaping now only considers pipe characters when parsing tables: `\|` results in a literal `|` instead of a column, everything else @@ -24,6 +31,10 @@ with the exception that 0.x versions can break between minor versions. - For tables without a body, `<tbody>` is no longer rendered in HTML - See https://github.github.com/gfm/#tables-extension- for details - Check non-null arguments early and provide a nicer message +### Fixed +- Fix incorrectly preserving HTML entities when rendering attributes +- Fix pathological case with input `[\\\\...` (a lot of backslashes) +- Fix pathological case with input `[]([]([](...` ## [0.12.1] - 2018-11-13 ### Changed From 9968edce3f2bd9fba9842f9e5a1e82a3edd67541 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 18:44:07 +1000 Subject: [PATCH 359/815] Add benchmark results for next version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.12.1 (before): Benchmark Mode Cnt Score Error Units SpecBenchmark.parseAndRenderExamples thrpt 100 324.749 ± 1.165 ops/s SpecBenchmark.parseAndRenderWholeSpec thrpt 100 142.286 ± 0.778 ops/s SpecBenchmark.parseExamples thrpt 100 501.449 ± 2.308 ops/s SpecBenchmark.parseWholeSpec thrpt 100 274.185 ± 1.257 ops/s SpecBenchmark.renderWholeSpec thrpt 100 330.116 ± 1.746 ops/s 0.13.0 (after): Benchmark Mode Cnt Score Error Units SpecBenchmark.parseAndRenderExamples thrpt 100 372.100 ± 1.411 ops/s SpecBenchmark.parseAndRenderWholeSpec thrpt 100 194.657 ± 1.721 ops/s SpecBenchmark.parseExamples thrpt 100 556.327 ± 1.665 ops/s SpecBenchmark.parseWholeSpec thrpt 100 295.557 ± 1.870 ops/s SpecBenchmark.renderWholeSpec thrpt 100 680.565 ± 2.472 ops/s --- CHANGELOG.md | 3 +++ .../src/test/java/org/commonmark/test/SpecBenchmark.java | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13b4ad9e4..34244ec84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ with the exception that 0.x versions can break between minor versions. - `InlineParserContext.getLinkReferenceDefinition` was added to allow custom inline parsers to look up definitions for reference links. ### Changed +- Performance improvements compared to previous version: + - Parsing 7-10% faster + - HTML rendering 105% faster - or in other words, twice as fast! - Update to CommonMark spec 0.29 (#156): - Change how newlines/spaces are handled in inline code - Info strings for tilde code blocks can contain backticks and tildes diff --git a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java index 42692e498..99da7aa25 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecBenchmark.java @@ -25,6 +25,8 @@ public class SpecBenchmark { private static final Parser PARSER = Parser.builder().build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); + private static final Node SPEC_NODE = PARSER.parse(SPEC); + public static void main(String[] args) throws Exception { Options options = new OptionsBuilder() .parent(new CommandLineOptions(args)) @@ -53,6 +55,11 @@ public long parseAndRenderExamples() { return parseAndRender(SPEC_EXAMPLES); } + @Benchmark + public long renderWholeSpec() { + return RENDERER.render(SPEC_NODE).length(); + } + private static long parseAndRender(List<String> examples) { long length = 0; for (String example : examples) { From 021b1d20e733c7a18ded8ad23c3877948df297f7 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 15 Jul 2019 21:14:48 +1000 Subject: [PATCH 360/815] CHANGELOG: Prepare for next release --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34244ec84..fb191fe95 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.13.0] - 2019-07-15 ### Added - `LinkReferenceDefinition` nodes are now part of the document (not rendered by default). @@ -258,6 +258,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.13.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.12.1...commonmark-parent-0.13.0 [0.12.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.11.0...commonmark-parent-0.12.1 [0.11.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.10.0...commonmark-parent-0.11.0 [0.10.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.9.0...commonmark-parent-0.10.0 From f7716897f9e37f03d023cf713e92a5b1ced59bc1 Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Mon, 15 Jul 2019 11:19:33 +0000 Subject: [PATCH 361/815] [maven-release-plugin] prepare release commonmark-parent-0.13.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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index e1b43fa25..b5827e68d 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 58b34fd0a..8c97516d2 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 9fbe9942a..c02c6fe4b 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 1bd2e093e..f556090e3 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index cfd7e5d51..d34ec6423 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index dea897e23..c3629eb81 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 507dc818c..9480a0813 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index ea12bec0f..de297f997 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 980bf29ef..8769eafa4 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 1b15e2d82..d51070717 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.12.2-SNAPSHOT</version> + <version>0.13.0</version> </dependency> <!-- Common test dependencies --> @@ -195,7 +195,7 @@ <connection>scm:git:git@github.com:atlassian/commonmark-java.git</connection> <developerConnection>scm:git:git@github.com:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.13.0</tag> </scm> </project> From 82a76a5d9f2fd1b11eecc91927eff183c0280eff Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Mon, 15 Jul 2019 11:19:38 +0000 Subject: [PATCH 362/815] [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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index b5827e68d..878083750 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 8c97516d2..25532f28c 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index c02c6fe4b..7d6f41574 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index f556090e3..9a3d98f56 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index d34ec6423..597ea191e 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index c3629eb81..98cd0cdd5 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 9480a0813..c7c962a56 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index de297f997..cd396b621 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 8769eafa4..bd9490902 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index d51070717..81cf3d5c5 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.13.0</version> + <version>0.13.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -195,7 +195,7 @@ <connection>scm:git:git@github.com:atlassian/commonmark-java.git</connection> <developerConnection>scm:git:git@github.com:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>commonmark-parent-0.13.0</tag> + <tag>HEAD</tag> </scm> </project> From 249135df0ddaf003c550b07f7e41dfdcb1c5b489 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 16 Jul 2019 11:28:27 +1000 Subject: [PATCH 363/815] Bump versions to newest release --- README.md | 6 +++--- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e37011eab..1452b07e3 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.12.1</version> + <version>0.13.0</version> </dependency> ``` @@ -227,7 +227,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.12.1</version> + <version>0.13.0</version> </dependency> ``` @@ -328,7 +328,7 @@ See CONTRIBUTING.md file. License ------- -Copyright (c) 2015-2018 Atlassian and others. +Copyright (c) 2015-2019 Atlassian and others. BSD (2-clause) licensed, see LICENSE.txt file. diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index 8bfddd864..a9ac22416 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -8,7 +8,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.12.1" >> test.properties +echo "version.maven=0.13.0" >> test.properties echo "version.maven_autolink=0.10.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index aad925daf..7f8a16b5e 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.12.1 +version.maven=0.13.0 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.10.0 # Version number of commonmark and extensions in project. -version.snapshot=0.12.2-SNAPSHOT +version.snapshot=0.13.1-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.10.0 ``` From 334976b116a58cdeabdadfedbd9ff19a2d8fd382 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Tue, 16 Jul 2019 11:44:23 +1000 Subject: [PATCH 364/815] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 28 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..95ce00314 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Some input not render as you expect? Include an example so we can help +title: '' +labels: bug +assignees: '' + +--- + +Steps to reproduce the problem (provide example Markdown if applicable): + +``` +my markdown +``` + +Expected behavior: + +``` +expected HTML +``` + +Actual behavior: + +``` +actual HTML +``` + +(Also see what the reference implementation does: https://spec.commonmark.org/dingus/) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..02c83ef31 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. Include source code if possible. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context about the feature request here. From bf8bea9dfcb49ee43e20372b42b43727d9a15da2 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 16 Jul 2019 11:44:47 +1000 Subject: [PATCH 365/815] Delete issue_template.md Removing old issue template. --- .github/issue_template.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .github/issue_template.md diff --git a/.github/issue_template.md b/.github/issue_template.md deleted file mode 100644 index 24a01da04..000000000 --- a/.github/issue_template.md +++ /dev/null @@ -1,7 +0,0 @@ -Steps to reproduce the problem (provide example input): - - -Expected behavior: - - -Actual behavior: From 77c1463bf82c3ec9f8ad8aa17c8dd06501eed909 Mon Sep 17 00:00:00 2001 From: lehvolk <alexey.volkov@jetbrains.com> Date: Fri, 8 Nov 2019 10:57:29 +0300 Subject: [PATCH 366/815] Fix possible regexp with stackoverflow problem. --- .../commonmark/internal/InlineParserImpl.java | 44 ++++++++++--------- .../org/commonmark/test/SpecialInputTest.java | 5 +++ 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 38972cada..5f4e3fb64 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -184,7 +184,7 @@ private Text text(String text) { * On success, return the new inline node. * On failure, return null. */ - private Node parseInline(Node previous) { + protected Node parseInline(Node previous) { char c = peek(); if (c == '\0') { return null; @@ -243,17 +243,21 @@ private Node parseInline(Node previous) { /** * If RE matches at current index in the input, advance index and return the match; otherwise return null. */ - private String match(Pattern re) { + protected String match(Pattern re) { if (index >= input.length()) { return null; } - Matcher matcher = re.matcher(input); - matcher.region(index, input.length()); - boolean m = matcher.find(); - if (m) { - index = matcher.end(); - return matcher.group(); - } else { + try { + Matcher matcher = re.matcher(input); + matcher.region(index, input.length()); + boolean m = matcher.find(); + if (m) { + index = matcher.end(); + return matcher.group(); + } else { + return null; + } + } catch (Error e) { return null; } } @@ -279,7 +283,7 @@ private void spnl() { /** * Parse a newline. If it was preceded by two spaces, return a hard line break; otherwise a soft line break. */ - private Node parseNewline(Node previous) { + protected Node parseNewline(Node previous) { index++; // assume we're at a \n // Check previous text for trailing spaces. @@ -306,7 +310,7 @@ private Node parseNewline(Node previous) { * Parse a backslash-escaped special character, adding either the escaped character, a hard line break * (if the backslash is followed by a newline), or a literal backslash to the block's children. */ - private Node parseBackslash() { + protected Node parseBackslash() { index++; Node node; if (peek() == '\n') { @@ -324,7 +328,7 @@ private Node parseBackslash() { /** * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. */ - private Node parseBackticks() { + protected Node parseBackticks() { String ticks = match(TICKS_HERE); if (ticks == null) { return null; @@ -358,7 +362,7 @@ private Node parseBackticks() { /** * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ - private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + protected Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar); if (res == null) { return null; @@ -383,7 +387,7 @@ private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimit /** * Add open bracket to delimiter stack and add a text node to block's children. */ - private Node parseOpenBracket() { + protected Node parseOpenBracket() { int startIndex = index; index++; @@ -399,7 +403,7 @@ 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() { + protected Node parseBang() { int startIndex = index; index++; if (peek() == '[') { @@ -420,7 +424,7 @@ private Node parseBang() { * Try to match close bracket against an opening in the delimiter stack. Return either a link or image, or a * plain [ character. If there is a matching delimiter, remove it from the delimiter stack. */ - private Node parseCloseBracket() { + protected Node parseCloseBracket() { index++; int startIndex = index; @@ -603,7 +607,7 @@ int parseLinkLabel() { /** * Attempt to parse an autolink (URL or email in pointy brackets). */ - private Node parseAutolink() { + protected Node parseAutolink() { String m; if ((m = match(EMAIL_AUTOLINK)) != null) { String dest = m.substring(1, m.length() - 1); @@ -623,7 +627,7 @@ private Node parseAutolink() { /** * Attempt to parse inline HTML. */ - private Node parseHtmlInline() { + protected Node parseHtmlInline() { String m = match(HTML_TAG); if (m != null) { HtmlInline node = new HtmlInline(); @@ -637,7 +641,7 @@ private Node parseHtmlInline() { /** * Attempt to parse a HTML style entity. */ - private Node parseEntity() { + protected Node parseEntity() { String m; if ((m = match(ENTITY_HERE)) != null) { return text(Html5Entities.entityToString(m)); @@ -649,7 +653,7 @@ private Node parseEntity() { /** * Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string. */ - private Node parseString() { + protected Node parseString() { int begin = index; int length = input.length(); while (index != length) { diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index c2bb9fd4c..a70127a72 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -140,6 +140,11 @@ public void emphasisMultipleOf3Rule() { assertRendering("a***b* c*", "<p>a*<em><em>b</em> c</em></p>\n"); } + @Test + public void renderEvenRegexpProducesStackoverflow() { + render("Contents: <!--[if gte mso 9]> <w:LatentStyles DefLockedState=\"false\" DefUnhideWhenUsed=\"false\" DefSemiHidden=\"false\" DefQFormat=\"false\" DefPriority=\"99\" LatentStyleCount=\"371\"> <w:xxx Locked=\"false\" Priority=\"52\" Name=\"Grid Table 7 Colorful 6\"/> <w:xxx Locked=\"false\" Priority=\"46\" Name=\"List Table 1 Light\"/> <w:xxx Locked=\"false\" Priority=\"47\" Name=\"List Table 2\"/> <w:xxx Locked=\"false\" Priority=\"48\" Name=\"List Table 3\"/> <w:xxx Locked=\"false\" Priority=\"49\" Name=\"List Table 4\"/> <w:xxx Locked=\"false\" Priority=\"50\" Name=\"List Table 5 Dark\"/> <w:xxx Locked=\"false\" Priority=\"51\" Name=\"List Table 6 Colorful\"/> <w:xxx Locked=\"false\" Priority=\"52\" Name=\"List Table 7 Colorful\"/> <w:xxx Locked=\"false\" Priority=\"46\" Name=\"List Table 1 Light Accent 1\"/> <w:xxx Locked=\"false\" Priority=\"47\" Name=\"List Table 2 Accent 1\"/> <w:xxx Locked=\"false\" Priority=\"48\" Name=\"List Table 3 Accent 1\"/> <w:xxx Locked=\"false\" Priority=\"49\" Name=\"List Table 4 Accent 1\"/> <w:xxx Locked=\"false\" Priority=\"50\" Name=\"List Table 5 Dark Accent 1\"/> <w:xxx Locked=\"false\" Priority=\"52\" Name=\"List Table 7 Colorful Accent 1\"/> <w:xxx Locked=\"false\" Priority=\"46\" Name=\"List Table 1 Light Accent 2\"/> <w:xxx Locked=\"false\" Priority=\"47\" Name=\"List Table 2 Accent 2\"/> <w:xxx Locked=\"false\" Priority=\"48\" Name=\"List Table 3 Accent 2\"/> <w:xxx Locked=\"false\" Priority=\"49\" Name=\"List Table 4 Accent 2\"/> <w:xxx Locked=\"false\" Priority=\"50\" Name=\"List Table 5 Dark Accent 2\"/> <w:xxx Locked=\"false\" Priority=\"51\" Name=\"List Table 6 Colorful Accent 2\"/> <w:xxx Locked=\"false\" Priority=\"52\" Name=\"List Table 7 Colorful Accent 2\"/> <w:xxx Locked=\"false\" Priority=\"46\" Name=\"List Table 1 Light Accent 3\"/> <w:xxx Locked=\"false\" Priority=\"47\" Name=\"List Table 2 Accent 3\"/> <w:xxx Locked=\"false\" Priority=\"48\" Name=\"List Table 3 Accent 3\"/> <w:xxx Locked=\"false\" Priority=\"49\" Name=\"List Table 4 Accent 3\" /> <w:xxx Locked=\"false\" Priority=\"50\" Name=\"List Table 5 Dark Accent 3\"/><w:xxx Locked=\"false\" Priority=\"51\" Name=\"List Table 6 Colorful Accent 3\"/></xml>"); + } + @Test public void deeplyIndentedList() { assertRendering("* one\n" + From 3e0507d8d21b94f45f0cacdbc43dc3625080dd85 Mon Sep 17 00:00:00 2001 From: lehvolk <alexey.volkov@jetbrains.com> Date: Fri, 8 Nov 2019 14:44:51 +0300 Subject: [PATCH 367/815] fix review remarks --- .../commonmark/internal/InlineParserImpl.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 5f4e3fb64..dfb50149a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -184,7 +184,7 @@ private Text text(String text) { * On success, return the new inline node. * On failure, return null. */ - protected Node parseInline(Node previous) { + private Node parseInline(Node previous) { char c = peek(); if (c == '\0') { return null; @@ -243,7 +243,7 @@ protected Node parseInline(Node previous) { /** * If RE matches at current index in the input, advance index and return the match; otherwise return null. */ - protected String match(Pattern re) { + private String match(Pattern re) { if (index >= input.length()) { return null; } @@ -257,7 +257,7 @@ protected String match(Pattern re) { } else { return null; } - } catch (Error e) { + } catch (StackOverflowError e) { return null; } } @@ -283,7 +283,7 @@ private void spnl() { /** * Parse a newline. If it was preceded by two spaces, return a hard line break; otherwise a soft line break. */ - protected Node parseNewline(Node previous) { + private Node parseNewline(Node previous) { index++; // assume we're at a \n // Check previous text for trailing spaces. @@ -310,7 +310,7 @@ protected Node parseNewline(Node previous) { * Parse a backslash-escaped special character, adding either the escaped character, a hard line break * (if the backslash is followed by a newline), or a literal backslash to the block's children. */ - protected Node parseBackslash() { + private Node parseBackslash() { index++; Node node; if (peek() == '\n') { @@ -328,7 +328,7 @@ protected Node parseBackslash() { /** * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. */ - protected Node parseBackticks() { + private Node parseBackticks() { String ticks = match(TICKS_HERE); if (ticks == null) { return null; @@ -362,7 +362,7 @@ protected Node parseBackticks() { /** * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ - protected Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar); if (res == null) { return null; @@ -387,7 +387,7 @@ protected Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delim /** * Add open bracket to delimiter stack and add a text node to block's children. */ - protected Node parseOpenBracket() { + private Node parseOpenBracket() { int startIndex = index; index++; @@ -403,7 +403,7 @@ protected 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. */ - protected Node parseBang() { + private Node parseBang() { int startIndex = index; index++; if (peek() == '[') { @@ -424,7 +424,7 @@ protected Node parseBang() { * Try to match close bracket against an opening in the delimiter stack. Return either a link or image, or a * plain [ character. If there is a matching delimiter, remove it from the delimiter stack. */ - protected Node parseCloseBracket() { + private Node parseCloseBracket() { index++; int startIndex = index; @@ -607,7 +607,7 @@ int parseLinkLabel() { /** * Attempt to parse an autolink (URL or email in pointy brackets). */ - protected Node parseAutolink() { + private Node parseAutolink() { String m; if ((m = match(EMAIL_AUTOLINK)) != null) { String dest = m.substring(1, m.length() - 1); @@ -627,7 +627,7 @@ protected Node parseAutolink() { /** * Attempt to parse inline HTML. */ - protected Node parseHtmlInline() { + private Node parseHtmlInline() { String m = match(HTML_TAG); if (m != null) { HtmlInline node = new HtmlInline(); @@ -641,7 +641,7 @@ protected Node parseHtmlInline() { /** * Attempt to parse a HTML style entity. */ - protected Node parseEntity() { + private Node parseEntity() { String m; if ((m = match(ENTITY_HERE)) != null) { return text(Html5Entities.entityToString(m)); @@ -653,7 +653,7 @@ protected Node parseEntity() { /** * Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string. */ - protected Node parseString() { + private Node parseString() { int begin = index; int length = input.length(); while (index != length) { From 6602db4c68e837f2525193bd71d4315562969196 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 25 Nov 2019 16:30:00 +1100 Subject: [PATCH 368/815] Remove redundant null check --- .../java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9bb3225d0..3ffeba0cf 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -267,7 +267,7 @@ private boolean isInTightList(Paragraph paragraph) { Node parent = paragraph.getParent(); if (parent != null) { Node gramps = parent.getParent(); - if (gramps != null && gramps instanceof ListBlock) { + if (gramps instanceof ListBlock) { ListBlock list = (ListBlock) gramps; return list.isTight(); } From 326f466c79b99bc39b9787bcd79da46a90017d65 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 25 Nov 2019 16:31:18 +1100 Subject: [PATCH 369/815] Prepare CHANGELOG for 0.13.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb191fe95..f1a5d3fac 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. +## [0.13.1] - 2019-11-25 +### Fixed +- Fix potential `StackOverflowError` for regular expressions used in the + inline parser (e.g. when parsing long HTML), thanks @lehvolk + ## [0.13.0] - 2019-07-15 ### Added - `LinkReferenceDefinition` nodes are now part of the document (not @@ -258,6 +263,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.13.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.0...commonmark-parent-0.13.1 [0.13.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.12.1...commonmark-parent-0.13.0 [0.12.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.11.0...commonmark-parent-0.12.1 [0.11.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.10.0...commonmark-parent-0.11.0 From ec6e6d8b35f862924c694e30d2a958a4097de688 Mon Sep 17 00:00:00 2001 From: Bamboo Build User <deployment-bamboo-bot@atlassian.com> Date: Mon, 25 Nov 2019 05:35:52 +0000 Subject: [PATCH 370/815] [maven-release-plugin] prepare release commonmark-parent-0.13.1 --- 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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 878083750..b84afc8b2 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 25532f28c..09ce82cb7 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 7d6f41574..731f077e8 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 9a3d98f56..f446ccd83 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 597ea191e..4f97ab46b 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 98cd0cdd5..c0b7b7014 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index c7c962a56..a05a06cd6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index cd396b621..8b8e318cc 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index bd9490902..5c7d882ef 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 81cf3d5c5..53c5f4b25 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.13.1</version> </dependency> <!-- Common test dependencies --> @@ -195,7 +195,7 @@ <connection>scm:git:git@github.com:atlassian/commonmark-java.git</connection> <developerConnection>scm:git:git@github.com:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.13.1</tag> </scm> </project> From 98cf5d37a65746e9baacb6bb627aeb6f8db377be Mon Sep 17 00:00:00 2001 From: Bamboo Build User <deployment-bamboo-bot@atlassian.com> Date: Mon, 25 Nov 2019 05:35:57 +0000 Subject: [PATCH 371/815] [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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index b84afc8b2..f9890176d 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 09ce82cb7..06c845fc5 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 731f077e8..63e9d682a 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index f446ccd83..ebb917785 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 4f97ab46b..b9b7cc122 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index c0b7b7014..e8fae2a11 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index a05a06cd6..f5036f412 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 8b8e318cc..7b586a947 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 5c7d882ef..8f963b2c1 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 53c5f4b25..0012af0de 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.13.1</version> + <version>0.13.2-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -195,7 +195,7 @@ <connection>scm:git:git@github.com:atlassian/commonmark-java.git</connection> <developerConnection>scm:git:git@github.com:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>commonmark-parent-0.13.1</tag> + <tag>HEAD</tag> </scm> </project> From 27f487f06c9f2bc9dc79c4ee519bb93a1a97b76c Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 25 Nov 2019 17:19:36 +1100 Subject: [PATCH 372/815] Bump versions in docs --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1452b07e3..b9bf09198 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.13.0</version> + <version>0.13.1</version> </dependency> ``` @@ -227,7 +227,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.13.0</version> + <version>0.13.1</version> </dependency> ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index a9ac22416..cf6a74a9b 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -8,7 +8,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.13.0" >> test.properties +echo "version.maven=0.13.1" >> test.properties echo "version.maven_autolink=0.10.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 7f8a16b5e..35141426e 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,7 +28,7 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.13.0 +version.maven=0.13.1 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.10.0 From 8577bdee5afa95b08c6f16e6aff0afea22ab52d0 Mon Sep 17 00:00:00 2001 From: David Vandorpe <david.vandorpe2@gmail.com> Date: Tue, 14 Jan 2020 00:04:25 +0100 Subject: [PATCH 373/815] Add option for stripping unsafe URLs Fixes #155 --- .../renderer/html/CoreHtmlNodeRenderer.java | 12 ++- .../renderer/html/DefaultUrlSanitizer.java | 83 +++++++++++++++++++ .../html/HtmlNodeRendererContext.java | 14 ++++ .../renderer/html/HtmlRenderer.java | 42 ++++++++++ .../renderer/html/UrlSanitizer.java | 26 ++++++ .../org/commonmark/test/HtmlRendererTest.java | 41 +++++++++ 6 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java create mode 100644 commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java 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 3ffeba0cf..eb387cf58 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -141,7 +141,14 @@ public void visit(IndentedCodeBlock indentedCodeBlock) { @Override public void visit(Link link) { Map<String, String> attrs = new LinkedHashMap<>(); - String url = context.encodeUrl(link.getDestination()); + String url = link.getDestination(); + + if (context.shouldBeSafe()) { + url = context.urlSanitizer().sanitizeLinkUrl(url); + attrs.put("rel", "nofollow"); + } + + url = context.encodeUrl(url); attrs.put("href", url); if (link.getTitle() != null) { attrs.put("title", link.getTitle()); @@ -178,6 +185,9 @@ public void visit(Image image) { String altText = altTextVisitor.getAltText(); Map<String, String> attrs = new LinkedHashMap<>(); + if(context.shouldBeSafe()) { + url = context.urlSanitizer().sanitizeImageUrl(url); + } attrs.put("src", url); attrs.put("alt", altText); if (image.getTitle() != null) { diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java new file mode 100644 index 000000000..6cdd31214 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java @@ -0,0 +1,83 @@ +package org.commonmark.renderer.html; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + * Allows http, https and mailto protocols for url. + * Also allows protocol relative urls, and relative urls. + * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java + */ +public class DefaultUrlSanitizer implements UrlSanitizer { + private Collection<String> protocols; + + private static final long HTML_SPACE_CHAR_BITMASK = + (1L << ' ') + | (1L << '\t') + | (1L << '\n') + | (1L << '\u000c') + | (1L << '\r'); + + + public DefaultUrlSanitizer() { + this(new ArrayList<String>() {{ + add("http"); + add("https"); + add("mailto"); + }}); + } + + public DefaultUrlSanitizer(Collection<String> protocols) { + this.protocols = protocols; + } + + @Override + public String sanitizeLinkUrl(String url) { + url = stripHtmlSpaces(url); + protocol_loop: + for (int i = 0, n = url.length(); i < n; ++i) { + switch (url.charAt(i)) { + case '/': + case '#': + case '?': // No protocol. + break protocol_loop; + case ':': + String protocol = url.substring(0, i).toLowerCase(); + if (!protocols.contains(protocol)) { + return ""; + } + break protocol_loop; + } + } + return url; + } + + + @Override + public String sanitizeImageUrl(String url) { + return sanitizeLinkUrl(url); + } + + private String stripHtmlSpaces(String s) { + int i = 0, n = s.length(); + for (; n > i; --n) { + if (!isHtmlSpace(s.charAt(n - 1))) { + break; + } + } + for (; i < n; ++i) { + if (!isHtmlSpace(s.charAt(i))) { + break; + } + } + if (i == 0 && n == s.length()) { + return s; + } + return s.substring(i, n); + } + + private boolean isHtmlSpace(int ch) { + return ch <= 0x20 && (HTML_SPACE_CHAR_BITMASK & (1L << ch)) != 0; + } +} 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 fd077a277..6f694fb7c 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java @@ -1,5 +1,7 @@ package org.commonmark.renderer.html; +import org.commonmark.node.Link; +import org.commonmark.node.Image; import org.commonmark.node.Node; import java.util.Map; @@ -44,4 +46,16 @@ public interface HtmlNodeRendererContext { * @return whether HTML blocks and tags should be escaped or not */ boolean shouldEscapeHtml(); + + /** + * + * @return true if the {@link UrlSanitizer} should be used. + */ + boolean shouldBeSafe(); + + /** + * + * @return Sanitizer to use for securing {@link Link} href and {@link Image} src if safe is true. + */ + UrlSanitizer urlSanitizer(); } 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 fac3b3ba2..f1cac8795 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -5,6 +5,8 @@ import org.commonmark.internal.util.Escaping; import org.commonmark.node.HtmlBlock; import org.commonmark.node.HtmlInline; +import org.commonmark.node.Link; +import org.commonmark.node.Image; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.Renderer; @@ -27,6 +29,8 @@ public class HtmlRenderer implements Renderer { private final String softbreak; private final boolean escapeHtml; + private final boolean safe; + private final UrlSanitizer urlSanitizer; private final boolean percentEncodeUrls; private final List<AttributeProviderFactory> attributeProviderFactories; private final List<HtmlNodeRendererFactory> nodeRendererFactories; @@ -34,7 +38,9 @@ public class HtmlRenderer implements Renderer { private HtmlRenderer(Builder builder) { this.softbreak = builder.softbreak; this.escapeHtml = builder.escapeHtml; + this.safe = builder.safe; this.percentEncodeUrls = builder.percentEncodeUrls; + this.urlSanitizer = builder.urlSanitizer; this.attributeProviderFactories = new ArrayList<>(builder.attributeProviderFactories); this.nodeRendererFactories = new ArrayList<>(builder.nodeRendererFactories.size() + 1); @@ -83,6 +89,8 @@ public static class Builder { private String softbreak = "\n"; private boolean escapeHtml = false; + private boolean safe = false; + private UrlSanitizer urlSanitizer = new DefaultUrlSanitizer(); private boolean percentEncodeUrls = false; private List<AttributeProviderFactory> attributeProviderFactories = new ArrayList<>(); private List<HtmlNodeRendererFactory> nodeRendererFactories = new ArrayList<>(); @@ -124,6 +132,30 @@ public Builder escapeHtml(boolean escapeHtml) { return this; } + /** + * Whether {@link Image} src and {@link Link} href should be filtered, defaults to {@code false}. + * <p> + * + * @param safe true for filtering, false for preserving raw attribute + * @return {@code this} + */ + public Builder safe(boolean safe) { + this.safe = safe; + return this; + } + + /** + * {@link UrlSanitizer} used to filter URL's if safe is true. + * <p> + * + * @param urlSanitizer Filterer used to filter {@link Image} src and {@link Link}. + * @return {@code this} + */ + public Builder urlSanitizer(UrlSanitizer urlSanitizer) { + this.urlSanitizer = urlSanitizer; + return this; + } + /** * Whether URLs of link or images should be percent-encoded, defaults to {@code false}. * <p> @@ -227,6 +259,16 @@ public boolean shouldEscapeHtml() { return escapeHtml; } + @Override + public boolean shouldBeSafe() { + return safe; + } + + @Override + public UrlSanitizer urlSanitizer() { + return urlSanitizer; + } + @Override public String encodeUrl(String url) { if (percentEncodeUrls) { diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java b/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java new file mode 100644 index 000000000..6ad93e41d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java @@ -0,0 +1,26 @@ +package org.commonmark.renderer.html; + +import org.commonmark.node.Image; +import org.commonmark.node.Link; + +/** + * Sanitizes urls for img and a elements by whitelisting protocols. + * This is intended to prevent XSS payloads like [Click this totally safe url](javascript:document.xss=true;) + * + * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java + */ +public interface UrlSanitizer { + /** + * Sanitize a url for use in the href attribute of a {@link Link}. + * @param url Link to sanitize + * @return Sanitized link + */ + public String sanitizeLinkUrl(String url); + + /** + * Sanitize a url for use in the src attribute of a {@link Image}. + * @param url Link to sanitize + * @return Sanitized link {@link Image} + */ + public String sanitizeImageUrl(String url); +} diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 30cbf24f3..4ba1554df 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -59,6 +59,39 @@ public void attributeEscaping() { assertEquals("<p><a href=\"&colon;\"></a></p>\n", defaultRenderer().render(paragraph)); } + @Test + public void unsafeShouldNotEscapeDangerousProtocols() { + Paragraph paragraph = new Paragraph(); + Link link = new Link(); + link.setDestination("javascript:alert(5);"); + paragraph.appendChild(link); + assertEquals("<p><a href=\"javascript:alert(5);\"></a></p>\n", unsafeRenderer().render(paragraph)); + } + + @Test + public void safeShouldSetRelNoFollow() { + Paragraph paragraph = new Paragraph(); + Link link = new Link(); + link.setDestination("/exampleUrl"); + paragraph.appendChild(link); + assertEquals("<p><a rel=\"nofollow\" href=\"/exampleUrl\"></a></p>\n", safeRenderer().render(paragraph)); + + paragraph = new Paragraph(); + link = new Link(); + link.setDestination("https://google.com"); + paragraph.appendChild(link); + assertEquals("<p><a rel=\"nofollow\" href=\"https://google.com\"></a></p>\n", safeRenderer().render(paragraph)); + } + + @Test + public void safeShouldEscapeDangerousProtocols() { + Paragraph paragraph = new Paragraph(); + Link link = new Link(); + link.setDestination("javascript:alert(5);"); + paragraph.appendChild(link); + assertEquals("<p><a rel=\"nofollow\" href=\"\"></a></p>\n", safeRenderer().render(paragraph)); + } + @Test public void percentEncodeUrlDisabled() { assertEquals("<p><a href=\"foo&bar\">a</a></p>\n", defaultRenderer().render(parse("[a](foo&bar)"))); @@ -267,6 +300,14 @@ private static HtmlRenderer htmlAllowingRenderer() { return HtmlRenderer.builder().escapeHtml(false).build(); } + private static HtmlRenderer safeRenderer() { + return HtmlRenderer.builder().safe(true).urlSanitizer(new DefaultUrlSanitizer()).build(); + } + + private static HtmlRenderer unsafeRenderer() { + return HtmlRenderer.builder().safe(false).build(); + } + private static HtmlRenderer htmlEscapingRenderer() { return HtmlRenderer.builder().escapeHtml(true).build(); } From 6ac90912b715cf7169a5b713fcffa63ae9bf3fe2 Mon Sep 17 00:00:00 2001 From: David Vandorpe <David.Vandorpe@UGent.be> Date: Tue, 14 Jan 2020 10:07:46 +0100 Subject: [PATCH 374/815] Fix review remarks, part 1 --- .../renderer/html/CoreHtmlNodeRenderer.java | 2 +- .../renderer/html/DefaultUrlSanitizer.java | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 10 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 eb387cf58..3e0fba571 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -185,7 +185,7 @@ public void visit(Image image) { String altText = altTextVisitor.getAltText(); Map<String, String> attrs = new LinkedHashMap<>(); - if(context.shouldBeSafe()) { + if (context.shouldBeSafe()) { url = context.urlSanitizer().sanitizeImageUrl(url); } attrs.put("src", url); 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 6cdd31214..6ada69b49 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java @@ -1,7 +1,9 @@ package org.commonmark.renderer.html; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; /** * @@ -10,7 +12,7 @@ * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java */ public class DefaultUrlSanitizer implements UrlSanitizer { - private Collection<String> protocols; + private Set<String> protocols; private static final long HTML_SPACE_CHAR_BITMASK = (1L << ' ') @@ -21,15 +23,11 @@ public class DefaultUrlSanitizer implements UrlSanitizer { public DefaultUrlSanitizer() { - this(new ArrayList<String>() {{ - add("http"); - add("https"); - add("mailto"); - }}); + this(Arrays.asList("http", "https", "mailto")); } public DefaultUrlSanitizer(Collection<String> protocols) { - this.protocols = protocols; + this.protocols = new HashSet<>(protocols); } @Override @@ -78,6 +76,16 @@ private String stripHtmlSpaces(String s) { } private boolean isHtmlSpace(int ch) { - return ch <= 0x20 && (HTML_SPACE_CHAR_BITMASK & (1L << ch)) != 0; + switch (ch) { + case ' ': + case '\t': + case '\n': + case '\u000c': + case '\r': + return true; + default: + return false; + + } } } From 1b159cce2a829982fdbf16a64d01f7e930ace63f Mon Sep 17 00:00:00 2001 From: David Vandorpe <David.Vandorpe@UGent.be> Date: Tue, 14 Jan 2020 10:28:38 +0100 Subject: [PATCH 375/815] Fix review remark, part 2 --- .../renderer/html/CoreHtmlNodeRenderer.java | 4 ++-- .../html/HtmlNodeRendererContext.java | 4 ++-- .../renderer/html/HtmlRenderer.java | 20 ++++++++--------- .../org/commonmark/test/HtmlRendererTest.java | 22 +++++++++---------- 4 files changed, 25 insertions(+), 25 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 3e0fba571..4a1ab6e5a 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -143,7 +143,7 @@ public void visit(Link link) { Map<String, String> attrs = new LinkedHashMap<>(); String url = link.getDestination(); - if (context.shouldBeSafe()) { + if (context.shouldSanitizeUrls()) { url = context.urlSanitizer().sanitizeLinkUrl(url); attrs.put("rel", "nofollow"); } @@ -185,7 +185,7 @@ public void visit(Image image) { String altText = altTextVisitor.getAltText(); Map<String, String> attrs = new LinkedHashMap<>(); - if (context.shouldBeSafe()) { + if (context.shouldSanitizeUrls()) { url = context.urlSanitizer().sanitizeImageUrl(url); } attrs.put("src", url); 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 6f694fb7c..653f189be 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java @@ -51,11 +51,11 @@ public interface HtmlNodeRendererContext { * * @return true if the {@link UrlSanitizer} should be used. */ - boolean shouldBeSafe(); + boolean shouldSanitizeUrls(); /** * - * @return Sanitizer to use for securing {@link Link} href and {@link Image} src if safe is true. + * @return Sanitizer to use for securing {@link Link} href and {@link Image} src if sanitizeUrls is true. */ UrlSanitizer urlSanitizer(); } 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 f1cac8795..e410ae32d 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -29,7 +29,7 @@ public class HtmlRenderer implements Renderer { private final String softbreak; private final boolean escapeHtml; - private final boolean safe; + private final boolean sanitizeUrls; private final UrlSanitizer urlSanitizer; private final boolean percentEncodeUrls; private final List<AttributeProviderFactory> attributeProviderFactories; @@ -38,7 +38,7 @@ public class HtmlRenderer implements Renderer { private HtmlRenderer(Builder builder) { this.softbreak = builder.softbreak; this.escapeHtml = builder.escapeHtml; - this.safe = builder.safe; + this.sanitizeUrls = builder.sanitizeUrls; this.percentEncodeUrls = builder.percentEncodeUrls; this.urlSanitizer = builder.urlSanitizer; this.attributeProviderFactories = new ArrayList<>(builder.attributeProviderFactories); @@ -89,7 +89,7 @@ public static class Builder { private String softbreak = "\n"; private boolean escapeHtml = false; - private boolean safe = false; + private boolean sanitizeUrls = false; private UrlSanitizer urlSanitizer = new DefaultUrlSanitizer(); private boolean percentEncodeUrls = false; private List<AttributeProviderFactory> attributeProviderFactories = new ArrayList<>(); @@ -133,19 +133,19 @@ public Builder escapeHtml(boolean escapeHtml) { } /** - * Whether {@link Image} src and {@link Link} href should be filtered, defaults to {@code false}. + * Whether {@link Image} src and {@link Link} href should be sanitized, defaults to {@code false}. * <p> * - * @param safe true for filtering, false for preserving raw attribute + * @param sanitizeUrls true for sanitization, false for preserving raw attribute * @return {@code this} */ - public Builder safe(boolean safe) { - this.safe = safe; + public Builder sanitizeUrls(boolean sanitizeUrls) { + this.sanitizeUrls = sanitizeUrls; return this; } /** - * {@link UrlSanitizer} used to filter URL's if safe is true. + * {@link UrlSanitizer} used to filter URL's if sanitizeUrls is true. * <p> * * @param urlSanitizer Filterer used to filter {@link Image} src and {@link Link}. @@ -260,8 +260,8 @@ public boolean shouldEscapeHtml() { } @Override - public boolean shouldBeSafe() { - return safe; + public boolean shouldSanitizeUrls() { + return sanitizeUrls; } @Override diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 4ba1554df..04b493cba 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -60,36 +60,36 @@ public void attributeEscaping() { } @Test - public void unsafeShouldNotEscapeDangerousProtocols() { + public void rawUrlsShouldNotFilterDangerousProtocols() { Paragraph paragraph = new Paragraph(); Link link = new Link(); link.setDestination("javascript:alert(5);"); paragraph.appendChild(link); - assertEquals("<p><a href=\"javascript:alert(5);\"></a></p>\n", unsafeRenderer().render(paragraph)); + assertEquals("<p><a href=\"javascript:alert(5);\"></a></p>\n", rawUrlsRenderer().render(paragraph)); } @Test - public void safeShouldSetRelNoFollow() { + public void sanitizedUrlsShouldSetRelNoFollow() { Paragraph paragraph = new Paragraph(); Link link = new Link(); link.setDestination("/exampleUrl"); paragraph.appendChild(link); - assertEquals("<p><a rel=\"nofollow\" href=\"/exampleUrl\"></a></p>\n", safeRenderer().render(paragraph)); + assertEquals("<p><a rel=\"nofollow\" href=\"/exampleUrl\"></a></p>\n", sanitizeUrlsRenderer().render(paragraph)); paragraph = new Paragraph(); link = new Link(); link.setDestination("https://google.com"); paragraph.appendChild(link); - assertEquals("<p><a rel=\"nofollow\" href=\"https://google.com\"></a></p>\n", safeRenderer().render(paragraph)); + assertEquals("<p><a rel=\"nofollow\" href=\"https://google.com\"></a></p>\n", sanitizeUrlsRenderer().render(paragraph)); } @Test - public void safeShouldEscapeDangerousProtocols() { + public void sanitizedUrlsShouldFilterDangerousProtocols() { Paragraph paragraph = new Paragraph(); Link link = new Link(); link.setDestination("javascript:alert(5);"); paragraph.appendChild(link); - assertEquals("<p><a rel=\"nofollow\" href=\"\"></a></p>\n", safeRenderer().render(paragraph)); + assertEquals("<p><a rel=\"nofollow\" href=\"\"></a></p>\n", sanitizeUrlsRenderer().render(paragraph)); } @Test @@ -300,12 +300,12 @@ private static HtmlRenderer htmlAllowingRenderer() { return HtmlRenderer.builder().escapeHtml(false).build(); } - private static HtmlRenderer safeRenderer() { - return HtmlRenderer.builder().safe(true).urlSanitizer(new DefaultUrlSanitizer()).build(); + private static HtmlRenderer sanitizeUrlsRenderer() { + return HtmlRenderer.builder().sanitizeUrls(true).urlSanitizer(new DefaultUrlSanitizer()).build(); } - private static HtmlRenderer unsafeRenderer() { - return HtmlRenderer.builder().safe(false).build(); + private static HtmlRenderer rawUrlsRenderer() { + return HtmlRenderer.builder().sanitizeUrls(false).build(); } private static HtmlRenderer htmlEscapingRenderer() { From 6b7e42718211d76b324ae61b6bb888bc536ff130 Mon Sep 17 00:00:00 2001 From: David Vandorpe <David.Vandorpe@UGent.be> Date: Tue, 14 Jan 2020 10:29:43 +0100 Subject: [PATCH 376/815] Remove redundant field --- .../org/commonmark/renderer/html/DefaultUrlSanitizer.java | 8 -------- 1 file changed, 8 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 6ada69b49..6cc96c5e7 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/DefaultUrlSanitizer.java @@ -14,14 +14,6 @@ public class DefaultUrlSanitizer implements UrlSanitizer { private Set<String> protocols; - private static final long HTML_SPACE_CHAR_BITMASK = - (1L << ' ') - | (1L << '\t') - | (1L << '\n') - | (1L << '\u000c') - | (1L << '\r'); - - public DefaultUrlSanitizer() { this(Arrays.asList("http", "https", "mailto")); } From cf3bd456b1f5cf5d55a3dde56325c04692552b1c Mon Sep 17 00:00:00 2001 From: David Vandorpe <David.Vandorpe@UGent.be> Date: Wed, 15 Jan 2020 10:16:12 +0100 Subject: [PATCH 377/815] Encode after sanitization --- .../org/commonmark/renderer/html/CoreHtmlNodeRenderer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 4a1ab6e5a..7d3552668 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/CoreHtmlNodeRenderer.java @@ -178,7 +178,7 @@ public void visit(OrderedList orderedList) { @Override public void visit(Image image) { - String url = context.encodeUrl(image.getDestination()); + String url = image.getDestination(); AltTextVisitor altTextVisitor = new AltTextVisitor(); image.accept(altTextVisitor); @@ -188,7 +188,8 @@ public void visit(Image image) { if (context.shouldSanitizeUrls()) { url = context.urlSanitizer().sanitizeImageUrl(url); } - attrs.put("src", url); + + attrs.put("src", context.encodeUrl(url)); attrs.put("alt", altText); if (image.getTitle() != null) { attrs.put("title", image.getTitle()); From a65c78125656e1dc6b75db76d27b006a64386631 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 20 Jan 2020 14:20:04 +1100 Subject: [PATCH 378/815] README: Add note about sanitizeUrls --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b9bf09198..97a2c3184 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,17 @@ renderer.render(document); // "<p>This is <em>Sparta</em></p>\n" ``` This uses the parser and renderer with default options. Both builders have -methods for configuring their behavior, e.g. calling `escapeHtml(true)` on -`HtmlRenderer` will escape raw HTML tags and blocks. For all available -options, see methods on the builders. +methods for configuring their behavior: -Note that this library doesn't try to sanitize the resulting HTML; that is -the responsibility of the caller. +* `escapeHtml(true)` on `HtmlRenderer` will escape raw HTML tags and blocks. +* `sanitizeUrls(true)` on `HtmlRenderer` will strip potentially unsafe URLs + from `<a>` and `<img>` tags +* For all available options, see methods on the builders. + +Note that this library doesn't try to sanitize the resulting HTML with regards +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. From 249e751c160d755d3bdc598de6287bedf21f45ed Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 22 Jan 2020 11:26:32 +1100 Subject: [PATCH 379/815] Prepare for 0.14.0 release --- CHANGELOG.md | 7 +++++++ .../renderer/html/HtmlNodeRendererContext.java | 8 ++++---- .../org/commonmark/renderer/html/HtmlRenderer.java | 12 ++++-------- .../org/commonmark/renderer/html/UrlSanitizer.java | 10 +++++++--- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a5d3fac..acd7c044a 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. +## [0.14.0] - 2020-01-22 +### Added +- Add `sanitizeUrls` to `HtmlRenderer.Builder` to enable sanitizing URLs + of `<a>` and `<img>` tags. Sanitizing logic can be customized via + `urlSanitizer`. Thanks @VandorpeDavid + ## [0.13.1] - 2019-11-25 ### Fixed - Fix potential `StackOverflowError` for regular expressions used in the @@ -263,6 +269,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.14.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.1...commonmark-parent-0.14.0 [0.13.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.0...commonmark-parent-0.13.1 [0.13.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.12.1...commonmark-parent-0.13.0 [0.12.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.11.0...commonmark-parent-0.12.1 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 653f189be..eb950ffa6 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlNodeRendererContext.java @@ -1,7 +1,7 @@ package org.commonmark.renderer.html; -import org.commonmark.node.Link; import org.commonmark.node.Image; +import org.commonmark.node.Link; import org.commonmark.node.Node; import java.util.Map; @@ -48,14 +48,14 @@ public interface HtmlNodeRendererContext { boolean shouldEscapeHtml(); /** - * * @return true if the {@link UrlSanitizer} should be used. + * @since 0.14.0 */ boolean shouldSanitizeUrls(); /** - * - * @return Sanitizer to use for securing {@link Link} href and {@link Image} src if sanitizeUrls is true. + * @return Sanitizer to use for securing {@link Link} href and {@link Image} src if {@link #shouldSanitizeUrls()} is true. + * @since 0.14.0 */ UrlSanitizer urlSanitizer(); } diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java index e410ae32d..19f53594f 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/HtmlRenderer.java @@ -3,11 +3,7 @@ import org.commonmark.Extension; import org.commonmark.internal.renderer.NodeRendererMap; import org.commonmark.internal.util.Escaping; -import org.commonmark.node.HtmlBlock; -import org.commonmark.node.HtmlInline; -import org.commonmark.node.Link; -import org.commonmark.node.Image; -import org.commonmark.node.Node; +import org.commonmark.node.*; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.Renderer; @@ -134,10 +130,10 @@ public Builder escapeHtml(boolean escapeHtml) { /** * Whether {@link Image} src and {@link Link} href should be sanitized, defaults to {@code false}. - * <p> * * @param sanitizeUrls true for sanitization, false for preserving raw attribute * @return {@code this} + * @since 0.14.0 */ public Builder sanitizeUrls(boolean sanitizeUrls) { this.sanitizeUrls = sanitizeUrls; @@ -145,11 +141,11 @@ public Builder sanitizeUrls(boolean sanitizeUrls) { } /** - * {@link UrlSanitizer} used to filter URL's if sanitizeUrls is true. - * <p> + * {@link UrlSanitizer} used to filter URL's if {@link #sanitizeUrls} is true. * * @param urlSanitizer Filterer used to filter {@link Image} src and {@link Link}. * @return {@code this} + * @since 0.14.0 */ public Builder urlSanitizer(UrlSanitizer urlSanitizer) { this.urlSanitizer = urlSanitizer; diff --git a/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java b/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java index 6ad93e41d..fb48ca361 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/html/UrlSanitizer.java @@ -6,21 +6,25 @@ /** * Sanitizes urls for img and a elements by whitelisting protocols. * This is intended to prevent XSS payloads like [Click this totally safe url](javascript:document.xss=true;) - * + * <p> * Implementation based on https://github.com/OWASP/java-html-sanitizer/blob/f07e44b034a45d94d6fd010279073c38b6933072/src/main/java/org/owasp/html/FilterUrlByProtocolAttributePolicy.java + * + * @since 0.14.0 */ public interface UrlSanitizer { /** * Sanitize a url for use in the href attribute of a {@link Link}. + * * @param url Link to sanitize * @return Sanitized link */ - public String sanitizeLinkUrl(String url); + String sanitizeLinkUrl(String url); /** * Sanitize a url for use in the src attribute of a {@link Image}. + * * @param url Link to sanitize * @return Sanitized link {@link Image} */ - public String sanitizeImageUrl(String url); + String sanitizeImageUrl(String url); } From a62e9cce7109a9bc9a59763caba52c6415bb7031 Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Wed, 22 Jan 2020 00:34:20 +0000 Subject: [PATCH 380/815] [maven-release-plugin] prepare release commonmark-parent-0.14.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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index f9890176d..85ad69f52 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 06c845fc5..3a8dc7e3b 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 63e9d682a..9c14b0cdd 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index ebb917785..1fb33c9e0 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index b9b7cc122..48a74caa6 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e8fae2a11..c45c272da 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index f5036f412..77e75b97e 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 7b586a947..fe5742b63 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 8f963b2c1..26ed103d0 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 0012af0de..82d1f2a00 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.13.2-SNAPSHOT</version> + <version>0.14.0</version> </dependency> <!-- Common test dependencies --> @@ -195,7 +195,7 @@ <connection>scm:git:git@github.com:atlassian/commonmark-java.git</connection> <developerConnection>scm:git:git@github.com:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.14.0</tag> </scm> </project> From bc297257c3594e02937d3239982b3f9867ddfc89 Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Wed, 22 Jan 2020 00:34:27 +0000 Subject: [PATCH 381/815] [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-ins/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 | 20 ++++++++++---------- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 85ad69f52..80dd347f1 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 3a8dc7e3b..3df0581e6 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 9c14b0cdd..ad69f1795 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 1fb33c9e0..ca797b7be 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 48a74caa6..6f8c59318 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index c45c272da..d4534ec55 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 77e75b97e..6f2bdf0fe 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index fe5742b63..efb27f330 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 26ed103d0..4850b2b45 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 82d1f2a00..ae01c90f1 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -87,42 +87,42 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.14.0</version> + <version>0.14.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -195,7 +195,7 @@ <connection>scm:git:git@github.com:atlassian/commonmark-java.git</connection> <developerConnection>scm:git:git@github.com:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>commonmark-parent-0.14.0</tag> + <tag>HEAD</tag> </scm> </project> From 6999ad7c0ed1642d4562dc981535ac50eb07a8b0 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 22 Jan 2020 11:41:27 +1100 Subject: [PATCH 382/815] Bump versions in docs --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 97a2c3184..1ebe736a5 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.13.1</version> + <version>0.14.0</version> </dependency> ``` @@ -232,7 +232,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.13.1</version> + <version>0.14.0</version> </dependency> ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index cf6a74a9b..781568ce1 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -8,7 +8,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.13.1" >> test.properties +echo "version.maven=0.14.0" >> test.properties echo "version.maven_autolink=0.10.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 35141426e..452556a63 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.13.1 +version.maven=0.14.0 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.10.0 # Version number of commonmark and extensions in project. -version.snapshot=0.13.1-SNAPSHOT +version.snapshot=0.14.1-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.10.0 ``` From 6da6d8ff122d88f66a09a6c312ac77a35ef12ede Mon Sep 17 00:00:00 2001 From: "Sean C. Sullivan" <github@seansullivan.com> Date: Tue, 17 Mar 2020 18:09:35 -0700 Subject: [PATCH 383/815] maven-compiler-plugin 3.8.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ae01c90f1..24d91e58e 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.7.0</version> + <version>3.8.1</version> <configuration> <source>7</source> <target>7</target> From 0d5c6c48bc5339182c3470d7886b8108fd2b7baa Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Mon, 23 Mar 2020 17:45:54 +1100 Subject: [PATCH 384/815] An extension for adding styles to nodes * style elements are given as key=value pairs inside curly braces, for example: ![text](/url.png){height=5 width=6} after the node to which the apply * only allow height and width as the supported set of style keys * only support styles for image nodes --- commonmark-ext-styles/pom.xml | 47 +++++++++ .../org/commonmark/ext/styles/Styles.java | 35 +++++++ .../ext/styles/StylesExtension.java | 43 ++++++++ .../internal/StylesAttributeProvider.java | 46 +++++++++ .../internal/StylesDelimiterProcessor.java | 51 ++++++++++ .../src/main/javadoc/overview.html | 6 ++ .../org/commonmark/ext/styles/StylesTest.java | 99 +++++++++++++++++++ pom.xml | 1 + 8 files changed, 328 insertions(+) create mode 100644 commonmark-ext-styles/pom.xml create mode 100644 commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/Styles.java create mode 100644 commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java create mode 100644 commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java create mode 100644 commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java create mode 100644 commonmark-ext-styles/src/main/javadoc/overview.html create mode 100644 commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java diff --git a/commonmark-ext-styles/pom.xml b/commonmark-ext-styles/pom.xml new file mode 100644 index 000000000..eaff86343 --- /dev/null +++ b/commonmark-ext-styles/pom.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark-parent</artifactId> + <version>0.13.1-SNAPSHOT</version> + </parent> + + <artifactId>commonmark-ext-styles</artifactId> + <name>commonmark-java extension for styles</name> + <description>commonmark-java extension for adding style details to images</description> + + <properties> + <autolink.version>0.10.0</autolink.version> + </properties> + + <dependencies> + <dependency> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark</artifactId> + </dependency> + + <dependency> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark-test-util</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestEntries> + <Automatic-Module-Name>org.commonmark.ext.styles</Automatic-Module-Name> + </manifestEntries> + </archive> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/Styles.java b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/Styles.java new file mode 100644 index 000000000..f2c067e1b --- /dev/null +++ b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/Styles.java @@ -0,0 +1,35 @@ +package org.commonmark.ext.styles; + +import org.commonmark.node.CustomNode; +import org.commonmark.node.Delimited; + +/** + * A styles node containing text and other inline nodes as children. + */ +public class Styles extends CustomNode implements Delimited { + + private final String styles; + + public Styles(String styles) { + this.styles = styles; + } + + @Override + public String getOpeningDelimiter() { + return "{"; + } + + @Override + public String getClosingDelimiter() { + return "}"; + } + + public String getStyles() { + return styles; + } + + @Override + protected String toStringAttributes() { + return "styles=" + styles; + } +} diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java new file mode 100644 index 000000000..311e3f4d1 --- /dev/null +++ b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java @@ -0,0 +1,43 @@ +package org.commonmark.ext.styles; + +import org.commonmark.Extension; +import org.commonmark.ext.styles.internal.StylesAttributeProvider; +import org.commonmark.ext.styles.internal.StylesDelimiterProcessor; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.AttributeProvider; +import org.commonmark.renderer.html.AttributeProviderContext; +import org.commonmark.renderer.html.AttributeProviderFactory; +import org.commonmark.renderer.html.HtmlRenderer; + +/** + * Extension for adding styles to nodes. + * <p> + * Create it with {@link #create()} and then configure it on the builders + * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, + * {@link HtmlRenderer.Builder#extensions(Iterable)}). + * </p> + */ +public class StylesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { + + private StylesExtension() { + } + + static Extension create() { + return new StylesExtension(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.customDelimiterProcessor(new StylesDelimiterProcessor()); + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.attributeProviderFactory(new AttributeProviderFactory() { + @Override + public AttributeProvider create(AttributeProviderContext context) { + return StylesAttributeProvider.create(); + } + }); + } +} diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java new file mode 100644 index 000000000..850682b06 --- /dev/null +++ b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java @@ -0,0 +1,46 @@ +package org.commonmark.ext.styles.internal; + +import org.commonmark.ext.styles.Styles; +import org.commonmark.node.AbstractVisitor; +import org.commonmark.node.CustomNode; +import org.commonmark.node.Image; +import org.commonmark.node.Node; +import org.commonmark.renderer.html.AttributeProvider; + +import java.util.*; + +public class StylesAttributeProvider implements AttributeProvider { + + // Only allow a defined set of styles to be used. + private static final Set<String> SUPPORTED_STYLES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("width", "height"))); + + private StylesAttributeProvider() { + } + + public static StylesAttributeProvider create() { + return new StylesAttributeProvider(); + } + + @Override + public void setAttributes(Node node, String tagName, final Map<String, String> attributes) { + if (node instanceof Image) { + node.accept(new AbstractVisitor() { + @Override + public void visit(CustomNode node) { + if (node instanceof Styles) { + Styles styles = (Styles) node; + for (String s: styles.getStyles().split(" ")) { + String[] attribute = s.split("="); + if (SUPPORTED_STYLES.contains(attribute[0].toLowerCase())) { + attributes.put(attribute[0], attribute[1]); + } + } + // Now that we have used the styles we remove the node. + styles.unlink(); + } + } + }); + } + } +} diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java new file mode 100644 index 000000000..a4b5d6bd3 --- /dev/null +++ b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java @@ -0,0 +1,51 @@ +package org.commonmark.ext.styles.internal; + +import org.commonmark.ext.styles.Styles; +import org.commonmark.node.Node; +import org.commonmark.node.Text; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; + +public class StylesDelimiterProcessor implements DelimiterProcessor { + + @Override + public char getOpeningCharacter() { + return '{'; + } + + @Override + public char getClosingCharacter() { + return '}'; + } + + @Override + public int getMinLength() { + return 1; + } + + @Override + public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + return 1; + } + + @Override + public void process(Text opener, Text closer, int delimiterCount) { + StringBuilder styleString = new StringBuilder(); + Node tmp = opener.getNext(); + while (tmp != null && tmp != closer) { + Node next = tmp.getNext(); + if (tmp instanceof Text) { + styleString.append(((Text) tmp).getLiteral()); + // Unlink the tmp node, now that we have retrieved its value. + tmp.unlink(); + } + tmp = next; + } + + Styles styles = new Styles(styleString.toString()); + + // The styles node is added as a child of the node to which the styles apply. + Node nodeToStyle = opener.getPrevious(); + nodeToStyle.appendChild(styles); + } +} diff --git a/commonmark-ext-styles/src/main/javadoc/overview.html b/commonmark-ext-styles/src/main/javadoc/overview.html new file mode 100644 index 000000000..5f673e320 --- /dev/null +++ b/commonmark-ext-styles/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ +<html> +<body> +<b>Extension for adding styles to nodes</b> +<p>See {@link org.commonmark.ext.styles.StylesExtension}</p> +</body> +</html> diff --git a/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java b/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java new file mode 100644 index 000000000..ee598eb5e --- /dev/null +++ b/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java @@ -0,0 +1,99 @@ +package org.commonmark.ext.styles; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; +import org.junit.Test; + +import java.util.Collections; +import java.util.Set; + +public class StylesTest extends RenderingTestCase { + + private static final Set<Extension> EXTENSIONS = Collections.singleton(StylesExtension.create()); + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void baseCase() { + assertRendering("![text](/url.png){height=5}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"5\" /></p>\n"); + + assertRendering("![text](/url.png){height=5 width=6}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"5\" width=\"6\" /></p>\n"); + + assertRendering("![text](/url.png){height=99px width=100px}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"99px\" width=\"100px\" /></p>\n"); + + assertRendering("![text](/url.png){width=100 height=100}", + "<p><img src=\"/url.png\" alt=\"text\" width=\"100\" height=\"100\" /></p>\n"); + + assertRendering("![text](/url.png){height=4.8 width=3.14}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"4.8\" width=\"3.14\" /></p>\n"); + + assertRendering("![text](/url.png){Width=18 HeIgHt=1001}", + "<p><img src=\"/url.png\" alt=\"text\" Width=\"18\" HeIgHt=\"1001\" /></p>\n"); + + assertRendering("![text](/url.png){height=green width=blue}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"green\" width=\"blue\" /></p>\n"); + } + + @Test + public void doubleDelimiters() { + assertRendering("![text](/url.png){{height=5}}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"5\" /></p>\n"); + } + + @Test + public void mismatchingDelimitersAreIgnored() { + assertRendering("![text](/url.png){", "<p><img src=\"/url.png\" alt=\"text\" />{</p>\n"); + } + + @Test + public void unsupportedStyleNamesAreRemoved() { + assertRendering("![text](/url.png){j=502 K=101 img=2 url=5}", "<p><img src=\"/url.png\" alt=\"text\" /></p>\n"); + } + + @Test + public void repeatedStyleNameUsesFinalOne() { + assertRendering("![text](/url.png){height=4 height=5 width=1 height=6}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"6\" width=\"1\" /></p>\n"); } + + @Test + public void styleValuesAreEscaped() { + assertRendering("![text](/url.png){height=<img}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"<img\" /></p>\n"); + assertRendering("![text](/url.png){height=\"\"img}", + "<p><img src=\"/url.png\" alt=\"text\" height=\"""img\" /></p>\n"); + } + + @Test + public void imageAltTextWithSpaces() { + assertRendering("![Android SDK Manager](/contrib/android-sdk-manager.png){height=502 width=101}", + "<p><img src=\"/contrib/android-sdk-manager.png\" alt=\"Android SDK Manager\" height=\"502\" width=\"101\" /></p>\n"); + } + + @Test + public void imageAltTextWithSoftLineBreak() { + assertRendering("![foo\nbar](/url){height=101 width=202}\n", + "<p><img src=\"/url\" alt=\"foo\nbar\" height=\"101\" width=\"202\" /></p>\n"); + } + + @Test + public void imageAltTextWithHardLineBreak() { + assertRendering("![foo \nbar](/url){height=506 width=1}\n", + "<p><img src=\"/url\" alt=\"foo\nbar\" height=\"506\" width=\"1\" /></p>\n"); + } + + @Test + public void imageAltTextWithEntities() { + assertRendering("![foo ä](/url){height=99 width=100}\n", + "<p><img src=\"/url\" alt=\"foo \u00E4\" height=\"99\" width=\"100\" /></p>\n"); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/pom.xml b/pom.xml index ae01c90f1..f56cd792f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ <module>commonmark-ext-gfm-tables</module> <module>commonmark-ext-heading-anchor</module> <module>commonmark-ext-ins</module> + <module>commonmark-ext-styles</module> <module>commonmark-ext-yaml-front-matter</module> <module>commonmark-integration-test</module> <module>commonmark-test-util</module> From 5ddcc1ca30e0e602b32b9bf96d2d793497929d06 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Mon, 23 Mar 2020 18:15:22 +1100 Subject: [PATCH 385/815] Fixed snapshot version in commonmark-ext-styles/pom.xml --- commonmark-ext-styles/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commonmark-ext-styles/pom.xml b/commonmark-ext-styles/pom.xml index eaff86343..701b42e4b 100644 --- a/commonmark-ext-styles/pom.xml +++ b/commonmark-ext-styles/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.13.1-SNAPSHOT</version> + <version>0.14.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-styles</artifactId> From dc67f1bd5ffddec796a361c6a26a249b53b65158 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Mon, 23 Mar 2020 18:23:47 +1100 Subject: [PATCH 386/815] Removed unneeded property --- commonmark-ext-styles/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/commonmark-ext-styles/pom.xml b/commonmark-ext-styles/pom.xml index 701b42e4b..90ace5404 100644 --- a/commonmark-ext-styles/pom.xml +++ b/commonmark-ext-styles/pom.xml @@ -11,10 +11,6 @@ <name>commonmark-java extension for styles</name> <description>commonmark-java extension for adding style details to images</description> - <properties> - <autolink.version>0.10.0</autolink.version> - </properties> - <dependencies> <dependency> <groupId>com.atlassian.commonmark</groupId> From 94b32d0876014a990b8e98cb57627a2641f0b8d1 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Tue, 24 Mar 2020 12:09:53 +1100 Subject: [PATCH 387/815] Made a few small changes related to early review feedback: * added info to README.md about the new commonmark-ext-styles artifact * made the create() method public on StylesExtension * added negative test case for text nodes to StylesTest --- README.md | 11 +++++++++++ .../org/commonmark/ext/styles/StylesExtension.java | 2 +- .../java/org/commonmark/ext/styles/StylesTest.java | 8 ++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ebe736a5..6ed463b47 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,17 @@ document start here Use class `YamlFrontMatterExtension` in artifact `commonmark-ext-yaml-front-matter`. To fetch metadata, use `YamlFrontMatterVisitor`. +### Styles + +Adds support for specifying style details (specifically height and width) for images. + +The style elements are given as key=value pairs inside curly braces after the node to which they apply, for example: +``` +![text](/url.png){height=5 width=6} +``` + +Use class `StylesExtension` in artifact `commonmark-ext-styles`. + See also -------- diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java index 311e3f4d1..ecd2df57a 100644 --- a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java +++ b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java @@ -22,7 +22,7 @@ public class StylesExtension implements Parser.ParserExtension, HtmlRenderer.Htm private StylesExtension() { } - static Extension create() { + public static Extension create() { return new StylesExtension(); } diff --git a/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java b/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java index ee598eb5e..066715af3 100644 --- a/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java +++ b/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java @@ -92,6 +92,14 @@ public void imageAltTextWithEntities() { "<p><img src=\"/url\" alt=\"foo \u00E4\" height=\"99\" width=\"100\" /></p>\n"); } + @Test + public void textNodesAreUnchanged() { + assertRendering("This is some text with random styles immediately afterwards{height=20}\n", + "<p>This is some text with random styles immediately afterwards</p>\n"); + assertRendering("This is some text with random styles after a space {width=100px}\n", + "<p>This is some text with random styles after a space </p>\n"); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); From b155133beab4b230d7cb9c86b613a8293ff77ed7 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Sat, 28 Mar 2020 16:12:52 +1100 Subject: [PATCH 388/815] Further changes related to pull request feedback: * Added more details to README.md * added HTML output that gets produced by the example * added comment that curly braces cannot be reused by other delimiter processors * fallback to text if the attributes cannot be parsed * added checks to StylesDelimiterProcessor that attribute names are in SUPPORTED_STYLES, and that node is an Image type * added support to StylesDelimiterProcessor for falling back to text (need to add back the opening and closing characters) * removed SUPPORTED_STYLES from StylesAttributeProvider (as the check is now performed in StylesDelimiterProcessor) * changed the styles splitting to use "\\s+" rather than single " " in StylesDelimiterProcessor/StylesAttributeProvider * added/updated tests in StylesTest related to falling back to text --- README.md | 9 +- .../internal/StylesAttributeProvider.java | 10 +- .../internal/StylesDelimiterProcessor.java | 100 ++++++++++++++++-- .../org/commonmark/ext/styles/StylesTest.java | 19 ++-- 4 files changed, 112 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 6ed463b47..f717e037a 100644 --- a/README.md +++ b/README.md @@ -323,13 +323,20 @@ Use class `YamlFrontMatterExtension` in artifact `commonmark-ext-yaml-front-matt Adds support for specifying style details (specifically height and width) for images. -The style elements are given as key=value pairs inside curly braces after the node to which they apply, for example: +The style elements are given as `key=value` pairs inside curly braces `{ }` after the node to which they apply, for example: ``` ![text](/url.png){height=5 width=6} ``` +will be rendered as: +``` +<img src="/url.png" alt="text" height="5" width="6" /> +``` Use class `StylesExtension` in artifact `commonmark-ext-styles`. +Note: since this extension uses curly braces `{` `}` as its delimiters (in `StylesDelimiterProcessor`), this means that other delimiter +processors *cannot* use curly braces for delimiting. + See also -------- diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java index 850682b06..3ebd2c3f1 100644 --- a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java +++ b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java @@ -11,10 +11,6 @@ public class StylesAttributeProvider implements AttributeProvider { - // Only allow a defined set of styles to be used. - private static final Set<String> SUPPORTED_STYLES = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList("width", "height"))); - private StylesAttributeProvider() { } @@ -30,11 +26,9 @@ public void setAttributes(Node node, String tagName, final Map<String, String> a public void visit(CustomNode node) { if (node instanceof Styles) { Styles styles = (Styles) node; - for (String s: styles.getStyles().split(" ")) { + for (String s : styles.getStyles().split("\\s+")) { String[] attribute = s.split("="); - if (SUPPORTED_STYLES.contains(attribute[0].toLowerCase())) { - attributes.put(attribute[0], attribute[1]); - } + attributes.put(attribute[0], attribute[1]); } // Now that we have used the styles we remove the node. styles.unlink(); diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java index a4b5d6bd3..ddefd5c99 100644 --- a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java +++ b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java @@ -1,13 +1,20 @@ package org.commonmark.ext.styles.internal; import org.commonmark.ext.styles.Styles; +import org.commonmark.node.Image; import org.commonmark.node.Node; import org.commonmark.node.Text; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; +import java.util.*; + public class StylesDelimiterProcessor implements DelimiterProcessor { + // Only allow a defined set of styles to be used. + private static final Set<String> SUPPORTED_STYLES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("width", "height"))); + @Override public char getOpeningCharacter() { return '{'; @@ -30,22 +37,95 @@ public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { @Override public void process(Text opener, Text closer, int delimiterCount) { - StringBuilder styleString = new StringBuilder(); + // Check if the attributes can be applied - if the previous node is an Image, and if all the attributes are in + // the set of SUPPORTED_STYLES + if (opener.getPrevious() instanceof Image) { + boolean canApply = true; + List<Node> toUnlink = new ArrayList<>(); + + StringBuilder styleString = new StringBuilder(); + Node tmp = opener.getNext(); + while (tmp != null && tmp != closer) { + Node next = tmp.getNext(); + // Only Text nodes can be used for attributes + if (tmp instanceof Text) { + String styles = ((Text) tmp).getLiteral(); + for (String s : styles.split("\\s+")) { + String[] attribute = s.split("="); + // Check if the attribute is in SUPPORTED_STYLES. + if (SUPPORTED_STYLES.contains(attribute[0].toLowerCase())) { + styleString.append(((Text) tmp).getLiteral()); + // The tmp node can be unlinked, as we have retrieved its value. + toUnlink.add(tmp); + } else { + // This attribute is not supported, so break here (no need to check any further ones). + canApply = false; + break; + } + } + } else { + // This node type is not supported, so break here (no need to check any further ones). + canApply = false; + break; + } + tmp = next; + } + + // Only if all of the above checks pass can the attributes be applied. + if (canApply) { + // Unlink the tmp nodes + for (Node node : toUnlink) { + node.unlink(); + } + + if (styleString.length() > 0) { + Styles styles = new Styles(styleString.toString()); + + // The styles node is added as a child of the node to which the styles apply. + Node nodeToStyle = opener.getPrevious(); + nodeToStyle.appendChild(styles); + } + return; + } + } + + // If we got here then the attributes cannot be applied, so fallback to leaving the text unchanged. + // Need to add back the opening and closing characters (which are removed elsewhere). + if (opener.getPrevious() == null) { + opener.getParent().prependChild(new Text("" + getOpeningCharacter())); + } else { + opener.getPrevious().insertAfter(new Text("" + getOpeningCharacter())); + } + closer.getParent().appendChild(new Text("" + getClosingCharacter())); + } + + /** + * Check that the attributes can be applied to the previous node. + * @param opener the text node that contained the opening delimiter + * @param closer the text node that contained the closing delimiter + * @return true if the previous node is an Image and the attributes are in the set of {@link #SUPPORTED_STYLES} + */ + private boolean canApply(Text opener, Text closer) { + if (!(opener.getPrevious() instanceof Image)) { + return false; + } + Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { Node next = tmp.getNext(); if (tmp instanceof Text) { - styleString.append(((Text) tmp).getLiteral()); - // Unlink the tmp node, now that we have retrieved its value. - tmp.unlink(); + String styles = ((Text) tmp).getLiteral(); + for (String s : styles.split("\\s+")) { + String[] attribute = s.split("="); + if (!SUPPORTED_STYLES.contains(attribute[0].toLowerCase())) { + return false; + } + } + } else { + return false; } tmp = next; } - - Styles styles = new Styles(styleString.toString()); - - // The styles node is added as a child of the node to which the styles apply. - Node nodeToStyle = opener.getPrevious(); - nodeToStyle.appendChild(styles); + return true; } } diff --git a/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java b/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java index 066715af3..da2aedd3b 100644 --- a/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java +++ b/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java @@ -23,7 +23,7 @@ public void baseCase() { assertRendering("![text](/url.png){height=5 width=6}", "<p><img src=\"/url.png\" alt=\"text\" height=\"5\" width=\"6\" /></p>\n"); - assertRendering("![text](/url.png){height=99px width=100px}", + assertRendering("![text](/url.png){height=99px width=100px}", "<p><img src=\"/url.png\" alt=\"text\" height=\"99px\" width=\"100px\" /></p>\n"); assertRendering("![text](/url.png){width=100 height=100}", @@ -51,8 +51,13 @@ public void mismatchingDelimitersAreIgnored() { } @Test - public void unsupportedStyleNamesAreRemoved() { - assertRendering("![text](/url.png){j=502 K=101 img=2 url=5}", "<p><img src=\"/url.png\" alt=\"text\" /></p>\n"); + public void unsupportedStyleNamesAreLeftUnchanged() { + assertRendering("![text](/url.png){j=502 K=101 img=2 url=5}", + "<p><img src=\"/url.png\" alt=\"text\" />{j=502 K=101 img=2 url=5}</p>\n"); + assertRendering("![foo](/url.png){height=3 invalid}\n", + "<p><img src=\"/url.png\" alt=\"foo\" />{height=3 invalid}</p>\n"); + assertRendering("![foo](/url.png){height=3 *test*}\n", + "<p><img src=\"/url.png\" alt=\"foo\" />{height=3 <em>test</em>}</p>\n"); } @Test @@ -94,10 +99,10 @@ public void imageAltTextWithEntities() { @Test public void textNodesAreUnchanged() { - assertRendering("This is some text with random styles immediately afterwards{height=20}\n", - "<p>This is some text with random styles immediately afterwards</p>\n"); - assertRendering("This is some text with random styles after a space {width=100px}\n", - "<p>This is some text with random styles after a space </p>\n"); + assertRendering("x{height=3 width=4}\n", "<p>x{height=3 width=4}</p>\n"); + assertRendering("x {height=3 width=4}\n", "<p>x {height=3 width=4}</p>\n"); + assertRendering("\\documentclass[12pt]{article}\n", "<p>\\documentclass[12pt]{article}</p>\n"); + assertRendering("some *text*{height=3 width=4}\n", "<p>some <em>text</em>{height=3 width=4}</p>\n"); } @Override From 9d192978dc36ad26b23233fdf8570f21947632f2 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Sat, 28 Mar 2020 16:47:08 +1100 Subject: [PATCH 389/815] Changes related to pull request feedback: * renamed "Styles" to "Image Attributes" * renamed "Styles*" classes to "ImageAttributes*" * moved package to "org.commonmark.ext.image.attributes" * moved artifact to "commonmark-ext-image-attributes" * renamed "style"/"styles" variables to "attributes" --- README.md | 13 +++---- .../pom.xml | 8 ++--- .../ext/image/attributes/ImageAttributes.java | 35 +++++++++++++++++++ .../attributes/ImageAttributesExtension.java | 18 +++++----- .../ImageAttributesAttributeProvider.java | 22 ++++++------ .../ImageAttributesDelimiterProcessor.java | 34 +++++++++--------- .../src/main/javadoc/overview.html | 6 ++++ .../image/attributes/ImageAttributesTest.java | 6 ++-- .../org/commonmark/ext/styles/Styles.java | 35 ------------------- .../src/main/javadoc/overview.html | 6 ---- pom.xml | 2 +- 11 files changed, 93 insertions(+), 92 deletions(-) rename {commonmark-ext-styles => commonmark-ext-image-attributes}/pom.xml (83%) create mode 100644 commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java rename commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java => commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java (60%) rename commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java => commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java (50%) rename commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java => commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java (76%) create mode 100644 commonmark-ext-image-attributes/src/main/javadoc/overview.html rename commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java => commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java (96%) delete mode 100644 commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/Styles.java delete mode 100644 commonmark-ext-styles/src/main/javadoc/overview.html diff --git a/README.md b/README.md index f717e037a..264478ae1 100644 --- a/README.md +++ b/README.md @@ -319,11 +319,12 @@ document start here Use class `YamlFrontMatterExtension` in artifact `commonmark-ext-yaml-front-matter`. To fetch metadata, use `YamlFrontMatterVisitor`. -### Styles +### Image Attributes -Adds support for specifying style details (specifically height and width) for images. +Adds support for specifying attributes (specifically height and width) for images. -The style elements are given as `key=value` pairs inside curly braces `{ }` after the node to which they apply, for example: +The attribute elements are given as `key=value` pairs inside curly braces `{ }` after the image node to which they apply, +for example: ``` ![text](/url.png){height=5 width=6} ``` @@ -332,10 +333,10 @@ will be rendered as: <img src="/url.png" alt="text" height="5" width="6" /> ``` -Use class `StylesExtension` in artifact `commonmark-ext-styles`. +Use class `StylesExtension` in artifact `commonmark-ext-image-attributes`. -Note: since this extension uses curly braces `{` `}` as its delimiters (in `StylesDelimiterProcessor`), this means that other delimiter -processors *cannot* use curly braces for delimiting. +Note: since this extension uses curly braces `{` `}` as its delimiters (in `StylesDelimiterProcessor`), this means that +other delimiter processors *cannot* use curly braces for delimiting. See also -------- diff --git a/commonmark-ext-styles/pom.xml b/commonmark-ext-image-attributes/pom.xml similarity index 83% rename from commonmark-ext-styles/pom.xml rename to commonmark-ext-image-attributes/pom.xml index 90ace5404..9fe11a3b6 100644 --- a/commonmark-ext-styles/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -7,9 +7,9 @@ <version>0.14.1-SNAPSHOT</version> </parent> - <artifactId>commonmark-ext-styles</artifactId> - <name>commonmark-java extension for styles</name> - <description>commonmark-java extension for adding style details to images</description> + <artifactId>commonmark-ext-image-attributes</artifactId> + <name>commonmark-java extension for image attributes</name> + <description>commonmark-java extension for adding attributes to images</description> <dependencies> <dependency> @@ -32,7 +32,7 @@ <configuration> <archive> <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.styles</Automatic-Module-Name> + <Automatic-Module-Name>org.commonmark.ext.image.attributes</Automatic-Module-Name> </manifestEntries> </archive> </configuration> diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java new file mode 100644 index 000000000..c0173ab02 --- /dev/null +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java @@ -0,0 +1,35 @@ +package org.commonmark.ext.image.attributes; + +import org.commonmark.node.CustomNode; +import org.commonmark.node.Delimited; + +/** + * A node containing text and other inline nodes as children. + */ +public class ImageAttributes extends CustomNode implements Delimited { + + private final String attributes; + + public ImageAttributes(String attributes) { + this.attributes = attributes; + } + + @Override + public String getOpeningDelimiter() { + return "{"; + } + + @Override + public String getClosingDelimiter() { + return "}"; + } + + public String getAttributes() { + return attributes; + } + + @Override + protected String toStringAttributes() { + return "imageAttributes=" + attributes; + } +} diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java similarity index 60% rename from commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java rename to commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java index ecd2df57a..1edfb443b 100644 --- a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/StylesExtension.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java @@ -1,8 +1,8 @@ -package org.commonmark.ext.styles; +package org.commonmark.ext.image.attributes; import org.commonmark.Extension; -import org.commonmark.ext.styles.internal.StylesAttributeProvider; -import org.commonmark.ext.styles.internal.StylesDelimiterProcessor; +import org.commonmark.ext.image.attributes.internal.ImageAttributesAttributeProvider; +import org.commonmark.ext.image.attributes.internal.ImageAttributesDelimiterProcessor; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProviderContext; @@ -10,25 +10,25 @@ import org.commonmark.renderer.html.HtmlRenderer; /** - * Extension for adding styles to nodes. + * Extension for adding attributes to image nodes. * <p> * Create it with {@link #create()} and then configure it on the builders * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, * {@link HtmlRenderer.Builder#extensions(Iterable)}). * </p> */ -public class StylesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +public class ImageAttributesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { - private StylesExtension() { + private ImageAttributesExtension() { } public static Extension create() { - return new StylesExtension(); + return new ImageAttributesExtension(); } @Override public void extend(Parser.Builder parserBuilder) { - parserBuilder.customDelimiterProcessor(new StylesDelimiterProcessor()); + parserBuilder.customDelimiterProcessor(new ImageAttributesDelimiterProcessor()); } @Override @@ -36,7 +36,7 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { rendererBuilder.attributeProviderFactory(new AttributeProviderFactory() { @Override public AttributeProvider create(AttributeProviderContext context) { - return StylesAttributeProvider.create(); + return ImageAttributesAttributeProvider.create(); } }); } diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java similarity index 50% rename from commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java rename to commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java index 3ebd2c3f1..1d34bed6b 100644 --- a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesAttributeProvider.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java @@ -1,6 +1,6 @@ -package org.commonmark.ext.styles.internal; +package org.commonmark.ext.image.attributes.internal; -import org.commonmark.ext.styles.Styles; +import org.commonmark.ext.image.attributes.ImageAttributes; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.CustomNode; import org.commonmark.node.Image; @@ -9,13 +9,13 @@ import java.util.*; -public class StylesAttributeProvider implements AttributeProvider { +public class ImageAttributesAttributeProvider implements AttributeProvider { - private StylesAttributeProvider() { + private ImageAttributesAttributeProvider() { } - public static StylesAttributeProvider create() { - return new StylesAttributeProvider(); + public static ImageAttributesAttributeProvider create() { + return new ImageAttributesAttributeProvider(); } @Override @@ -24,14 +24,14 @@ public void setAttributes(Node node, String tagName, final Map<String, String> a node.accept(new AbstractVisitor() { @Override public void visit(CustomNode node) { - if (node instanceof Styles) { - Styles styles = (Styles) node; - for (String s : styles.getStyles().split("\\s+")) { + if (node instanceof ImageAttributes) { + ImageAttributes imageAttributes = (ImageAttributes) node; + for (String s : imageAttributes.getAttributes().split("\\s+")) { String[] attribute = s.split("="); attributes.put(attribute[0], attribute[1]); } - // Now that we have used the styles we remove the node. - styles.unlink(); + // Now that we have used the image attributes we remove the node. + imageAttributes.unlink(); } } }); diff --git a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java similarity index 76% rename from commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java rename to commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java index ddefd5c99..ad2943eec 100644 --- a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/internal/StylesDelimiterProcessor.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java @@ -1,6 +1,6 @@ -package org.commonmark.ext.styles.internal; +package org.commonmark.ext.image.attributes.internal; -import org.commonmark.ext.styles.Styles; +import org.commonmark.ext.image.attributes.ImageAttributes; import org.commonmark.node.Image; import org.commonmark.node.Node; import org.commonmark.node.Text; @@ -9,10 +9,10 @@ import java.util.*; -public class StylesDelimiterProcessor implements DelimiterProcessor { +public class ImageAttributesDelimiterProcessor implements DelimiterProcessor { - // Only allow a defined set of styles to be used. - private static final Set<String> SUPPORTED_STYLES = Collections.unmodifiableSet( + // Only allow a defined set of attributes to be used. + private static final Set<String> SUPPORTED_ATTRIBUTES = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("width", "height"))); @Override @@ -38,23 +38,23 @@ public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { @Override public void process(Text opener, Text closer, int delimiterCount) { // Check if the attributes can be applied - if the previous node is an Image, and if all the attributes are in - // the set of SUPPORTED_STYLES + // the set of SUPPORTED_ATTRIBUTES if (opener.getPrevious() instanceof Image) { boolean canApply = true; List<Node> toUnlink = new ArrayList<>(); - StringBuilder styleString = new StringBuilder(); + StringBuilder attributesString = new StringBuilder(); Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { Node next = tmp.getNext(); // Only Text nodes can be used for attributes if (tmp instanceof Text) { - String styles = ((Text) tmp).getLiteral(); - for (String s : styles.split("\\s+")) { + String attributes = ((Text) tmp).getLiteral(); + for (String s : attributes.split("\\s+")) { String[] attribute = s.split("="); // Check if the attribute is in SUPPORTED_STYLES. - if (SUPPORTED_STYLES.contains(attribute[0].toLowerCase())) { - styleString.append(((Text) tmp).getLiteral()); + if (SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { + attributesString.append(((Text) tmp).getLiteral()); // The tmp node can be unlinked, as we have retrieved its value. toUnlink.add(tmp); } else { @@ -78,12 +78,12 @@ public void process(Text opener, Text closer, int delimiterCount) { node.unlink(); } - if (styleString.length() > 0) { - Styles styles = new Styles(styleString.toString()); + if (attributesString.length() > 0) { + ImageAttributes imageAttributes = new ImageAttributes(attributesString.toString()); - // The styles node is added as a child of the node to which the styles apply. + // The new node is added as a child of the image node to which the attributes apply. Node nodeToStyle = opener.getPrevious(); - nodeToStyle.appendChild(styles); + nodeToStyle.appendChild(imageAttributes); } return; } @@ -103,7 +103,7 @@ public void process(Text opener, Text closer, int delimiterCount) { * Check that the attributes can be applied to the previous node. * @param opener the text node that contained the opening delimiter * @param closer the text node that contained the closing delimiter - * @return true if the previous node is an Image and the attributes are in the set of {@link #SUPPORTED_STYLES} + * @return true if the previous node is an Image and the attributes are in the set of {@link #SUPPORTED_ATTRIBUTES} */ private boolean canApply(Text opener, Text closer) { if (!(opener.getPrevious() instanceof Image)) { @@ -117,7 +117,7 @@ private boolean canApply(Text opener, Text closer) { String styles = ((Text) tmp).getLiteral(); for (String s : styles.split("\\s+")) { String[] attribute = s.split("="); - if (!SUPPORTED_STYLES.contains(attribute[0].toLowerCase())) { + if (!SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { return false; } } diff --git a/commonmark-ext-image-attributes/src/main/javadoc/overview.html b/commonmark-ext-image-attributes/src/main/javadoc/overview.html new file mode 100644 index 000000000..060597233 --- /dev/null +++ b/commonmark-ext-image-attributes/src/main/javadoc/overview.html @@ -0,0 +1,6 @@ +<html> +<body> +<b>Extension for adding attributes to image nodes</b> +<p>See {@link org.commonmark.ext.image.attributes.ImageAttributes}</p> +</body> +</html> diff --git a/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java similarity index 96% rename from commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java rename to commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java index da2aedd3b..d2038776d 100644 --- a/commonmark-ext-styles/src/test/java/org/commonmark/ext/styles/StylesTest.java +++ b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java @@ -1,4 +1,4 @@ -package org.commonmark.ext.styles; +package org.commonmark.ext.image.attributes; import org.commonmark.Extension; import org.commonmark.parser.Parser; @@ -9,9 +9,9 @@ import java.util.Collections; import java.util.Set; -public class StylesTest extends RenderingTestCase { +public class ImageAttributesTest extends RenderingTestCase { - private static final Set<Extension> EXTENSIONS = Collections.singleton(StylesExtension.create()); + private static final Set<Extension> EXTENSIONS = Collections.singleton(ImageAttributesExtension.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-styles/src/main/java/org/commonmark/ext/styles/Styles.java b/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/Styles.java deleted file mode 100644 index f2c067e1b..000000000 --- a/commonmark-ext-styles/src/main/java/org/commonmark/ext/styles/Styles.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.commonmark.ext.styles; - -import org.commonmark.node.CustomNode; -import org.commonmark.node.Delimited; - -/** - * A styles node containing text and other inline nodes as children. - */ -public class Styles extends CustomNode implements Delimited { - - private final String styles; - - public Styles(String styles) { - this.styles = styles; - } - - @Override - public String getOpeningDelimiter() { - return "{"; - } - - @Override - public String getClosingDelimiter() { - return "}"; - } - - public String getStyles() { - return styles; - } - - @Override - protected String toStringAttributes() { - return "styles=" + styles; - } -} diff --git a/commonmark-ext-styles/src/main/javadoc/overview.html b/commonmark-ext-styles/src/main/javadoc/overview.html deleted file mode 100644 index 5f673e320..000000000 --- a/commonmark-ext-styles/src/main/javadoc/overview.html +++ /dev/null @@ -1,6 +0,0 @@ -<html> -<body> -<b>Extension for adding styles to nodes</b> -<p>See {@link org.commonmark.ext.styles.StylesExtension}</p> -</body> -</html> diff --git a/pom.xml b/pom.xml index f56cd792f..f186b7f73 100644 --- a/pom.xml +++ b/pom.xml @@ -25,8 +25,8 @@ <module>commonmark-ext-gfm-strikethrough</module> <module>commonmark-ext-gfm-tables</module> <module>commonmark-ext-heading-anchor</module> + <module>commonmark-ext-image-attributes</module> <module>commonmark-ext-ins</module> - <module>commonmark-ext-styles</module> <module>commonmark-ext-yaml-front-matter</module> <module>commonmark-integration-test</module> <module>commonmark-test-util</module> From ac55e373e480ba2c20b21a2e94d3b51c489f80f8 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Sat, 28 Mar 2020 19:21:16 +1100 Subject: [PATCH 390/815] Added image attributes to SpecIntegrationTest --- commonmark-integration-test/pom.xml | 4 ++++ .../java/org/commonmark/integration/SpecIntegrationTest.java | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 6f2bdf0fe..cc408c48d 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -32,6 +32,10 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> </dependency> + <dependency> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark-ext-image-attributes</artifactId> + </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> 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 6462f4094..2c4e4a84d 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SpecIntegrationTest.java @@ -2,6 +2,7 @@ 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; @@ -21,6 +22,7 @@ public class SpecIntegrationTest extends SpecTestCase { private static final List<Extension> EXTENSIONS = Arrays.asList( AutolinkExtension.create(), + ImageAttributesExtension.create(), InsExtension.create(), StrikethroughExtension.create(), TablesExtension.create(), @@ -75,6 +77,9 @@ private static Map<String, String> getOverriddenExamples() { m.put("---\nFoo\n---\nBar\n---\nBaz\n", "<h2>Bar</h2>\n<p>Baz</p>\n"); m.put("---\n---\n", ""); + // Image attributes + m.put("![text](/url.png){height=5 width=6}", "<img src=\"/url.png\" alt=\"text\" height=\"5\" width=\"6\" />"); + return m; } From 2079fe675aa33c5679d302300eb72fa3fe826e55 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Sat, 28 Mar 2020 19:29:55 +1100 Subject: [PATCH 391/815] Added missing dependency info for commonmark-ext-image-attributes artifact --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index f186b7f73..83ab20e55 100644 --- a/pom.xml +++ b/pom.xml @@ -95,6 +95,11 @@ <artifactId>commonmark-ext-autolink</artifactId> <version>0.14.1-SNAPSHOT</version> </dependency> + <dependency> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark-ext-image-attributes</artifactId> + <version>0.14.1-SNAPSHOT</version> + </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> From f64b7b1bc89082192e1fb9d9d614908b475d8b05 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Thu, 16 Apr 2020 16:28:39 +1000 Subject: [PATCH 392/815] Fix bug related to keys without values, and removed some dead code * added check on attribute.length to ImageAttributesAttributeProvider and ImageAttributesDelimiterProcessor * new test styleWithNoValueIsIgnored * removed an unused method in ImageAttributesDelimiterProcessor --- .../ImageAttributesAttributeProvider.java | 4 ++- .../ImageAttributesDelimiterProcessor.java | 33 +------------------ .../image/attributes/ImageAttributesTest.java | 6 ++++ 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java index 1d34bed6b..f2efb0432 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java @@ -28,7 +28,9 @@ public void visit(CustomNode node) { ImageAttributes imageAttributes = (ImageAttributes) node; for (String s : imageAttributes.getAttributes().split("\\s+")) { String[] attribute = s.split("="); - attributes.put(attribute[0], attribute[1]); + if (attribute.length > 1) { + attributes.put(attribute[0], attribute[1]); + } } // Now that we have used the image attributes we remove the node. imageAttributes.unlink(); diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java index ad2943eec..4f4215941 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 @@ -52,8 +52,7 @@ public void process(Text opener, Text closer, int delimiterCount) { String attributes = ((Text) tmp).getLiteral(); for (String s : attributes.split("\\s+")) { String[] attribute = s.split("="); - // Check if the attribute is in SUPPORTED_STYLES. - if (SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { + if (attribute.length > 1 && SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { attributesString.append(((Text) tmp).getLiteral()); // The tmp node can be unlinked, as we have retrieved its value. toUnlink.add(tmp); @@ -98,34 +97,4 @@ public void process(Text opener, Text closer, int delimiterCount) { } closer.getParent().appendChild(new Text("" + getClosingCharacter())); } - - /** - * Check that the attributes can be applied to the previous node. - * @param opener the text node that contained the opening delimiter - * @param closer the text node that contained the closing delimiter - * @return true if the previous node is an Image and the attributes are in the set of {@link #SUPPORTED_ATTRIBUTES} - */ - private boolean canApply(Text opener, Text closer) { - if (!(opener.getPrevious() instanceof Image)) { - return false; - } - - Node tmp = opener.getNext(); - while (tmp != null && tmp != closer) { - Node next = tmp.getNext(); - if (tmp instanceof Text) { - String styles = ((Text) tmp).getLiteral(); - for (String s : styles.split("\\s+")) { - String[] attribute = s.split("="); - if (!SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { - return false; - } - } - } else { - return false; - } - tmp = next; - } - return true; - } } 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 d2038776d..cfffc8084 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 @@ -60,6 +60,12 @@ public void unsupportedStyleNamesAreLeftUnchanged() { "<p><img src=\"/url.png\" alt=\"foo\" />{height=3 <em>test</em>}</p>\n"); } + @Test + public void styleWithNoValueIsIgnored() { + assertRendering("![text](/url.png){height}", + "<p><img src=\"/url.png\" alt=\"text\" />{height}</p>\n"); + } + @Test public void repeatedStyleNameUsesFinalOne() { assertRendering("![text](/url.png){height=4 height=5 width=1 height=6}", From d559572ace9c4db534cc553e84c89ba13bc50f33 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Thu, 16 Apr 2020 16:46:09 +1000 Subject: [PATCH 393/815] Use a Map<String, String> for the attributes --- .../commonmark/ext/image/attributes/ImageAttributes.java | 8 +++++--- .../internal/ImageAttributesAttributeProvider.java | 8 +++----- .../internal/ImageAttributesDelimiterProcessor.java | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java index c0173ab02..1ee43958b 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributes.java @@ -3,14 +3,16 @@ import org.commonmark.node.CustomNode; import org.commonmark.node.Delimited; +import java.util.Map; + /** * A node containing text and other inline nodes as children. */ public class ImageAttributes extends CustomNode implements Delimited { - private final String attributes; + private final Map<String, String> attributes; - public ImageAttributes(String attributes) { + public ImageAttributes(Map<String, String> attributes) { this.attributes = attributes; } @@ -24,7 +26,7 @@ public String getClosingDelimiter() { return "}"; } - public String getAttributes() { + public Map<String, String> getAttributes() { return attributes; } diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java index f2efb0432..30fec4bb7 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java @@ -26,11 +26,9 @@ public void setAttributes(Node node, String tagName, final Map<String, String> a public void visit(CustomNode node) { if (node instanceof ImageAttributes) { ImageAttributes imageAttributes = (ImageAttributes) node; - for (String s : imageAttributes.getAttributes().split("\\s+")) { - String[] attribute = s.split("="); - if (attribute.length > 1) { - attributes.put(attribute[0], attribute[1]); - } + Map<String, String> attributesMap = imageAttributes.getAttributes(); + for (String key : attributesMap.keySet()) { + attributes.put(key, attributesMap.get(key)); } // Now that we have used the image attributes we remove the node. imageAttributes.unlink(); diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesDelimiterProcessor.java index 4f4215941..839015186 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 @@ -43,7 +43,7 @@ public void process(Text opener, Text closer, int delimiterCount) { boolean canApply = true; List<Node> toUnlink = new ArrayList<>(); - StringBuilder attributesString = new StringBuilder(); + Map<String, String> attributesMap = new LinkedHashMap<>(); Node tmp = opener.getNext(); while (tmp != null && tmp != closer) { Node next = tmp.getNext(); @@ -53,7 +53,7 @@ public void process(Text opener, Text closer, int delimiterCount) { for (String s : attributes.split("\\s+")) { String[] attribute = s.split("="); if (attribute.length > 1 && SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { - attributesString.append(((Text) tmp).getLiteral()); + attributesMap.put(attribute[0], attribute[1]); // The tmp node can be unlinked, as we have retrieved its value. toUnlink.add(tmp); } else { @@ -77,8 +77,8 @@ public void process(Text opener, Text closer, int delimiterCount) { node.unlink(); } - if (attributesString.length() > 0) { - ImageAttributes imageAttributes = new ImageAttributes(attributesString.toString()); + if (attributesMap.size() > 0) { + ImageAttributes imageAttributes = new ImageAttributes(attributesMap); // The new node is added as a child of the image node to which the attributes apply. Node nodeToStyle = opener.getPrevious(); From 52c8e1dea4427a32aa798a5f149b71db139745f0 Mon Sep 17 00:00:00 2001 From: F Doherty <44657799+dohertyfjatl@users.noreply.github.com> Date: Tue, 28 Apr 2020 21:34:42 +1000 Subject: [PATCH 394/815] Update commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java Co-Authored-By: Robin Stocker <robin.stocker@gmail.com> --- .../internal/ImageAttributesAttributeProvider.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java index 30fec4bb7..edd9c4692 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/internal/ImageAttributesAttributeProvider.java @@ -26,9 +26,8 @@ public void setAttributes(Node node, String tagName, final Map<String, String> a public void visit(CustomNode node) { if (node instanceof ImageAttributes) { ImageAttributes imageAttributes = (ImageAttributes) node; - Map<String, String> attributesMap = imageAttributes.getAttributes(); - for (String key : attributesMap.keySet()) { - attributes.put(key, attributesMap.get(key)); + for (Map.Entry<String, String> entry : imageAttributes.getAttributes().entrySet()) { + attributes.put(entry.getKey(), entry.getValue()); } // Now that we have used the image attributes we remove the node. imageAttributes.unlink(); From 6436f4176e2d39de163e01baac95aad9ccee620d Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 12 May 2020 11:28:11 +1000 Subject: [PATCH 395/815] Update link to CLA --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a187cea4b..dbf3be13f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,8 +30,7 @@ appropriate link below to digitally sign the CLA. The Corporate CLA is for those who are contributing as a member of an organization and the individual CLA is for those contributing as an individual. -* [CLA for corporate contributors](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=e1c17c66-ca4d-4aab-a953-2c231af4a20b) -* [CLA for individuals](https://na2.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=3f94fbdc-2fbe-46ac-b14c-5d152700ae5d) +https://opensource.atlassian.com/cla Releasing --------- From 72f9173b21d0163938cab39d45edb98f9329aee5 Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Mon, 27 Apr 2020 09:49:23 +1000 Subject: [PATCH 396/815] Add support for task list items * new module commonmark-ext-task-list-items * new TaskListItemMarker to denote ListItems which represent task lists * added TaskListItemInlineParser which checks ListItems for ones which match a task list regexp, and adds a TaskListItemMarker as the first child of such ListItems * new TaskListItemHtmlNodeRenderer which renders the html ("input type=checkbox..") for TaskListItemMarker nodes * new TaskListItemsExtension which configures the parser and renderer for task list items * added TaskListItemsTest with test cases for task lists * updated README.md with a description of the Task List Items extension --- README.md | 23 ++++ commonmark-ext-task-list-items/pom.xml | 43 +++++++ .../task/list/items/TaskListItemMarker.java | 19 +++ .../list/items/TaskListItemsExtension.java | 52 +++++++++ .../TaskListItemHtmlNodeRenderer.java | 52 +++++++++ .../internal/TaskListItemInlineParser.java | 47 ++++++++ .../task/list/items/TaskListItemsTest.java | 109 ++++++++++++++++++ commonmark-integration-test/pom.xml | 4 + .../integration/SpecIntegrationTest.java | 6 + pom.xml | 6 + 10 files changed, 361 insertions(+) create mode 100644 commonmark-ext-task-list-items/pom.xml create mode 100644 commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemMarker.java create mode 100644 commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java create mode 100644 commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java create mode 100644 commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemInlineParser.java create mode 100644 commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java diff --git a/README.md b/README.md index 264478ae1..6ef0bbe07 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,29 @@ Use class `StylesExtension` in artifact `commonmark-ext-image-attributes`. Note: since this extension uses curly braces `{` `}` as its delimiters (in `StylesDelimiterProcessor`), this means that other delimiter processors *cannot* use curly braces for delimiting. +### Task List Items + +Adds support for tasks as list items. + +A task can be represented as a list item where the first non-whitespace character is a left bracket `[`, then a single +whitespace character or the letter `x` in lowercase or uppercase, then a right bracket `]` followed by at least one +whitespace before any other content. + +For example: +``` +- [ ] task #1 +- [x] task #2 +``` +will be rendered as: +``` +<ul> +<li><input type="checkbox" disabled=""> task #1</li> +<li><input type="checkbox" disabled="" checked=""> task #2</li> +</ul> +``` + +Use class `TaskListItemsExtension` in artifact `commonmark-ext-task-list-items`. + See also -------- diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml new file mode 100644 index 000000000..52dc6c63f --- /dev/null +++ b/commonmark-ext-task-list-items/pom.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark-parent</artifactId> + <version>0.14.1-SNAPSHOT</version> + </parent> + + <artifactId>commonmark-ext-task-list-items</artifactId> + <name>commonmark-java extension for task list items</name> + <description>commonmark-java extension for task list items</description> + + <dependencies> + <dependency> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark</artifactId> + </dependency> + + <dependency> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark-test-util</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestEntries> + <Automatic-Module-Name>org.commonmark.ext.task.list.items</Automatic-Module-Name> + </manifestEntries> + </archive> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemMarker.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemMarker.java new file mode 100644 index 000000000..9eca59bc9 --- /dev/null +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemMarker.java @@ -0,0 +1,19 @@ +package org.commonmark.ext.task.list.items; + +import org.commonmark.node.CustomNode; + +/** + * A marker node indicating that a list item contains a task. + */ +public class TaskListItemMarker extends CustomNode { + + private final boolean checked; + + public TaskListItemMarker(boolean checked) { + this.checked = checked; + } + + public boolean isChecked() { + return checked; + } +} diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java new file mode 100644 index 000000000..feb68955e --- /dev/null +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java @@ -0,0 +1,52 @@ +package org.commonmark.ext.task.list.items; + +import org.commonmark.Extension; +import org.commonmark.ext.task.list.items.internal.TaskListItemHtmlNodeRenderer; +import org.commonmark.ext.task.list.items.internal.TaskListItemInlineParser; +import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.InlineParserFactory; +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; + +/** + * Extension for adding task list items. + * <p> + * Create it with {@link #create()} and then configure it on the builders + * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, + * {@link HtmlRenderer.Builder#extensions(Iterable)}). + * </p> + */ +public class TaskListItemsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { + + private TaskListItemsExtension() { + } + + public static Extension create() { + return new TaskListItemsExtension(); + } + + @Override + public void extend(Parser.Builder parserBuilder) { + parserBuilder.inlineParserFactory(new InlineParserFactory() { + + @Override + public InlineParser create(InlineParserContext inlineParserContext) { + return new TaskListItemInlineParser(inlineParserContext); + } + }); + } + + @Override + public void extend(HtmlRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { + @Override + public NodeRenderer create(HtmlNodeRendererContext context) { + return new TaskListItemHtmlNodeRenderer(context); + } + }); + } +} 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 new file mode 100644 index 000000000..563c2e828 --- /dev/null +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemHtmlNodeRenderer.java @@ -0,0 +1,52 @@ +package org.commonmark.ext.task.list.items.internal; + +import org.commonmark.ext.task.list.items.TaskListItemMarker; +import org.commonmark.node.Node; +import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.html.HtmlNodeRendererContext; +import org.commonmark.renderer.html.HtmlWriter; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +public class TaskListItemHtmlNodeRenderer implements NodeRenderer { + + private final HtmlNodeRendererContext context; + private final HtmlWriter html; + + public TaskListItemHtmlNodeRenderer(HtmlNodeRendererContext context) { + this.context = context; + this.html = context.getWriter(); + } + + @Override + public Set<Class<? extends Node>> getNodeTypes() { + return Collections.<Class<? extends Node>>singleton(TaskListItemMarker.class); + } + + @Override + public void render(Node node) { + if (node instanceof TaskListItemMarker) { + Map<String, String> attributes = context.extendAttributes(node, "input", Collections.<String, String>emptyMap()); + attributes.put("type", "checkbox"); + attributes.put("disabled", ""); + if (((TaskListItemMarker) node).isChecked()) { + attributes.put("checked", ""); + } + html.tag("input", attributes); + // Add a space after the input tag (as the next text node has been trimmed) + html.text(" "); + renderChildren(node); + } + } + + 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-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemInlineParser.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemInlineParser.java new file mode 100644 index 000000000..ae6b666b2 --- /dev/null +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemInlineParser.java @@ -0,0 +1,47 @@ +package org.commonmark.ext.task.list.items.internal; + +import org.commonmark.ext.task.list.items.TaskListItemMarker; +import org.commonmark.internal.InlineParserImpl; +import org.commonmark.node.ListItem; +import org.commonmark.node.Node; +import org.commonmark.parser.InlineParserContext; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An extension of {@link InlineParserImpl} which parses {@link TaskListItemMarker}s in {@link ListItem}s. + */ +public class TaskListItemInlineParser extends InlineParserImpl { + + private static final Pattern REGEX_BULLET = Pattern.compile("[\\-+*]?"); + private static final Pattern REGEX_TASK_LIST_ITEM = Pattern.compile("^(\\s*" + REGEX_BULLET + "\\s*)\\[([xX\\s])]\\s+(.*)"); + + public TaskListItemInlineParser(InlineParserContext inlineParserContext) { + super(inlineParserContext); + } + + @Override + public void parse(String input, Node node) { + Node parent = node.getParent(); + if (parent instanceof ListItem) { + Matcher matcher = REGEX_TASK_LIST_ITEM.matcher(input); + if (matcher.matches()) { + String checked = matcher.group(2); + boolean isChecked = Objects.equals(checked, "X") || Objects.equals(checked, "x"); + + // Add the task list item marker node as the first child of the parent. + parent.prependChild(new TaskListItemMarker(isChecked)); + + // Parse the node using the input after the task marker (in other words, group 3 from the matcher). + // (Note that the String gets trimmed in the call to super.parse(..), so we should add a space between + // the TaskListItemMarker and the text that follows it when we come to render it). + super.parse(matcher.group(3), node); + return; + } + } + + super.parse(input, node); + } +} 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 new file mode 100644 index 000000000..88f40c3af --- /dev/null +++ b/commonmark-ext-task-list-items/src/test/java/org/commonmark/ext/task/list/items/TaskListItemsTest.java @@ -0,0 +1,109 @@ +package org.commonmark.ext.task.list.items; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +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<Extension> EXTENSIONS = Collections.singleton(TaskListItemsExtension.create()); + private static final String HTML_CHECKED = "<input type=\"checkbox\" disabled=\"\" checked=\"\">"; + private static final String HTML_UNCHECKED = "<input type=\"checkbox\" disabled=\"\">"; + private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + + @Test + public void baseCase() { + assertRendering("- [x] this is *done*\n", "<ul>\n<li>" + HTML_CHECKED + " this is <em>done</em></li>\n</ul>\n"); + + assertRendering("- [ ] do this\n", "<ul>\n<li>" + HTML_UNCHECKED + " do this</li>\n</ul>\n"); + + assertRendering("- [x] foo\n" + + " - [ ] bar\n" + + " - [x] baz\n" + + "- [ ] bim", + "<ul>\n" + + "<li>" + HTML_CHECKED + " foo\n" + + "<ul>\n" + + "<li>" + HTML_UNCHECKED + " bar</li>\n" + + "<li>" + HTML_CHECKED + " baz</li>\n" + + "</ul>\n" + + "</li>\n" + + "<li>" + HTML_UNCHECKED + " bim</li>\n" + + "</ul>\n"); + + assertRendering("* [ ] do this\n* [ ] and this", + "<ul>\n<li>" + HTML_UNCHECKED + " do this</li>\n<li>" + HTML_UNCHECKED + " and this</li>\n</ul>\n"); + + assertRendering("+ [x] one\n" + + " - [ ] two\n" + + " * [x] three\n", + "<ul>\n" + + "<li>" + HTML_CHECKED + " one\n" + + "<ul>\n" + + "<li>" + HTML_UNCHECKED + " two\n" + + "<ul>\n" + + "<li>" + HTML_CHECKED + " three</li>\n" + + "</ul>\n" + + "</li>\n" + + "</ul>\n" + + "</li>\n" + + "</ul>\n"); + + assertRendering("TODO list\n" + + "---------\n" + + "- [ ] first task\n" + + "- [x] second task\n" + + "- [ ] third task\n\n" + + "Let me know when you are finished", + "<h2>TODO list</h2>\n" + + "<ul>\n" + + "<li>" + HTML_UNCHECKED + " first task</li>\n" + + "<li>" + HTML_CHECKED + " second task</li>\n" + + "<li>" + HTML_UNCHECKED + " third task</li>\n" + + "</ul>\n" + + "<p>Let me know when you are finished</p>\n"); + } + + @Test + public void notListItem() { + assertRendering("[x] this is not a task\n", "<p>[x] this is not a task</p>\n"); + assertRendering(" [ ] this is not a task either\n", "<p>[ ] this is not a task either</p>\n"); + } + + @Test + public void notValidTaskFormat() { + assertRendering("- [x]no space\n", "<ul>\n<li>[x]no space</li>\n</ul>\n"); + assertRendering("- [O] is not a _task_\n", "<ul>\n<li>[O] is not a <em>task</em></li>\n</ul>\n"); + assertRendering("* [] neither is this\n", "<ul>\n<li>[] neither is this</li>\n</ul>\n"); + assertRendering("* [ ] nor this\n" + + "* [XX] nor this\n", + "<ul>\n<li>[ ] nor this</li>\n<li>[XX] nor this</li>\n</ul>\n"); + assertRendering("+ [x]] is not a task\n", "<ul>\n<li>[x]] is not a task</li>\n</ul>\n"); + assertRendering("- [x isn't\n", "<ul>\n<li>[x isn't</li>\n</ul>\n"); + assertRendering("- [[x is not\n", "<ul>\n<li>[[x is not</li>\n</ul>\n"); + assertRendering("- x] nope\n", "<ul>\n<li>x] nope</li>\n</ul>\n"); + assertRendering("- x]] no way\n", "<ul>\n<li>x]] no way</li>\n</ul>\n"); + assertRendering("+ (x) sorry no\n", "<ul>\n<li>(x) sorry no</li>\n</ul>\n"); + assertRendering("+ {x} sorry not sorry\n", "<ul>\n<li>{x} sorry not sorry</li>\n</ul>\n"); + assertRendering("+ [[x]] nooo\n", "<ul>\n<li>[[x]] nooo</li>\n</ul>\n"); + assertRendering("+ text before [x] is not a task\n", "<ul>\n<li>text before [x] is not a task</li>\n</ul>\n"); + } + + @Test + public void withoutText() { + assertRendering("* [x] \n" + + "* [ ] \n", + "<ul>\n<li>" + HTML_CHECKED + " </li>\n<li>" + HTML_UNCHECKED + " </li>\n</ul>\n"); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index cc408c48d..f343a3d6e 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -36,6 +36,10 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> </dependency> + <dependency> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark-ext-task-list-items</artifactId> + </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> 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 2c4e4a84d..ed2d22cde 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 @@ -7,6 +7,7 @@ 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; @@ -26,6 +27,7 @@ public class SpecIntegrationTest extends SpecTestCase { InsExtension.create(), StrikethroughExtension.create(), TablesExtension.create(), + TaskListItemsExtension.create(), YamlFrontMatterExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. @@ -80,6 +82,10 @@ private static Map<String, String> getOverriddenExamples() { // Image attributes m.put("![text](/url.png){height=5 width=6}", "<img src=\"/url.png\" alt=\"text\" height=\"5\" width=\"6\" />"); + // Task list items + m.put("- [ ] task to do\n- [x] task done\n", "<ul>\n<li><input type=\"checkbox\" disabled=\"\"> task to do</li>\n" + + "<li><input type=\"checkbox\" disabled=\"\" checked=\"\"> task done</li>\n</ul>\n"); + return m; } diff --git a/pom.xml b/pom.xml index d37be75c0..ec4d8e37a 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ <module>commonmark-ext-heading-anchor</module> <module>commonmark-ext-image-attributes</module> <module>commonmark-ext-ins</module> + <module>commonmark-ext-task-list-items</module> <module>commonmark-ext-yaml-front-matter</module> <module>commonmark-integration-test</module> <module>commonmark-test-util</module> @@ -120,6 +121,11 @@ <artifactId>commonmark-ext-heading-anchor</artifactId> <version>0.14.1-SNAPSHOT</version> </dependency> + <dependency> + <groupId>com.atlassian.commonmark</groupId> + <artifactId>commonmark-ext-task-list-items</artifactId> + <version>0.14.1-SNAPSHOT</version> + </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> From 44c3acb0788578ee21ec6928004df39e42714e6a Mon Sep 17 00:00:00 2001 From: Frank Doherty <fdoherty@atlassian.com> Date: Tue, 28 Apr 2020 22:27:48 +1000 Subject: [PATCH 397/815] Some refactor based on pull request feedback: * use a PostProcessor rather than extending InlineParserImpl: * removed TaskListItemInlineParser * added TaskListItemPostProcessor to visit the first child Text node of a ListItem node and check for the task list format, adding a TaskListItemMarker to the ListItem when found * changed TaskListItemsExtension to use the new TaskListItemPostProcessor * small fix to TaskListItemHtmlNodeRenderer to where the extended attributes are set * updated TaskListItemsTest to move the test for "task markers without text" to notValidTaskFormat (as they are not valid tasks) --- .../list/items/TaskListItemsExtension.java | 13 +---- .../TaskListItemHtmlNodeRenderer.java | 5 +- .../internal/TaskListItemInlineParser.java | 47 ------------------ .../internal/TaskListItemPostProcessor.java | 49 +++++++++++++++++++ .../task/list/items/TaskListItemsTest.java | 8 +-- 5 files changed, 55 insertions(+), 67 deletions(-) delete mode 100644 commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemInlineParser.java create mode 100644 commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemPostProcessor.java diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java index feb68955e..3577f6700 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java @@ -2,10 +2,7 @@ import org.commonmark.Extension; import org.commonmark.ext.task.list.items.internal.TaskListItemHtmlNodeRenderer; -import org.commonmark.ext.task.list.items.internal.TaskListItemInlineParser; -import org.commonmark.parser.InlineParser; -import org.commonmark.parser.InlineParserContext; -import org.commonmark.parser.InlineParserFactory; +import org.commonmark.ext.task.list.items.internal.TaskListItemPostProcessor; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; @@ -31,13 +28,7 @@ public static Extension create() { @Override public void extend(Parser.Builder parserBuilder) { - parserBuilder.inlineParserFactory(new InlineParserFactory() { - - @Override - public InlineParser create(InlineParserContext inlineParserContext) { - return new TaskListItemInlineParser(inlineParserContext); - } - }); + parserBuilder.postProcessor(new TaskListItemPostProcessor()); } @Override 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 563c2e828..f2b2215f6 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 @@ -7,6 +7,7 @@ import org.commonmark.renderer.html.HtmlWriter; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -28,13 +29,13 @@ public Set<Class<? extends Node>> getNodeTypes() { @Override public void render(Node node) { if (node instanceof TaskListItemMarker) { - Map<String, String> attributes = context.extendAttributes(node, "input", Collections.<String, String>emptyMap()); + Map<String, String> attributes = new LinkedHashMap<>(); attributes.put("type", "checkbox"); attributes.put("disabled", ""); if (((TaskListItemMarker) node).isChecked()) { attributes.put("checked", ""); } - html.tag("input", attributes); + html.tag("input", context.extendAttributes(node, "input", attributes)); // Add a space after the input tag (as the next text node has been trimmed) html.text(" "); renderChildren(node); diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemInlineParser.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemInlineParser.java deleted file mode 100644 index ae6b666b2..000000000 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemInlineParser.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.commonmark.ext.task.list.items.internal; - -import org.commonmark.ext.task.list.items.TaskListItemMarker; -import org.commonmark.internal.InlineParserImpl; -import org.commonmark.node.ListItem; -import org.commonmark.node.Node; -import org.commonmark.parser.InlineParserContext; - -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * An extension of {@link InlineParserImpl} which parses {@link TaskListItemMarker}s in {@link ListItem}s. - */ -public class TaskListItemInlineParser extends InlineParserImpl { - - private static final Pattern REGEX_BULLET = Pattern.compile("[\\-+*]?"); - private static final Pattern REGEX_TASK_LIST_ITEM = Pattern.compile("^(\\s*" + REGEX_BULLET + "\\s*)\\[([xX\\s])]\\s+(.*)"); - - public TaskListItemInlineParser(InlineParserContext inlineParserContext) { - super(inlineParserContext); - } - - @Override - public void parse(String input, Node node) { - Node parent = node.getParent(); - if (parent instanceof ListItem) { - Matcher matcher = REGEX_TASK_LIST_ITEM.matcher(input); - if (matcher.matches()) { - String checked = matcher.group(2); - boolean isChecked = Objects.equals(checked, "X") || Objects.equals(checked, "x"); - - // Add the task list item marker node as the first child of the parent. - parent.prependChild(new TaskListItemMarker(isChecked)); - - // Parse the node using the input after the task marker (in other words, group 3 from the matcher). - // (Note that the String gets trimmed in the call to super.parse(..), so we should add a space between - // the TaskListItemMarker and the text that follows it when we come to render it). - super.parse(matcher.group(3), node); - return; - } - } - - super.parse(input, node); - } -} diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemPostProcessor.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemPostProcessor.java new file mode 100644 index 000000000..b95c2e30d --- /dev/null +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/internal/TaskListItemPostProcessor.java @@ -0,0 +1,49 @@ +package org.commonmark.ext.task.list.items.internal; + +import org.commonmark.ext.task.list.items.TaskListItemMarker; +import org.commonmark.node.*; +import org.commonmark.parser.PostProcessor; + +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TaskListItemPostProcessor implements PostProcessor { + + private static final Pattern REGEX_TASK_LIST_ITEM = Pattern.compile("^\\[([xX\\s])]\\s+(.*)"); + + @Override + public Node process(Node node) { + TaskListItemVisitor visitor = new TaskListItemVisitor(); + node.accept(visitor); + return node; + } + + private static class TaskListItemVisitor extends AbstractVisitor { + + @Override + public void visit(ListItem listItem) { + Node child = listItem.getFirstChild(); + if (child instanceof Paragraph) { + Node node = child.getFirstChild(); + if (node instanceof Text) { + Text textNode = (Text) node; + Matcher matcher = REGEX_TASK_LIST_ITEM.matcher(textNode.getLiteral()); + if (matcher.matches()) { + String checked = matcher.group(1); + boolean isChecked = Objects.equals(checked, "X") || Objects.equals(checked, "x"); + + // Add the task list item marker node as the first child of the list item. + listItem.prependChild(new TaskListItemMarker(isChecked)); + + // Parse the node using the input after the task marker (in other words, group 2 from the matcher). + // (Note that the String has been trimmed, so we should add a space between the + // TaskListItemMarker and the text that follows it when we come to render it). + textNode.setLiteral(matcher.group(2)); + } + } + } + visitChildren(listItem); + } + } +} 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 88f40c3af..3e6c6a259 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 @@ -93,13 +93,7 @@ public void notValidTaskFormat() { assertRendering("+ {x} sorry not sorry\n", "<ul>\n<li>{x} sorry not sorry</li>\n</ul>\n"); assertRendering("+ [[x]] nooo\n", "<ul>\n<li>[[x]] nooo</li>\n</ul>\n"); assertRendering("+ text before [x] is not a task\n", "<ul>\n<li>text before [x] is not a task</li>\n</ul>\n"); - } - - @Test - public void withoutText() { - assertRendering("* [x] \n" + - "* [ ] \n", - "<ul>\n<li>" + HTML_CHECKED + " </li>\n<li>" + HTML_UNCHECKED + " </li>\n</ul>\n"); + assertRendering("* [x] \n* [ ] \n", "<ul>\n<li>[x]</li>\n<li>[ ]</li>\n</ul>\n"); } @Override From ffdf6ef578e16f02d51426c98d494c2d5305b189 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 15 May 2020 16:23:07 +1000 Subject: [PATCH 398/815] Add test for character references without semicolons --- .../test/java/org/commonmark/test/HtmlRendererTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java index 04b493cba..18ce967b2 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlRendererTest.java @@ -50,6 +50,13 @@ public void textEscaping() { assertEquals("<p>escaping: & < > " '</p>\n", rendered); } + @Test + public void characterReferencesWithoutSemicolonsShouldNotBeParsedShouldBeEscaped() { + String input = "[example](javascript:alert('XSS'))"; + String rendered = defaultRenderer().render(parse(input)); + assertEquals("<p><a href=\"&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29\">example</a></p>\n", rendered); + } + @Test public void attributeEscaping() { Paragraph paragraph = new Paragraph(); From 8be430307ef2fe9c84518ab1fd902381217f254b Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 15 May 2020 16:43:11 +1000 Subject: [PATCH 399/815] Prepare for 0.15.0 --- CHANGELOG.md | 14 ++++++++++++++ README.md | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acd7c044a..3c3964cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ 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.15.0] - 2020-05-15 +### Added +- Extension for width/height attributes for images, thanks @dohertyfjatl + - Syntax: `![text](/url.png){width=640 height=480}` + - Use class `ImageAttributesExtension` in artifact `commonmark-ext-image-attributes` +- Extension for task lists (GitHub-style), thanks @dohertyfjatl + - Syntax: + ``` + - [x] task #1` + - [ ] task #2` + ``` + - Use class `TaskListItemsExtension` in artifact `commonmark-ext-task-list-items` + ## [0.14.0] - 2020-01-22 ### Added - Add `sanitizeUrls` to `HtmlRenderer.Builder` to enable sanitizing URLs @@ -269,6 +282,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.15.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.14.0...commonmark-parent-0.15.0 [0.14.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.1...commonmark-parent-0.14.0 [0.13.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.0...commonmark-parent-0.13.1 [0.13.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.12.1...commonmark-parent-0.13.0 diff --git a/README.md b/README.md index 6ef0bbe07..2ee998193 100644 --- a/README.md +++ b/README.md @@ -326,14 +326,14 @@ Adds support for specifying attributes (specifically height and width) for image The attribute elements are given as `key=value` pairs inside curly braces `{ }` after the image node to which they apply, for example: ``` -![text](/url.png){height=5 width=6} +![text](/url.png){width=640 height=480} ``` will be rendered as: ``` -<img src="/url.png" alt="text" height="5" width="6" /> +<img src="/url.png" alt="text" width="640" height="480" /> ``` -Use class `StylesExtension` in artifact `commonmark-ext-image-attributes`. +Use class `ImageAttributesExtension` in artifact `commonmark-ext-image-attributes`. Note: since this extension uses curly braces `{` `}` as its delimiters (in `StylesDelimiterProcessor`), this means that other delimiter processors *cannot* use curly braces for delimiting. From e518a166af7eea1578988787c236ba4121f042ad Mon Sep 17 00:00:00 2001 From: alex <alexkarezin@gmail.com> Date: Tue, 19 May 2020 08:30:31 -0400 Subject: [PATCH 400/815] Adding a link to the README header and diagrams to CONTRIBUTING.md. The linked dashboard describes overall repository structure. The goal is to help new developers explore project and understand architecture/dependencies. Direct link: https://www.sourcespy.com/github/atlassiancommonmarkjava/ --- CONTRIBUTING.md | 8 ++++++++ README.md | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dbf3be13f..86c141127 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,14 @@ existing issues with label "help wanted". For bigger changes, make sure you start a discussion first by creating an issue and explaining the intended change. +The [sourcespy dashboard](https://sourcespy.com/github/atlassiancommonmarkjava/) +provides a high level overview of the repository including +[class diagram](https://sourcespy.com/github/atlassiancommonmarkjava/xx-omodel-.html), +[module dependencies](https://sourcespy.com/github/atlassiancommonmarkjava/xx-omodulesc-.html), +[module hierarchy](https://sourcespy.com/github/atlassiancommonmarkjava/xx-omodules-.html), +[external libraries](https://sourcespy.com/github/atlassiancommonmarkjava/xx-ojavalibs-.html), +and other components of the system. + CLA --- diff --git a/README.md b/README.md index 2ee998193..77b710ce2 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Java library for parsing and rendering [Markdown] text according to the [![javadoc](https://www.javadoc.io/badge/com.atlassian.commonmark/commonmark.svg?color=blue)](https://www.javadoc.io/doc/com.atlassian.commonmark/commonmark) [![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) [![codecov](https://codecov.io/gh/atlassian/commonmark-java/branch/master/graph/badge.svg)](https://codecov.io/gh/atlassian/commonmark-java) +[![SourceSpy Dashboard](https://sourcespy.com/shield.svg)](https://sourcespy.com/github/atlassiancommonmarkjava/) Introduction ------------ @@ -370,7 +371,7 @@ See also Contributing ------------ -See CONTRIBUTING.md file. +See [CONTRIBUTING.md](CONTRIBUTING.md) file. License ------- From 7c578b2750213f5c7f38d9cea63a722a75989a9f Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 21 May 2020 17:58:06 +1000 Subject: [PATCH 401/815] Update date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c3964cdb..fe2d164c9 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. -## [0.15.0] - 2020-05-15 +## [0.15.0] - 2020-05-21 ### Added - Extension for width/height attributes for images, thanks @dohertyfjatl - Syntax: `![text](/url.png){width=640 height=480}` From 6ed1491552f56d2f3e7868fca7b84dc1951a0ed8 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 21 May 2020 20:13:22 +1000 Subject: [PATCH 402/815] Configure separate push URL in developerConnection This is needed for releasing on internal build server until we have a better way to push to Maven Central. --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ec4d8e37a..fca41d85e 100644 --- a/pom.xml +++ b/pom.xml @@ -205,7 +205,8 @@ <scm> <connection>scm:git:git@github.com:atlassian/commonmark-java.git</connection> - <developerConnection>scm:git:git@github.com:atlassian/commonmark-java.git</developerConnection> + <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> + <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> <tag>HEAD</tag> </scm> From 821e2ebc8d4bb114530fa0310f01e22b55c22976 Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Thu, 21 May 2020 10:26:13 +0000 Subject: [PATCH 403/815] [maven-release-plugin] prepare release commonmark-parent-0.15.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 80dd347f1..c0efb4c95 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 3df0581e6..ff65bd9a9 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index ad69f1795..9cd5da9f1 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index ca797b7be..446c4157c 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 9fe11a3b6..22bd8c11a 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 6f8c59318..90b7336ac 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 52dc6c63f..f088dd0ee 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index d4534ec55..7d6c6b467 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index f343a3d6e..b6009c968 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index efb27f330..8fafe8577 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 4850b2b45..fda0bb416 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index fca41d85e..c3b11ef2f 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -89,52 +89,52 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.14.1-SNAPSHOT</version> + <version>0.15.0</version> </dependency> <!-- Common test dependencies --> @@ -208,7 +208,7 @@ <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.15.0</tag> </scm> </project> From 8462787d8a31c1cf89cd6094e9b877cbd1247aa8 Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Thu, 21 May 2020 10:26:14 +0000 Subject: [PATCH 404/815] [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 c0efb4c95..dceed69c1 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index ff65bd9a9..3759a24e2 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 9cd5da9f1..9c67e1f7f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 446c4157c..36326141e 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 22bd8c11a..393db6d5d 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 90b7336ac..250ce7dff 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index f088dd0ee..bd57f5300 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 7d6c6b467..ce34640b4 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index b6009c968..4d0a562ff 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 8fafe8577..4e4ced4e4 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index fda0bb416..a6eb2e437 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index c3b11ef2f..bf5e7f061 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -89,52 +89,52 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.15.0</version> + <version>0.15.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -208,7 +208,7 @@ <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>commonmark-parent-0.15.0</tag> + <tag>HEAD</tag> </scm> </project> From 3de7a084b74d81d25f4106850f6780560a27c39f Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 22 May 2020 11:07:06 +1000 Subject: [PATCH 405/815] Bump version references --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 2 +- .../ext/image/attributes/ImageAttributesExtension.java | 2 ++ .../ext/task/list/items/TaskListItemsExtension.java | 2 ++ 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2ee998193..230490d23 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.14.0</version> + <version>0.15.0</version> </dependency> ``` @@ -232,7 +232,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.14.0</version> + <version>0.15.0</version> </dependency> ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index 781568ce1..08fac5480 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -8,7 +8,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.14.0" >> test.properties +echo "version.maven=0.15.0" >> test.properties echo "version.maven_autolink=0.10.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 452556a63..3aa0cbdb3 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,7 +28,7 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.14.0 +version.maven=0.15.0 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.10.0 diff --git a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java index 1edfb443b..28c6abab2 100644 --- a/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java +++ b/commonmark-ext-image-attributes/src/main/java/org/commonmark/ext/image/attributes/ImageAttributesExtension.java @@ -16,6 +16,8 @@ * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, * {@link HtmlRenderer.Builder#extensions(Iterable)}). * </p> + * + * @since 0.15.0 */ public class ImageAttributesExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { diff --git a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java index 3577f6700..9bf0a2155 100644 --- a/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java +++ b/commonmark-ext-task-list-items/src/main/java/org/commonmark/ext/task/list/items/TaskListItemsExtension.java @@ -16,6 +16,8 @@ * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, * {@link HtmlRenderer.Builder#extensions(Iterable)}). * </p> + * + * @since 0.15.0 */ public class TaskListItemsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { From d3220a96ecfa30ecf4682a20b03b56faaa89bbcb Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 29 May 2020 20:03:40 +1000 Subject: [PATCH 406/815] Add text content rendering support for `InsExtension` See #170. --- CHANGELOG.md | 4 +++ .../org/commonmark/ext/ins/InsExtension.java | 26 +++++++++++--- .../ext/ins/internal/InsHtmlNodeRenderer.java | 36 +++++++++++++++++++ .../ext/ins/internal/InsNodeRenderer.java | 30 +--------------- .../internal/InsTextContentNodeRenderer.java | 27 ++++++++++++++ .../java/org/commonmark/ext/ins/InsTest.java | 9 +++++ 6 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java create mode 100644 commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsTextContentNodeRenderer.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fe2d164c9..0d3111822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## Unreleased +### Added +- Add text content rendering support for `InsExtension` + ## [0.15.0] - 2020-05-21 ### Added - Extension for width/height attributes for images, thanks @dohertyfjatl 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 831cd75c8..2f980d93b 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 @@ -2,12 +2,16 @@ import org.commonmark.Extension; import org.commonmark.ext.ins.internal.InsDelimiterProcessor; -import org.commonmark.ext.ins.internal.InsNodeRenderer; +import org.commonmark.ext.ins.internal.InsHtmlNodeRenderer; +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.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.NodeRenderer; +import org.commonmark.renderer.text.TextContentNodeRendererContext; +import org.commonmark.renderer.text.TextContentNodeRendererFactory; +import org.commonmark.renderer.text.TextContentRenderer; /** * Extension for ins using ++ @@ -20,7 +24,9 @@ * The parsed ins text regions are turned into {@link Ins} nodes. * </p> */ -public class InsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension { +public class InsExtension implements Parser.ParserExtension, + HtmlRenderer.HtmlRendererExtension, + TextContentRenderer.TextContentRendererExtension { private InsExtension() { } @@ -39,7 +45,17 @@ public void extend(HtmlRenderer.Builder rendererBuilder) { rendererBuilder.nodeRendererFactory(new HtmlNodeRendererFactory() { @Override public NodeRenderer create(HtmlNodeRendererContext context) { - return new InsNodeRenderer(context); + return new InsHtmlNodeRenderer(context); + } + }); + } + + @Override + public void extend(TextContentRenderer.Builder rendererBuilder) { + rendererBuilder.nodeRendererFactory(new TextContentNodeRendererFactory() { + @Override + public NodeRenderer create(TextContentNodeRendererContext context) { + return new InsTextContentNodeRenderer(context); } }); } 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 new file mode 100644 index 000000000..139a0b2cd --- /dev/null +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsHtmlNodeRenderer.java @@ -0,0 +1,36 @@ +package org.commonmark.ext.ins.internal; + +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 InsHtmlNodeRenderer extends InsNodeRenderer { + + private final HtmlNodeRendererContext context; + private final HtmlWriter html; + + public InsHtmlNodeRenderer(HtmlNodeRendererContext context) { + this.context = context; + this.html = context.getWriter(); + } + + @Override + public void render(Node node) { + Map<String, String> attributes = context.extendAttributes(node, "ins", Collections.<String, String>emptyMap()); + html.tag("ins", attributes); + renderChildren(node); + html.tag("/ins"); + } + + 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/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java index faf15cae7..0a44a2826 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsNodeRenderer.java @@ -1,44 +1,16 @@ package org.commonmark.ext.ins.internal; import org.commonmark.ext.ins.Ins; -import org.commonmark.renderer.html.HtmlWriter; -import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; import java.util.Collections; -import java.util.Map; import java.util.Set; -public class InsNodeRenderer implements NodeRenderer { - - private final HtmlNodeRendererContext context; - private final HtmlWriter html; - - public InsNodeRenderer(HtmlNodeRendererContext context) { - this.context = context; - this.html = context.getWriter(); - } +abstract class InsNodeRenderer implements NodeRenderer { @Override public Set<Class<? extends Node>> getNodeTypes() { return Collections.<Class<? extends Node>>singleton(Ins.class); } - - @Override - public void render(Node node) { - Map<String, String> attributes = context.extendAttributes(node, "ins", Collections.<String, String>emptyMap()); - html.tag("ins", attributes); - renderChildren(node); - html.tag("/ins"); - } - - 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/main/java/org/commonmark/ext/ins/internal/InsTextContentNodeRenderer.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsTextContentNodeRenderer.java new file mode 100644 index 000000000..f30947c93 --- /dev/null +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsTextContentNodeRenderer.java @@ -0,0 +1,27 @@ +package org.commonmark.ext.ins.internal; + +import org.commonmark.node.Node; +import org.commonmark.renderer.text.TextContentNodeRendererContext; + +public class InsTextContentNodeRenderer extends InsNodeRenderer { + + private final TextContentNodeRendererContext context; + + public InsTextContentNodeRenderer(TextContentNodeRendererContext context) { + this.context = context; + } + + @Override + public void render(Node node) { + renderChildren(node); + } + + 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/InsTest.java b/commonmark-ext-ins/src/test/java/org/commonmark/ext/ins/InsTest.java index 2b97431c3..05c8b41c5 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 @@ -4,6 +4,7 @@ import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; @@ -17,6 +18,8 @@ public class InsTest extends RenderingTestCase { private static final Set<Extension> EXTENSIONS = Collections.singleton(InsExtension.create()); private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).build(); + private static final TextContentRenderer CONTENT_RENDERER = TextContentRenderer.builder() + .extensions(EXTENSIONS).build(); @Test public void onePlusIsNotEnough() { @@ -80,6 +83,12 @@ public void delimited() { assertEquals("++", ins.getClosingDelimiter()); } + @Test + public void textContentRenderer() { + Node document = PARSER.parse("++foo++"); + assertEquals("foo", CONTENT_RENDERER.render(document)); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); From 61527c1d152d54630fc612ab0a43475370e56260 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 29 May 2020 20:56:55 +1000 Subject: [PATCH 407/815] Prepare for 0.15.1 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d3111822..9cd5a9046 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.15.1] - 2020-05-29 ### Added - Add text content rendering support for `InsExtension` @@ -286,6 +286,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.15.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.0...commonmark-parent-0.15.1 [0.15.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.14.0...commonmark-parent-0.15.0 [0.14.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.1...commonmark-parent-0.14.0 [0.13.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.0...commonmark-parent-0.13.1 From 220f85b35202d66c0d967fc37803b6a4c87cfbc7 Mon Sep 17 00:00:00 2001 From: Bamboo Build User <deployment-bamboo-bot@atlassian.com> Date: Fri, 29 May 2020 10:59:22 +0000 Subject: [PATCH 408/815] [maven-release-plugin] prepare release commonmark-parent-0.15.1 --- 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 dceed69c1..2d906f47e 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 3759a24e2..afc0d520f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 9c67e1f7f..1633fd5ec 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 36326141e..19551bf5c 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 393db6d5d..3c3a0f97d 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 250ce7dff..74aefdf70 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index bd57f5300..8d7c7fd9d 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index ce34640b4..6626247d6 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 4d0a562ff..0b5f00fec 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 4e4ced4e4..5834c7689 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index a6eb2e437..203b0a0a9 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index bf5e7f061..5aa755293 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -89,52 +89,52 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.15.1-SNAPSHOT</version> + <version>0.15.1</version> </dependency> <!-- Common test dependencies --> @@ -208,7 +208,7 @@ <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.15.1</tag> </scm> </project> From f4db757d33ce6f2deda8dc44a87bd943a5016740 Mon Sep 17 00:00:00 2001 From: Bamboo Build User <deployment-bamboo-bot@atlassian.com> Date: Fri, 29 May 2020 10:59:25 +0000 Subject: [PATCH 409/815] [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 2d906f47e..6f15a9386 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index afc0d520f..7faf16d84 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 1633fd5ec..9332ef1f2 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 19551bf5c..5b2fd43d4 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 3c3a0f97d..6e9d0e103 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 74aefdf70..4d822b7b9 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 8d7c7fd9d..8fb3e0e0a 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 6626247d6..5bf99bee2 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 0b5f00fec..7732645df 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 5834c7689..4ce25a747 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 203b0a0a9..03fcc85ff 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 5aa755293..a16eb9913 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -89,52 +89,52 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.15.1</version> + <version>0.15.2-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -208,7 +208,7 @@ <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>commonmark-parent-0.15.1</tag> + <tag>HEAD</tag> </scm> </project> From e8fe5b175f693507b70445b124da1c19e19da1c7 Mon Sep 17 00:00:00 2001 From: Matthieu Brouillard <matthieu@brouillard.fr> Date: Tue, 9 Jun 2020 15:16:14 +0200 Subject: [PATCH 410/815] add third-party extensions paragraph adding project 'commonmark-ext-notifications' (https://github.com/McFoggy/commonmark-ext-notifications) as the first third party extension in the list closes #173 --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 53f3b4745..9302e306c 100644 --- a/README.md +++ b/README.md @@ -362,6 +362,12 @@ will be rendered as: Use class `TaskListItemsExtension` in artifact `commonmark-ext-task-list-items`. +### Third-party extensions + +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` + See also -------- From bb30626c80d9abfbf899bd9d069ce01c607e5997 Mon Sep 17 00:00:00 2001 From: Evgeny Naumenko <evgeny.naumenko@jetbrains.com> Date: Sun, 19 Jul 2020 22:07:59 +0300 Subject: [PATCH 411/815] Image attributes extension is not expected to alter unrelated text nodes --- .../attributes/internal/ImageAttributesDelimiterProcessor.java | 2 +- .../commonmark/ext/image/attributes/ImageAttributesTest.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) 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 839015186..68e3f7847 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 @@ -95,6 +95,6 @@ public void process(Text opener, Text closer, int delimiterCount) { } else { opener.getPrevious().insertAfter(new Text("" + getOpeningCharacter())); } - closer.getParent().appendChild(new Text("" + getClosingCharacter())); + closer.insertAfter(new Text("" + getClosingCharacter())); } } 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 cfffc8084..206fd9f47 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 @@ -109,6 +109,8 @@ public void textNodesAreUnchanged() { assertRendering("x {height=3 width=4}\n", "<p>x {height=3 width=4}</p>\n"); assertRendering("\\documentclass[12pt]{article}\n", "<p>\\documentclass[12pt]{article}</p>\n"); assertRendering("some *text*{height=3 width=4}\n", "<p>some <em>text</em>{height=3 width=4}</p>\n"); + assertRendering("{NN} text", "<p>{NN} text</p>\n"); + assertRendering("{}", "<p>{}</p>\n"); } @Override From 9956ac983afa12864a027c431ddeba911602e2c0 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 20 Jul 2020 20:31:25 +1000 Subject: [PATCH 412/815] Changelog for 0.15.2 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cd5a9046..da926a359 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. +## [0.15.2] - 2020-07-20 +### Fixed +- image-attributes extension: Fix unexpected altering of text in case + parsing of attributes fails, e.g. `{NN} text` -> `{NN text}`, thanks @jk1 + ## [0.15.1] - 2020-05-29 ### Added - Add text content rendering support for `InsExtension` @@ -286,6 +291,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.15.2]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.1...commonmark-parent-0.15.2 [0.15.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.0...commonmark-parent-0.15.1 [0.15.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.14.0...commonmark-parent-0.15.0 [0.14.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.1...commonmark-parent-0.14.0 From c0b68a64b126cd008fff936816bb8c716482c2c0 Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Mon, 20 Jul 2020 10:34:44 +0000 Subject: [PATCH 413/815] [maven-release-plugin] prepare release commonmark-parent-0.15.2 --- 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 6f15a9386..17eb799bf 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 7faf16d84..1bf396178 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 9332ef1f2..0f6fd4411 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 5b2fd43d4..cf1b33acf 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 6e9d0e103..4ab6a0093 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 4d822b7b9..fe340208d 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 8fb3e0e0a..20b4da3e3 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 5bf99bee2..bbb22b653 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 7732645df..f5eb8c233 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 4ce25a747..1e2bdad74 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 03fcc85ff..1a9f1385e 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index a16eb9913..7f3adec72 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -89,52 +89,52 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.15.2-SNAPSHOT</version> + <version>0.15.2</version> </dependency> <!-- Common test dependencies --> @@ -208,7 +208,7 @@ <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.15.2</tag> </scm> </project> From 2d366c07f206acdece59cc8743070b32302e96ac Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Mon, 20 Jul 2020 10:34:47 +0000 Subject: [PATCH 414/815] [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 17eb799bf..6ace6dcdf 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 1bf396178..830679c7e 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 0f6fd4411..f79d47846 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index cf1b33acf..68280c177 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 4ab6a0093..a3bfdce37 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index fe340208d..0c4a8b863 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 20b4da3e3..85724e508 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index bbb22b653..8e6455a16 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index f5eb8c233..84e395140 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 1e2bdad74..527583c4c 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 1a9f1385e..4bc665269 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 7f3adec72..d81dc186a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -89,52 +89,52 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.15.2</version> + <version>0.15.3-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -208,7 +208,7 @@ <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>commonmark-parent-0.15.2</tag> + <tag>HEAD</tag> </scm> </project> From 5c14fbecbdb5427adcb2f7bc3ad71828a46f2423 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 20 Jul 2020 20:38:14 +1000 Subject: [PATCH 415/815] Bump version references --- README.md | 4 ++-- commonmark-android-test/.travis.sh | 2 +- commonmark-android-test/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9302e306c..b981e7089 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.15.0</version> + <version>0.15.2</version> </dependency> ``` @@ -233,7 +233,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.15.0</version> + <version>0.15.2</version> </dependency> ``` diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/.travis.sh index 08fac5480..44426d7fe 100755 --- a/commonmark-android-test/.travis.sh +++ b/commonmark-android-test/.travis.sh @@ -8,7 +8,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.15.0" >> test.properties +echo "version.maven=0.15.2" >> test.properties echo "version.maven_autolink=0.10.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 3aa0cbdb3..a4c498fc4 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,7 +28,7 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.15.0 +version.maven=0.15.2 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.10.0 From 090f5e2811c3f8501d82a8f7fc5b9f81893238e8 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 20 Jul 2020 20:42:24 +1000 Subject: [PATCH 416/815] Fix typo in changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da926a359..64e160aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,8 +23,8 @@ with the exception that 0.x versions can break between minor versions. - Extension for task lists (GitHub-style), thanks @dohertyfjatl - Syntax: ``` - - [x] task #1` - - [ ] task #2` + - [x] task #1 + - [ ] task #2 ``` - Use class `TaskListItemsExtension` in artifact `commonmark-ext-task-list-items` From 6bea5287106b0f8358b9e12d2be8a876b39e5e7c Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 30 Jul 2020 20:49:54 +1000 Subject: [PATCH 417/815] Improve ProfilingMain --- .../java/org/commonmark/ProfilingMain.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/ProfilingMain.java b/commonmark/src/test/java/org/commonmark/ProfilingMain.java index 0f0c08153..31ae2b5f5 100644 --- a/commonmark/src/test/java/org/commonmark/ProfilingMain.java +++ b/commonmark/src/test/java/org/commonmark/ProfilingMain.java @@ -1,9 +1,11 @@ package org.commonmark; +import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.TestResources; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -15,17 +17,29 @@ public class ProfilingMain { private static final HtmlRenderer RENDERER = HtmlRenderer.builder().build(); public static void main(String[] args) throws Exception { - System.out.println("Started up, attach profiler now"); - Thread.sleep(10_000); - System.out.println("Parsing and rendering"); - parseAndRender(Collections.singletonList(SPEC)); - System.out.println("Finished parsing"); + System.out.println("Attach profiler, then press enter to start parsing."); + System.in.read(); + System.out.println("Parsing"); + List<Node> nodes = parse(Collections.singletonList(SPEC)); + System.out.println("Finished parsing, press enter to start rendering"); + System.in.read(); + System.out.println(render(nodes)); + System.out.println("Finished rendering"); } - private static long parseAndRender(List<String> examples) { - long length = 0; + private static List<Node> parse(List<String> examples) { + List<Node> nodes = new ArrayList<>(); for (String example : examples) { - String result = RENDERER.render(PARSER.parse(example)); + Node doc = PARSER.parse(example); + nodes.add(doc); + } + return nodes; + } + + private static long render(List<Node> examples) { + long length = 0; + for (Node example : examples) { + String result = RENDERER.render(example); length += result.length(); } return length; From eff0fd59080ddb33549e16eab6c35efc62902862 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 30 Jul 2020 21:58:58 +1000 Subject: [PATCH 418/815] Optimize block parser fields in DocumentParser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Get rid of unnecessary subList and new ArrayList for each line * Replace set of allBlockParsers with list, by only adding block parsers when we close them (which excludes replaced block parsers) Nice speedup, before: Benchmark Mode Cnt Score Error Units SpecBenchmark.parseExamples thrpt 100 571.107 ± 4.729 ops/s SpecBenchmark.parseWholeSpec thrpt 100 346.729 ± 1.741 ops/s After: Benchmark Mode Cnt Score Error Units SpecBenchmark.parseExamples thrpt 100 719.877 ± 11.851 ops/s SpecBenchmark.parseWholeSpec thrpt 100 407.535 ± 3.174 ops/s --- .../commonmark/internal/DocumentParser.java | 77 +++++++++---------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index d1b43a7b4..700519ac9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -66,9 +66,8 @@ public class DocumentParser implements ParserState { private final DocumentBlockParser documentBlockParser; private final Map<String, LinkReferenceDefinition> definitions = new LinkedHashMap<>(); - private List<BlockParser> activeBlockParsers = new ArrayList<>(); - // LinkedHashSet to have a deterministic order - private Set<BlockParser> allBlockParsers = new LinkedHashSet<>(); + private final List<BlockParser> activeBlockParsers = new ArrayList<>(); + private final List<BlockParser> allBlockParsers = new ArrayList<>(); public DocumentParser(List<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory, List<DelimiterProcessor> delimiterProcessors) { @@ -179,18 +178,17 @@ private void incorporateLine(CharSequence ln) { columnIsInTab = false; // For each containing block, try to parse the associated line start. - // Bail out on failure: container will point to the last matching block. - // Set all_matched to false if not all containers match. - // The document will always match, can be skipped + // The document will always match, so we can skip the first block parser and start at 1 matches int matches = 1; - for (BlockParser blockParser : activeBlockParsers.subList(1, activeBlockParsers.size())) { + for (int i = 1; i < activeBlockParsers.size(); i++) { + BlockParser blockParser = activeBlockParsers.get(i); findNextNonSpace(); BlockContinue result = blockParser.tryContinue(this); if (result instanceof BlockContinueImpl) { BlockContinueImpl blockContinue = (BlockContinueImpl) result; if (blockContinue.isFinalize()) { - finalize(blockParser); + closeBlockParsers(activeBlockParsers.size() - i); return; } else { if (blockContinue.getNewIndex() != -1) { @@ -205,10 +203,9 @@ private void incorporateLine(CharSequence ln) { } } - List<BlockParser> unmatchedBlockParsers = new ArrayList<>(activeBlockParsers.subList(matches, activeBlockParsers.size())); - BlockParser lastMatchedBlockParser = activeBlockParsers.get(matches - 1); - BlockParser blockParser = lastMatchedBlockParser; - boolean allClosed = unmatchedBlockParsers.isEmpty(); + int unmatchedBlocks = activeBlockParsers.size() - matches; + BlockParser blockParser = activeBlockParsers.get(matches - 1); + boolean startedNewBlock = false; // Unless last matched container is a code block, try new container starts, // adding children to the last matched container: @@ -228,9 +225,12 @@ private void incorporateLine(CharSequence ln) { break; } - if (!allClosed) { - finalizeBlocks(unmatchedBlockParsers); - allClosed = true; + startedNewBlock = true; + + // We're starting a new block. If we have any previous blocks that need to be closed, we need to do it now. + if (unmatchedBlocks > 0) { + closeBlockParsers(unmatchedBlocks); + unmatchedBlocks = 0; } if (blockStart.getNewIndex() != -1) { @@ -253,7 +253,7 @@ private void incorporateLine(CharSequence ln) { // appropriate block. // First check for a lazy paragraph continuation: - if (!allClosed && !isBlank() && + if (!startedNewBlock && !isBlank() && getActiveBlockParser().canHaveLazyContinuationLines()) { // lazy paragraph continuation addLine(); @@ -261,8 +261,8 @@ private void incorporateLine(CharSequence ln) { } else { // finalize any blocks not matched - if (!allClosed) { - finalizeBlocks(unmatchedBlockParsers); + if (unmatchedBlocks > 0) { + closeBlockParsers(unmatchedBlocks); } if (!blockParser.isContainer()) { @@ -387,10 +387,6 @@ private BlockStartImpl findBlockStart(BlockParser blockParser) { * definitions. */ private void finalize(BlockParser blockParser) { - if (getActiveBlockParser() == blockParser) { - deactivateBlockParser(); - } - if (blockParser instanceof ParagraphParser) { addDefinitionsFrom((ParagraphParser) blockParser); } @@ -429,7 +425,7 @@ private void processInlines() { */ private <T extends BlockParser> T addChild(T blockParser) { while (!getActiveBlockParser().canContain(blockParser.getBlock())) { - finalize(getActiveBlockParser()); + closeBlockParsers(1); } getActiveBlockParser().getBlock().appendChild(blockParser.getBlock()); @@ -440,17 +436,15 @@ private <T extends BlockParser> T addChild(T blockParser) { private void activateBlockParser(BlockParser blockParser) { activeBlockParsers.add(blockParser); - allBlockParsers.add(blockParser); } - private void deactivateBlockParser() { - activeBlockParsers.remove(activeBlockParsers.size() - 1); + private BlockParser deactivateBlockParser() { + return activeBlockParsers.remove(activeBlockParsers.size() - 1); } private void prepareActiveBlockParserForReplacement() { - BlockParser old = getActiveBlockParser(); - deactivateBlockParser(); - allBlockParsers.remove(old); + // Note that we don't want to parse inlines or finalize this block, as it's getting replaced. + BlockParser old = deactivateBlockParser(); if (old instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) old; @@ -465,20 +459,21 @@ private void prepareActiveBlockParserForReplacement() { old.getBlock().unlink(); } - /** - * Finalize blocks of previous line. Returns true. - */ - private void finalizeBlocks(List<BlockParser> blockParsers) { - for (int i = blockParsers.size() - 1; i >= 0; i--) { - BlockParser blockParser = blockParsers.get(i); - finalize(blockParser); - } + private Document finalizeAndProcess() { + closeBlockParsers(activeBlockParsers.size()); + processInlines(); + return documentBlockParser.getBlock(); } - private Document finalizeAndProcess() { - finalizeBlocks(this.activeBlockParsers); - this.processInlines(); - return this.documentBlockParser.getBlock(); + private void closeBlockParsers(int count) { + for (int i = 0; i < count; i++) { + BlockParser blockParser = deactivateBlockParser(); + finalize(blockParser); + // Remember for inline parsing. Note that a lot of blocks don't need inline parsing. We could have a + // separate interface (e.g. BlockParserWithInlines) so that we only have to remember those that actually + // have inlines to parse. + allBlockParsers.add(blockParser); + } } private static class MatchedBlockParserImpl implements MatchedBlockParser { From 25c23c8ff41902f5dfc4f268c3e9f0da70f1140a Mon Sep 17 00:00:00 2001 From: leofernandesmo <leonardo.fernandes@ifal.edu.br> Date: Tue, 4 Aug 2020 16:26:46 -0300 Subject: [PATCH 419/815] Removed test code duplication --- .../test/TextContentRendererTest.java | 132 ++++++++++-------- 1 file changed, 74 insertions(+), 58 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java index 7a873b19d..2f5e61ff8 100644 --- a/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -15,15 +15,15 @@ public void textContentText() { String rendered; source = "foo bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo bar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo bar", rendered); source = "foo foo\n\nbar\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo foo\nbar\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo foo bar bar", rendered); } @@ -33,33 +33,39 @@ public void textContentEmphasis() { String rendered; source = "***foo***"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source, defaultRenderer()); assertEquals("foo", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo", rendered); source = "foo ***foo*** bar ***bar***"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo foo bar bar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo foo bar bar", rendered); source = "foo\n***foo***\nbar\n\n***bar***"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\nfoo\nbar\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo foo bar bar", rendered); } + private String defaultRenderer(String source, TextContentRenderer textContentRenderer) { + String rendered; + rendered = textContentRenderer.render(parse(source)); + return rendered; + } + @Test public void textContentQuotes() { String source; String rendered; source = "foo\n>foo\nbar\n\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\n«foo\nbar»\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo «foo bar» bar", rendered); } @@ -71,37 +77,37 @@ public void textContentLinks() { source = "foo [text](http://link \"title\") bar"; expected = "foo \"text\" (title: http://link) bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); source = "foo [text](http://link \"http://link\") bar"; expected = "foo \"text\" (http://link) bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); source = "foo [text](http://link) bar"; expected = "foo \"text\" (http://link) bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); source = "foo [text]() bar"; expected = "foo \"text\" bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); source = "foo http://link bar"; expected = "foo http://link bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); } @@ -113,23 +119,23 @@ public void textContentImages() { source = "foo ![text](http://link \"title\") bar"; expected = "foo \"text\" (title: http://link) bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); source = "foo ![text](http://link) bar"; expected = "foo \"text\" (http://link) bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); source = "foo ![text]() bar"; expected = "foo \"text\" bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); } @@ -139,51 +145,51 @@ public void textContentLists() { String rendered; source = "foo\n* foo\n* bar\n\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\n* foo\n* bar\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo foo bar bar", rendered); source = "foo\n- foo\n- bar\n\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\n- foo\n- bar\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo foo bar bar", rendered); source = "foo\n1. foo\n2. bar\n\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\n1. foo\n2. bar\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo 1. foo 2. bar bar", rendered); source = "foo\n0) foo\n1) bar\n\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\n0) foo\n1) bar\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo 0) foo 1) bar bar", rendered); source = "bar\n1. foo\n 1. bar\n2. foo"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("bar\n1. foo\n 1. bar\n2. foo", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("bar 1. foo 1. bar 2. foo", rendered); source = "bar\n* foo\n - bar\n* foo"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("bar\n* foo\n - bar\n* foo", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("bar foo bar foo", rendered); source = "bar\n* foo\n 1. bar\n 2. bar\n* foo"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("bar\n* foo\n 1. bar\n 2. bar\n* foo", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("bar foo 1. bar 2. bar foo", rendered); source = "bar\n1. foo\n * bar\n * bar\n2. foo"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("bar\n1. foo\n * bar\n * bar\n2. foo", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("bar 1. foo bar bar 2. foo", rendered); } @@ -195,9 +201,9 @@ public void textContentCode() { source = "foo `code` bar"; expected = "foo \"code\" bar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals(expected, rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals(expected, rendered); } @@ -207,15 +213,15 @@ public void textContentCodeBlock() { String rendered; source = "foo\n```\nfoo\nbar\n```\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\nfoo\nbar\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo foo bar bar", rendered); source = "foo\n\n foo\n bar\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\nfoo\n bar\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo foo bar bar", rendered); } @@ -225,24 +231,26 @@ public void textContentBrakes() { String rendered; source = "foo\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo bar", rendered); source = "foo \nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo bar", rendered); source = "foo\n___\nbar"; - rendered = defaultRenderer().render(parse(source)); + rendered = defaultRenderer(source); assertEquals("foo\n***\nbar", rendered); - rendered = strippedRenderer().render(parse(source)); + rendered = strippedRenderer(source); assertEquals("foo bar", rendered); } + + @Test public void textContentHtml() { String rendered; @@ -254,11 +262,11 @@ public void textContentHtml() { " </td>\n" + " </tr>\n" + "</table>"; - rendered = defaultRenderer().render(parse(html)); + rendered = defaultRenderer(html); assertEquals(html, rendered); html = "foo <foo>foobar</foo> bar"; - rendered = defaultRenderer().render(parse(html)); + rendered = defaultRenderer(html); assertEquals(html, rendered); } @@ -273,4 +281,12 @@ private TextContentRenderer strippedRenderer() { private Node parse(String source) { return Parser.builder().build().parse(source); } + + private String strippedRenderer(String source) { + return strippedRenderer().render(parse(source)); + } + + private String defaultRenderer(String source) { + return defaultRenderer().render(parse(source)); + } } From 8ec32f1a75e9490652c64d5b3424e4370fbce5ba Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 31 Aug 2020 15:29:27 +1000 Subject: [PATCH 420/815] Add GitHub actions --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..9b2c8ce5c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +# See https://docs.github.com/en/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: ci + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Build with Maven + run: mvn -B package From 3ce316940bce8190afc3a2db2d1d6963e751f668 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 31 Aug 2020 15:45:33 +1000 Subject: [PATCH 421/815] Move coverage to actions --- .github/workflows/ci.yml | 26 +++++++++++++++++++++++--- .travis.yml | 7 ------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b2c8ce5c..e1c22438d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,14 +7,34 @@ on: [push] jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + java: [1.8, 11] steps: - name: Checkout sources uses: actions/checkout@v2 - - name: Set up JDK 1.8 + - name: Set up JDK uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: ${{ matrix.java }} - - name: Build with Maven + - name: Build run: mvn -B package + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Build with coverage + run: mvn -B -Pcoverage clean test jacoco:report-aggregate + + - name: Publish coverage + uses: codecov/codecov-action@v1 diff --git a/.travis.yml b/.travis.yml index f73430272..ebe918c1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,10 +22,3 @@ matrix: script: - 'if [ $TEST = java ]; then mvn test -Dsurefire.useFile=false; fi' - 'if [ $TEST = android ]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' - -after_success: | - if [ $TRAVIS_JDK_VERSION = oraclejdk8 ] && [ $TEST = java ]; then - # Calculate test coverage - mvn clean test jacoco:report-aggregate -Pcoverage - bash <(curl -s https://codecov.io/bash) - fi From 8fed49c21ef8630cab03f556c73ba1835d8419dc Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Sat, 15 Aug 2020 16:52:08 +1000 Subject: [PATCH 422/815] Restart work for adding source positions The previous attempt required every block parser to implement source positions, which is not ideal. This one is for trying to see if we can get it done inside DocumentParser. Note that SourceSpan initially used 1-based indexes and beginIndex/endIndex, but it was changed to 0-based and a length because: 0-based fits better with the existing API and other Java APIs. And instead of having an endIndex, having a length would allow us to easily extend the SourceSpan to include a character offset in addition to line/column offset. --- .../commonmark/ext/gfm/tables/TablesTest.java | 46 +++++ commonmark-test-util/pom.xml | 4 + .../commonmark/internal/DocumentParser.java | 56 +++++- .../org/commonmark/internal/SourceSpans.java | 13 ++ .../main/java/org/commonmark/node/Node.java | 13 ++ .../java/org/commonmark/node/SourceSpan.java | 69 +++++++ .../commonmark/test/SourceSpanRenderer.java | 92 +++++++++ .../org/commonmark/test/SourceSpansTest.java | 178 ++++++++++++++++++ pom.xml | 5 + 9 files changed, 470 insertions(+), 6 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/SourceSpans.java create mode 100644 commonmark/src/main/java/org/commonmark/node/SourceSpan.java create mode 100644 commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java create mode 100644 commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java 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 563ae8c18..950926e1f 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 @@ -2,6 +2,7 @@ import org.commonmark.Extension; import org.commonmark.node.Node; +import org.commonmark.node.SourceSpan; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProviderContext; @@ -15,6 +16,8 @@ import java.util.Set; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.emptyIterable; import static org.junit.Assert.assertThat; public class TablesTest extends RenderingTestCase { @@ -643,6 +646,49 @@ public void setAttributes(Node node, String tagName, Map<String, String> attribu "</table>\n")); } + @Test + public void sourceSpans() { + Node document = PARSER.parse("Abc|Def\n---|---\n|1|2\n 3|four|\n|||\n"); + + TableBlock block = (TableBlock) document.getFirstChild(); + assertThat(block.getSourceSpans(), contains(SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 7), + SourceSpan.of(2, 0, 4), SourceSpan.of(3, 1, 7), SourceSpan.of(4, 0, 3))); + + TableHead head = (TableHead) block.getFirstChild(); + assertThat(head.getSourceSpans(), contains(SourceSpan.of(0, 0, 7))); + + TableRow headRow = (TableRow) head.getFirstChild(); + assertThat(headRow.getSourceSpans(), contains(SourceSpan.of(0, 0, 7))); + TableCell headRowCell1 = (TableCell) headRow.getFirstChild(); + TableCell headRowCell2 = (TableCell) headRow.getLastChild(); + assertThat(headRowCell1.getSourceSpans(), contains(SourceSpan.of(0, 0, 3))); + assertThat(headRowCell2.getSourceSpans(), contains(SourceSpan.of(0, 4, 3))); + + TableBody body = (TableBody) block.getLastChild(); + assertThat(body.getSourceSpans(), contains(SourceSpan.of(2, 0, 4), SourceSpan.of(3, 1, 7), SourceSpan.of(4, 0, 3))); + + TableRow bodyRow1 = (TableRow) body.getFirstChild(); + assertThat(bodyRow1.getSourceSpans(), contains(SourceSpan.of(2, 0, 4))); + TableCell bodyRow1Cell1 = (TableCell) bodyRow1.getFirstChild(); + TableCell bodyRow1Cell2 = (TableCell) bodyRow1.getLastChild(); + assertThat(bodyRow1Cell1.getSourceSpans(), contains(SourceSpan.of(2, 1, 1))); + assertThat(bodyRow1Cell2.getSourceSpans(), contains(SourceSpan.of(2, 3, 1))); + + TableRow bodyRow2 = (TableRow) body.getFirstChild().getNext(); + assertThat(bodyRow2.getSourceSpans(), contains(SourceSpan.of(3, 1, 7))); + TableCell bodyRow2Cell1 = (TableCell) bodyRow2.getFirstChild(); + TableCell bodyRow2Cell2 = (TableCell) bodyRow2.getLastChild(); + assertThat(bodyRow2Cell1.getSourceSpans(), contains(SourceSpan.of(3, 1, 1))); + assertThat(bodyRow2Cell2.getSourceSpans(), contains(SourceSpan.of(3, 3, 4))); + + TableRow bodyRow3 = (TableRow) body.getLastChild(); + assertThat(bodyRow3.getSourceSpans(), contains(SourceSpan.of(4, 0, 3))); + TableCell bodyRow3Cell1 = (TableCell) bodyRow3.getFirstChild(); + TableCell bodyRow3Cell2 = (TableCell) bodyRow3.getLastChild(); + assertThat(bodyRow3Cell1.getSourceSpans(), emptyIterable()); + assertThat(bodyRow3Cell2.getSourceSpans(), emptyIterable()); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 527583c4c..92046f159 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -16,6 +16,10 @@ <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-library</artifactId> + </dependency> </dependencies> <build> diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 700519ac9..ce965f1e3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,16 +1,40 @@ package org.commonmark.internal; import org.commonmark.internal.util.Parsing; -import org.commonmark.node.*; +import org.commonmark.node.Block; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.Document; +import org.commonmark.node.FencedCodeBlock; +import org.commonmark.node.Heading; +import org.commonmark.node.HtmlBlock; +import org.commonmark.node.IndentedCodeBlock; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.ListBlock; +import org.commonmark.node.Paragraph; +import org.commonmark.node.SourceSpan; +import org.commonmark.node.ThematicBreak; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserFactory; -import org.commonmark.parser.block.*; +import org.commonmark.parser.block.BlockContinue; +import org.commonmark.parser.block.BlockParser; +import org.commonmark.parser.block.BlockParserFactory; +import org.commonmark.parser.block.BlockStart; +import org.commonmark.parser.block.MatchedBlockParser; +import org.commonmark.parser.block.ParserState; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; public class DocumentParser implements ParserState { @@ -40,6 +64,11 @@ public class DocumentParser implements ParserState { private CharSequence line; + /** + * Line index (0-based) + */ + private int lineIndex = -1; + /** * current index (offset) in input line (0-based) */ @@ -137,6 +166,10 @@ public CharSequence getLine() { return line; } + public int getLineIndex() { + return lineIndex; + } + @Override public int getIndex() { return index; @@ -172,6 +205,7 @@ public BlockParser getActiveBlockParser() { * line of input, then finalizing the document. */ private void incorporateLine(CharSequence ln) { + lineIndex++; line = Parsing.prepareLine(ln); index = 0; column = 0; @@ -256,6 +290,10 @@ private void incorporateLine(CharSequence ln) { if (!startedNewBlock && !isBlank() && getActiveBlockParser().canHaveLazyContinuationLines()) { // lazy paragraph continuation + // TODO + for (BlockParser parser : activeBlockParsers.subList(1, activeBlockParsers.size())) { +// parser.onLazyContinuationLine(this); + } addLine(); } else { @@ -269,7 +307,11 @@ private void incorporateLine(CharSequence ln) { addLine(); } else if (!isBlank()) { // create paragraph container for line - addChild(new ParagraphParser()); + // TODO: +// SourceSpan sourceSpan = SourceSpans.fromState(this, this.getNextNonSpaceIndex()); +// ParagraphParser paragraphParser = new ParagraphParser(sourceSpan); + ParagraphParser paragraphParser = new ParagraphParser(); + addChild(paragraphParser); addLine(); } } @@ -392,6 +434,8 @@ private void finalize(BlockParser blockParser) { } blockParser.closeBlock(); + // TODO + //blockParser.getBlock().setSourceSpans(blockParser.getSourceSpans()); } private void addDefinitionsFrom(ParagraphParser paragraphParser) { @@ -420,8 +464,8 @@ private void processInlines() { } /** - * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try - * its parent, and so on til we find a block that can accept children. + * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try + * its parent, and so on until we find a block that can accept children. */ private <T extends BlockParser> T addChild(T blockParser) { while (!getActiveBlockParser().canContain(blockParser.getBlock())) { diff --git a/commonmark/src/main/java/org/commonmark/internal/SourceSpans.java b/commonmark/src/main/java/org/commonmark/internal/SourceSpans.java new file mode 100644 index 000000000..bf8715be8 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/SourceSpans.java @@ -0,0 +1,13 @@ +package org.commonmark.internal; + +import org.commonmark.node.SourceSpan; +import org.commonmark.parser.block.ParserState; + +public class SourceSpans { + + public static SourceSpan fromState(ParserState parserState, int startIndex) { +// int length = parserState.getLine().length(); +// return SourceSpan.of(parserState.getLineIndex(), startIndex, length - startIndex); + return null; + } +} diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index e7b24c08c..7a742ea85 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -1,5 +1,9 @@ package org.commonmark.node; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public abstract class Node { private Node parent = null; @@ -7,6 +11,7 @@ public abstract class Node { private Node lastChild = null; private Node prev = null; private Node next = null; + private List<SourceSpan> sourceSpans = new ArrayList<>(); public abstract void accept(Visitor visitor); @@ -30,6 +35,14 @@ public Node getParent() { return parent; } + public List<SourceSpan> getSourceSpans() { + return Collections.unmodifiableList(sourceSpans); + } + + public void setSourceSpans(List<SourceSpan> sourceSpans) { + this.sourceSpans = new ArrayList<>(sourceSpans); + } + protected void setParent(Node parent) { this.parent = parent; } diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java new file mode 100644 index 000000000..c3c49531b --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java @@ -0,0 +1,69 @@ +package org.commonmark.node; + +import java.util.Objects; + +public class SourceSpan { + + private final int lineIndex; + private final int columnIndex; + private final int length; + + public static SourceSpan of(int lineIndex, int columnIndex, int length) { + return new SourceSpan(lineIndex, columnIndex, length); + } + + private SourceSpan(int lineIndex, int columnIndex, int length) { + this.lineIndex = lineIndex; + this.columnIndex = columnIndex; + this.length = length; + } + + /** + * @return 0-based index of line in source + */ + public int getLineIndex() { + return lineIndex; + } + + /** + * @return 0-based index of column (character on line) in source + */ + public int getColumnIndex() { + return columnIndex; + } + + /** + * @return length of the span + */ + public int getLength() { + return length; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SourceSpan that = (SourceSpan) o; + return lineIndex == that.lineIndex && + columnIndex == that.columnIndex && + length == that.length; + } + + @Override + public int hashCode() { + return Objects.hash(lineIndex, columnIndex, length); + } + + @Override + public String toString() { + return "SourceSpan{" + + "lineIndex=" + lineIndex + + ", columnIndex=" + columnIndex + + ", length=" + length + + "}"; + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java new file mode 100644 index 000000000..4cb057b36 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java @@ -0,0 +1,92 @@ +package org.commonmark.test; + +import org.commonmark.node.AbstractVisitor; +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; + +public class SourceSpanRenderer { + + public static String render(Node document, String source) { + SourceSpanMarkersVisitor visitor = new SourceSpanMarkersVisitor(); + document.accept(visitor); + Map<Integer, Map<Integer, List<String>>> markers = visitor.getMarkers(); + + StringBuilder sb = new StringBuilder(); + + String[] lines = source.split("\n"); + + for (int lineIndex = 0; lineIndex < lines.length; lineIndex++) { + String line = lines[lineIndex]; + Map<Integer, List<String>> lineMarkers = markers.get(lineIndex); + for (int i = 0; i < line.length(); i++) { + appendMarkers(lineMarkers, i, sb); + sb.append(line.charAt(i)); + } + appendMarkers(lineMarkers, line.length(), sb); + sb.append("\n"); + } + + return sb.toString(); + } + + private static void appendMarkers(Map<Integer, List<String>> lineMarkers, int columnIndex, StringBuilder sb) { + if (lineMarkers != null) { + List<String> columnMarkers = lineMarkers.get(columnIndex); + if (columnMarkers != null) { + for (String marker : columnMarkers) { + sb.append(marker); + } + } + } + } + + private static class SourceSpanMarkersVisitor extends AbstractVisitor { + + private final Map<Integer, Map<Integer, List<String>>> markers = new HashMap<>(); + private final String opening = "({[<⸢⸤"; + private final String closing = ")}]>⸣⸥"; + + private int markerIndex; + + public Map<Integer, Map<Integer, List<String>>> getMarkers() { + return markers; + } + + @Override + protected void visitChildren(Node parent) { + if (!parent.getSourceSpans().isEmpty()) { + for (SourceSpan sourceSpan : 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); + } + markerIndex++; + } + super.visitChildren(parent); + } + + private List<String> getMarkers(int lineIndex, int columnIndex) { + Map<Integer, List<String>> columnMap = markers.get(lineIndex); + if (columnMap == null) { + columnMap = new HashMap<>(); + markers.put(lineIndex, columnMap); + } + + List<String> markers = columnMap.get(columnIndex); + if (markers == null) { + markers = new LinkedList<>(); + columnMap.put(columnIndex, markers); + } + + return markers; + } + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java new file mode 100644 index 000000000..2264ebfaf --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -0,0 +1,178 @@ +package org.commonmark.test; + +import org.commonmark.node.BlockQuote; +import org.commonmark.node.FencedCodeBlock; +import org.commonmark.node.Heading; +import org.commonmark.node.HtmlBlock; +import org.commonmark.node.IndentedCodeBlock; +import org.commonmark.node.ListBlock; +import org.commonmark.node.ListItem; +import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.SourceSpan; +import org.commonmark.node.ThematicBreak; +import org.commonmark.parser.Parser; +import org.junit.Test; + +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.*; + +public class SourceSpansTest { + + private static final Parser PARSER = Parser.builder().build(); + + @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, 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\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)); + } + + @Test + public void thematicBreak() { + assertSpans("---\n", ThematicBreak.class, SourceSpan.of(0, 0, 3)); + assertSpans(" ---\n", ThematicBreak.class, SourceSpan.of(0, 2, 3)); + assertSpans("> ---\n", ThematicBreak.class, SourceSpan.of(0, 2, 3)); + } + + @Test + public void atxHeading() { + assertSpans("# foo", Heading.class, SourceSpan.of(0, 0, 5)); + assertSpans(" # foo", Heading.class, SourceSpan.of(0, 1, 5)); + assertSpans("## foo ##", Heading.class, SourceSpan.of(0, 0, 9)); + assertSpans("> # foo", Heading.class, SourceSpan.of(0, 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, 2, 3), SourceSpan.of(1, 2, 3)); + assertSpans("> foo\n> ===\n", Heading.class, SourceSpan.of(0, 2, 3), SourceSpan.of(1, 2, 3)); + } + + @Test + public void indentedCodeBlock() { + assertSpans(" foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3)); + assertSpans(" foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 4)); + assertSpans("\tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 1, 3)); + assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 2, 3)); + assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 3, 3)); + assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3)); + assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 4)); + assertSpans(" \t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 5)); + assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 1, 4)); + assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 1, 5)); + assertSpans(" foo\n bar\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3), SourceSpan.of(1, 4, 4)); + assertSpans(" foo\n\tbar\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3), SourceSpan.of(1, 1, 3)); + assertSpans(" foo\n \n \n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3), SourceSpan.of(2, 4, 1)); + assertSpans("> foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 6, 3)); + } + + @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)); + 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)); + assertSpans(" ```\n foo\n ```\n", FencedCodeBlock.class, + SourceSpan.of(0, 3, 3), SourceSpan.of(1, 3, 3), SourceSpan.of(2, 3, 3)); + assertSpans(" ```\n foo\nfoo\n```\n", FencedCodeBlock.class, + SourceSpan.of(0, 1, 3), SourceSpan.of(1, 1, 3), SourceSpan.of(2, 0, 3), SourceSpan.of(3, 0, 3)); + assertSpans("```info\nfoo\n```\n", FencedCodeBlock.class, + SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3)); + assertSpans("* ```\n foo\n ```\n", FencedCodeBlock.class, + SourceSpan.of(0, 2, 3), SourceSpan.of(1, 2, 3), SourceSpan.of(2, 2, 3)); + assertSpans("> ```\n> foo\n> ```\n", FencedCodeBlock.class, + SourceSpan.of(0, 2, 3), SourceSpan.of(1, 2, 3), SourceSpan.of(2, 2, 3)); + + Node document = PARSER.parse("```\nfoo\n```\nbar\n"); + Paragraph paragraph = (Paragraph) document.getLastChild(); + assertThat(paragraph.getSourceSpans(), contains(SourceSpan.of(3, 0, 3))); + } + + @Test + public void htmlBlock() { + assertSpans("<div>\n", HtmlBlock.class, SourceSpan.of(0, 0, 5)); + assertSpans(" <div>\n foo\n </div>\n", HtmlBlock.class, SourceSpan.of(0, 0, 6), SourceSpan.of(1, 0, 4), SourceSpan.of(2, 0, 7)); + assertSpans("* <div>\n", HtmlBlock.class, SourceSpan.of(0, 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, 1, 5)); + assertSpans(" > foo\n > bar\n", BlockQuote.class, SourceSpan.of(0, 3, 5), SourceSpan.of(1, 2, 5)); + // 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)); + } + + @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, 2, 3)); + 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, 2, 5)); + assertSpans("* foo\n * bar\n", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 2, 5)); + assertSpans("* foo\n> bar\n", ListBlock.class, SourceSpan.of(0, 0, 5)); + assertSpans("> * foo\n", ListBlock.class, SourceSpan.of(0, 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, 2, 5), SourceSpan.of(2, 0, 3)); + + Node document = PARSER.parse("* foo\n * bar\n"); + ListBlock listBlock = (ListBlock) document.getFirstChild().getFirstChild().getLastChild(); + assertThat(listBlock.getSourceSpans(), contains(SourceSpan.of(1, 2, 5))); + } + + @Test + public void listItem() { + assertSpans("* foo\n", ListItem.class, SourceSpan.of(0, 0, 5)); + assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 1, 5)); + assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 2, 5)); + assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 3, 5)); + assertSpans("*\n foo\n", ListItem.class, SourceSpan.of(0, 0, 1), SourceSpan.of(1, 2, 3)); + assertSpans("*\n foo\n bar\n", ListItem.class, SourceSpan.of(0, 0, 1), SourceSpan.of(1, 2, 3), SourceSpan.of(2, 2, 3)); + assertSpans("> * foo\n", ListItem.class, SourceSpan.of(0, 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)); + } + + @Test + public void visualCheck() { + assertEquals("(> {[* <foo>]})\n(> {[<bar>]})\n(> {⸢* ⸤baz⸥⸣})\n", + visualizeSourceSpans("> * foo\n> bar\n> * baz\n")); + assertEquals("(> {[* <```>]})\n(> {[<foo>]})\n(> {[<```>]})\n", + visualizeSourceSpans("> * ```\n> foo\n> ```")); + } + + private String visualizeSourceSpans(String source) { + Node document = PARSER.parse(source); + return SourceSpanRenderer.render(document, source); + } + + private static void assertSpans(String input, Class<? extends Node> nodeClass, SourceSpan... expectedSourceSpans) { + Node node = PARSER.parse(input); + while (node != null && !nodeClass.isInstance(node)) { + node = node.getFirstChild(); + } + if (node == null) { + fail("Expected to find " + nodeClass + " node"); + } else { + assertThat(node.getSourceSpans(), contains(expectedSourceSpans)); + } + } +} diff --git a/pom.xml b/pom.xml index d81dc186a..2bc6e3e40 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,11 @@ <artifactId>junit</artifactId> <version>4.12</version> </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-library</artifactId> + <version>1.3</version> + </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> From e7d33ae7063bc8876a874edbe1959c750d514c4b Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Sat, 15 Aug 2020 21:05:08 +1000 Subject: [PATCH 423/815] Some tests passing The block parsers will still need to be extended so that they can communicate where the source started, in case they return positions after marker characters. I think we can put that into BlockStart and BlockContinue though. --- .../commonmark/internal/DocumentParser.java | 71 ++++++++++++------- .../main/java/org/commonmark/node/Node.java | 3 +- .../org/commonmark/test/SourceSpansTest.java | 8 ++- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index ce965f1e3..9691c94ff 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -95,7 +95,7 @@ public class DocumentParser implements ParserState { private final DocumentBlockParser documentBlockParser; private final Map<String, LinkReferenceDefinition> definitions = new LinkedHashMap<>(); - private final List<BlockParser> activeBlockParsers = new ArrayList<>(); + private final List<OpenBlockParser> openBlockParsers = new ArrayList<>(); private final List<BlockParser> allBlockParsers = new ArrayList<>(); public DocumentParser(List<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory, @@ -105,7 +105,7 @@ public DocumentParser(List<BlockParserFactory> blockParserFactories, InlineParse this.delimiterProcessors = delimiterProcessors; this.documentBlockParser = new DocumentBlockParser(); - activateBlockParser(this.documentBlockParser); + activateBlockParser(new OpenBlockParser(documentBlockParser, 0)); } public static Set<Class<? extends Block>> getDefaultBlockParserTypes() { @@ -197,7 +197,7 @@ public boolean isBlank() { @Override public BlockParser getActiveBlockParser() { - return activeBlockParsers.get(activeBlockParsers.size() - 1); + return openBlockParsers.get(openBlockParsers.size() - 1).blockParser; } /** @@ -214,15 +214,17 @@ private void incorporateLine(CharSequence ln) { // 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 int matches = 1; - for (int i = 1; i < activeBlockParsers.size(); i++) { - BlockParser blockParser = activeBlockParsers.get(i); + for (int i = 1; i < openBlockParsers.size(); i++) { + OpenBlockParser openBlockParser = openBlockParsers.get(i); + BlockParser blockParser = openBlockParser.blockParser; findNextNonSpace(); BlockContinue result = blockParser.tryContinue(this); if (result instanceof BlockContinueImpl) { BlockContinueImpl blockContinue = (BlockContinueImpl) result; if (blockContinue.isFinalize()) { - closeBlockParsers(activeBlockParsers.size() - i); + // TODO: Source pos here? + closeBlockParsers(openBlockParsers.size() - i); return; } else { if (blockContinue.getNewIndex() != -1) { @@ -230,6 +232,7 @@ private void incorporateLine(CharSequence ln) { } else if (blockContinue.getNewColumn() != -1) { setNewColumn(blockContinue.getNewColumn()); } + openBlockParser.index = index; matches++; } } else { @@ -237,8 +240,8 @@ private void incorporateLine(CharSequence ln) { } } - int unmatchedBlocks = activeBlockParsers.size() - matches; - BlockParser blockParser = activeBlockParsers.get(matches - 1); + int unmatchedBlocks = openBlockParsers.size() - matches; + BlockParser blockParser = openBlockParsers.get(matches - 1).blockParser; boolean startedNewBlock = false; // Unless last matched container is a code block, try new container starts, @@ -278,7 +281,8 @@ private void incorporateLine(CharSequence ln) { } for (BlockParser newBlockParser : blockStart.getBlockParsers()) { - blockParser = addChild(newBlockParser); + addChild(new OpenBlockParser(newBlockParser, index)); + blockParser = newBlockParser; tryBlockStarts = newBlockParser.isContainer(); } } @@ -289,11 +293,9 @@ private void incorporateLine(CharSequence ln) { // First check for a lazy paragraph continuation: if (!startedNewBlock && !isBlank() && getActiveBlockParser().canHaveLazyContinuationLines()) { + openBlockParsers.get(openBlockParsers.size() - 1).index = index; // lazy paragraph continuation // TODO - for (BlockParser parser : activeBlockParsers.subList(1, activeBlockParsers.size())) { -// parser.onLazyContinuationLine(this); - } addLine(); } else { @@ -311,7 +313,7 @@ private void incorporateLine(CharSequence ln) { // SourceSpan sourceSpan = SourceSpans.fromState(this, this.getNextNonSpaceIndex()); // ParagraphParser paragraphParser = new ParagraphParser(sourceSpan); ParagraphParser paragraphParser = new ParagraphParser(); - addChild(paragraphParser); + addChild(new OpenBlockParser(paragraphParser, index)); addLine(); } } @@ -410,6 +412,13 @@ private void addLine() { content = line.subSequence(index, line.length()); } getActiveBlockParser().addLine(content); + for (OpenBlockParser openBlockParser : openBlockParsers) { + int blockIndex = openBlockParser.index; + int length = line.length() - blockIndex; + if (length != 0) { + openBlockParser.blockParser.getBlock().getSourceSpans().add(SourceSpan.of(lineIndex, blockIndex, length)); + } + } } private BlockStartImpl findBlockStart(BlockParser blockParser) { @@ -467,28 +476,26 @@ private void processInlines() { * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try * its parent, and so on until we find a block that can accept children. */ - private <T extends BlockParser> T addChild(T blockParser) { - while (!getActiveBlockParser().canContain(blockParser.getBlock())) { + private void addChild(OpenBlockParser openBlockParser) { + while (!getActiveBlockParser().canContain(openBlockParser.blockParser.getBlock())) { closeBlockParsers(1); } - getActiveBlockParser().getBlock().appendChild(blockParser.getBlock()); - activateBlockParser(blockParser); - - return blockParser; + getActiveBlockParser().getBlock().appendChild(openBlockParser.blockParser.getBlock()); + activateBlockParser(openBlockParser); } - private void activateBlockParser(BlockParser blockParser) { - activeBlockParsers.add(blockParser); + private void activateBlockParser(OpenBlockParser openBlockParser) { + openBlockParsers.add(openBlockParser); } - private BlockParser deactivateBlockParser() { - return activeBlockParsers.remove(activeBlockParsers.size() - 1); + private OpenBlockParser deactivateBlockParser() { + return openBlockParsers.remove(openBlockParsers.size() - 1); } private void prepareActiveBlockParserForReplacement() { // Note that we don't want to parse inlines or finalize this block, as it's getting replaced. - BlockParser old = deactivateBlockParser(); + BlockParser old = deactivateBlockParser().blockParser; if (old instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) old; @@ -500,18 +507,20 @@ private void prepareActiveBlockParserForReplacement() { addDefinitionsFrom(paragraphParser); } + // TODO: Copy source positions of old block + old.getBlock().unlink(); } private Document finalizeAndProcess() { - closeBlockParsers(activeBlockParsers.size()); + closeBlockParsers(openBlockParsers.size()); processInlines(); return documentBlockParser.getBlock(); } private void closeBlockParsers(int count) { for (int i = 0; i < count; i++) { - BlockParser blockParser = deactivateBlockParser(); + BlockParser blockParser = deactivateBlockParser().blockParser; finalize(blockParser); // Remember for inline parsing. Note that a lot of blocks don't need inline parsing. We could have a // separate interface (e.g. BlockParserWithInlines) so that we only have to remember those that actually @@ -547,4 +556,14 @@ public CharSequence getParagraphContent() { return null; } } + + private static class OpenBlockParser { + private final BlockParser blockParser; + private int index; + + OpenBlockParser(BlockParser blockParser, int index) { + this.blockParser = blockParser; + this.index = index; + } + } } diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index 7a742ea85..462acc545 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -1,7 +1,6 @@ package org.commonmark.node; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public abstract class Node { @@ -36,7 +35,7 @@ public Node getParent() { } public List<SourceSpan> getSourceSpans() { - return Collections.unmodifiableList(sourceSpans); + return sourceSpans; } public void setSourceSpans(List<SourceSpan> sourceSpans) { diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index 2264ebfaf..9e03c94da 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -14,6 +14,8 @@ import org.commonmark.parser.Parser; import org.junit.Test; +import java.util.Arrays; + import static org.hamcrest.Matchers.contains; import static org.junit.Assert.*; @@ -21,6 +23,10 @@ public class SourceSpansTest { private static final Parser PARSER = Parser.builder().build(); + // TODO: Tests for when column is within tab + + // TODO: Tests for where paragraph starts with link reference definitions + @Test public void paragraph() { assertSpans("foo\n", Paragraph.class, SourceSpan.of(0, 0, 3)); @@ -172,7 +178,7 @@ private static void assertSpans(String input, Class<? extends Node> nodeClass, S if (node == null) { fail("Expected to find " + nodeClass + " node"); } else { - assertThat(node.getSourceSpans(), contains(expectedSourceSpans)); + assertEquals(Arrays.asList(expectedSourceSpans), node.getSourceSpans()); } } } From 149ed277b4fb8b9165cec7cca3ec0732a71b9a78 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 2 Sep 2020 22:00:42 +1000 Subject: [PATCH 424/815] Set block source spans based on the index This means that e.g. this: ``` paragraph starting with a space ``` Will include the starting space in the source positions. I was initially thinking it should not be included, but that makes a lot of things harder, for not much gain IMO. E.g. for a code block with 5 spaces: ``` code ``` Where should the source position start, at index 0, 4, or 5? This implements 0. 4 is kind of hard to implement due to tabs being treated as 4-space tabstops. --- .../commonmark/ext/gfm/tables/TablesTest.java | 37 ++++--- .../commonmark/internal/DocumentParser.java | 63 +++++++---- .../LinkReferenceDefinitionParser.java | 15 ++- .../commonmark/internal/ParagraphParser.java | 12 +- .../java/org/commonmark/node/SourceSpan.java | 4 +- .../parser/block/AbstractBlockParser.java | 6 + .../commonmark/parser/block/BlockParser.java | 8 ++ .../commonmark/test/SourceSpanRenderer.java | 9 +- .../org/commonmark/test/SourceSpansTest.java | 104 +++++++++++------- 9 files changed, 172 insertions(+), 86 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 950926e1f..b38358d8b 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 @@ -11,13 +11,13 @@ 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 static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.emptyIterable; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; public class TablesTest extends RenderingTestCase { @@ -651,42 +651,43 @@ public void sourceSpans() { Node document = PARSER.parse("Abc|Def\n---|---\n|1|2\n 3|four|\n|||\n"); TableBlock block = (TableBlock) document.getFirstChild(); - assertThat(block.getSourceSpans(), contains(SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 7), - SourceSpan.of(2, 0, 4), SourceSpan.of(3, 1, 7), SourceSpan.of(4, 0, 3))); + assertEquals(Arrays.asList(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(); - assertThat(head.getSourceSpans(), contains(SourceSpan.of(0, 0, 7))); + assertEquals(Arrays.asList(SourceSpan.of(0, 0, 7)), head.getSourceSpans()); TableRow headRow = (TableRow) head.getFirstChild(); - assertThat(headRow.getSourceSpans(), contains(SourceSpan.of(0, 0, 7))); + assertEquals(Arrays.asList(SourceSpan.of(0, 0, 7)), headRow.getSourceSpans()); TableCell headRowCell1 = (TableCell) headRow.getFirstChild(); TableCell headRowCell2 = (TableCell) headRow.getLastChild(); - assertThat(headRowCell1.getSourceSpans(), contains(SourceSpan.of(0, 0, 3))); - assertThat(headRowCell2.getSourceSpans(), contains(SourceSpan.of(0, 4, 3))); + assertEquals(Arrays.asList(SourceSpan.of(0, 0, 3)), headRowCell1.getSourceSpans()); + assertEquals(Arrays.asList(SourceSpan.of(0, 4, 3)), headRowCell2.getSourceSpans()); TableBody body = (TableBody) block.getLastChild(); - assertThat(body.getSourceSpans(), contains(SourceSpan.of(2, 0, 4), SourceSpan.of(3, 1, 7), SourceSpan.of(4, 0, 3))); + assertEquals(Arrays.asList(SourceSpan.of(2, 0, 4), SourceSpan.of(3, 1, 7), SourceSpan.of(4, 0, 3)), body.getSourceSpans()); TableRow bodyRow1 = (TableRow) body.getFirstChild(); - assertThat(bodyRow1.getSourceSpans(), contains(SourceSpan.of(2, 0, 4))); + assertEquals(Arrays.asList(SourceSpan.of(2, 0, 4)), bodyRow1.getSourceSpans()); TableCell bodyRow1Cell1 = (TableCell) bodyRow1.getFirstChild(); TableCell bodyRow1Cell2 = (TableCell) bodyRow1.getLastChild(); - assertThat(bodyRow1Cell1.getSourceSpans(), contains(SourceSpan.of(2, 1, 1))); - assertThat(bodyRow1Cell2.getSourceSpans(), contains(SourceSpan.of(2, 3, 1))); + assertEquals(Arrays.asList(SourceSpan.of(2, 1, 1)), bodyRow1Cell1.getSourceSpans()); + assertEquals(Arrays.asList(SourceSpan.of(2, 3, 1)), bodyRow1Cell2.getSourceSpans()); TableRow bodyRow2 = (TableRow) body.getFirstChild().getNext(); - assertThat(bodyRow2.getSourceSpans(), contains(SourceSpan.of(3, 1, 7))); + assertEquals(Arrays.asList(SourceSpan.of(3, 1, 7)), bodyRow2.getSourceSpans()); TableCell bodyRow2Cell1 = (TableCell) bodyRow2.getFirstChild(); TableCell bodyRow2Cell2 = (TableCell) bodyRow2.getLastChild(); - assertThat(bodyRow2Cell1.getSourceSpans(), contains(SourceSpan.of(3, 1, 1))); - assertThat(bodyRow2Cell2.getSourceSpans(), contains(SourceSpan.of(3, 3, 4))); + assertEquals(Arrays.asList(SourceSpan.of(3, 1, 1)), bodyRow2Cell1.getSourceSpans()); + assertEquals(Arrays.asList(SourceSpan.of(3, 3, 4)), bodyRow2Cell2.getSourceSpans()); TableRow bodyRow3 = (TableRow) body.getLastChild(); - assertThat(bodyRow3.getSourceSpans(), contains(SourceSpan.of(4, 0, 3))); + assertEquals(Arrays.asList(SourceSpan.of(4, 0, 3)), bodyRow3.getSourceSpans()); TableCell bodyRow3Cell1 = (TableCell) bodyRow3.getFirstChild(); TableCell bodyRow3Cell2 = (TableCell) bodyRow3.getLastChild(); - assertThat(bodyRow3Cell1.getSourceSpans(), emptyIterable()); - assertThat(bodyRow3Cell2.getSourceSpans(), emptyIterable()); + assertEquals(Collections.emptyList(), bodyRow3Cell1.getSourceSpans()); + assertEquals(Collections.emptyList(), bodyRow3Cell2.getSourceSpans()); } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 9691c94ff..6287d42d2 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -222,8 +222,9 @@ private void incorporateLine(CharSequence ln) { BlockContinue result = blockParser.tryContinue(this); if (result instanceof BlockContinueImpl) { BlockContinueImpl blockContinue = (BlockContinueImpl) result; + openBlockParser.sourceIndex = getIndex(); if (blockContinue.isFinalize()) { - // TODO: Source pos here? + addSourceSpans(); closeBlockParsers(openBlockParsers.size() - i); return; } else { @@ -232,7 +233,6 @@ private void incorporateLine(CharSequence ln) { } else if (blockContinue.getNewColumn() != -1) { setNewColumn(blockContinue.getNewColumn()); } - openBlockParser.index = index; matches++; } } else { @@ -244,10 +244,13 @@ private void incorporateLine(CharSequence ln) { BlockParser blockParser = openBlockParsers.get(matches - 1).blockParser; boolean startedNewBlock = false; + int lastIndex = index; + // Unless last matched container is a code block, try new container starts, // adding children to the last matched container: boolean tryBlockStarts = blockParser.getBlock() instanceof Paragraph || blockParser.isContainer(); while (tryBlockStarts) { + lastIndex = index; findNextNonSpace(); // this is a little performance optimization: @@ -263,6 +266,7 @@ private void incorporateLine(CharSequence ln) { } startedNewBlock = true; + int sourceIndex = getIndex(); // We're starting a new block. If we have any previous blocks that need to be closed, we need to do it now. if (unmatchedBlocks > 0) { @@ -276,12 +280,17 @@ private void incorporateLine(CharSequence ln) { setNewColumn(blockStart.getNewColumn()); } + List<SourceSpan> replacedSourceSpans = null; if (blockStart.isReplaceActiveBlockParser()) { - prepareActiveBlockParserForReplacement(); + Block replacedBlock = prepareActiveBlockParserForReplacement(); + replacedSourceSpans = replacedBlock.getSourceSpans(); } for (BlockParser newBlockParser : blockStart.getBlockParsers()) { - addChild(new OpenBlockParser(newBlockParser, index)); + addChild(new OpenBlockParser(newBlockParser, sourceIndex)); + if (replacedSourceSpans != null) { + newBlockParser.getBlock().setSourceSpans(replacedSourceSpans); + } blockParser = newBlockParser; tryBlockStarts = newBlockParser.isContainer(); } @@ -293,9 +302,8 @@ private void incorporateLine(CharSequence ln) { // First check for a lazy paragraph continuation: if (!startedNewBlock && !isBlank() && getActiveBlockParser().canHaveLazyContinuationLines()) { - openBlockParsers.get(openBlockParsers.size() - 1).index = index; + openBlockParsers.get(openBlockParsers.size() - 1).sourceIndex = lastIndex; // lazy paragraph continuation - // TODO addLine(); } else { @@ -309,12 +317,18 @@ private void incorporateLine(CharSequence ln) { addLine(); } else if (!isBlank()) { // create paragraph container for line - // TODO: -// SourceSpan sourceSpan = SourceSpans.fromState(this, this.getNextNonSpaceIndex()); -// ParagraphParser paragraphParser = new ParagraphParser(sourceSpan); ParagraphParser paragraphParser = new ParagraphParser(); - addChild(new OpenBlockParser(paragraphParser, index)); + addChild(new OpenBlockParser(paragraphParser, lastIndex)); addLine(); + } else { + // This can happen for a list item like this: + // ``` + // * + // list item + // ``` + // + // The first line does not start a paragraph yet, but we still want to record source positions. + addSourceSpans(); } } } @@ -412,11 +426,17 @@ private void addLine() { content = line.subSequence(index, line.length()); } getActiveBlockParser().addLine(content); - for (OpenBlockParser openBlockParser : openBlockParsers) { - int blockIndex = openBlockParser.index; + addSourceSpans(); + } + + private void addSourceSpans() { + // Don't add source spans for Document itself (it would get the whole source text) + for (int i = 1; i < openBlockParsers.size(); i++) { + OpenBlockParser openBlockParser = openBlockParsers.get(i); + int blockIndex = openBlockParser.sourceIndex; int length = line.length() - blockIndex; if (length != 0) { - openBlockParser.blockParser.getBlock().getSourceSpans().add(SourceSpan.of(lineIndex, blockIndex, length)); + openBlockParser.blockParser.addSourceSpan(SourceSpan.of(lineIndex, blockIndex, length)); } } } @@ -443,8 +463,6 @@ private void finalize(BlockParser blockParser) { } blockParser.closeBlock(); - // TODO - //blockParser.getBlock().setSourceSpans(blockParser.getSourceSpans()); } private void addDefinitionsFrom(ParagraphParser paragraphParser) { @@ -493,8 +511,8 @@ private OpenBlockParser deactivateBlockParser() { return openBlockParsers.remove(openBlockParsers.size() - 1); } - private void prepareActiveBlockParserForReplacement() { - // Note that we don't want to parse inlines or finalize this block, as it's getting replaced. + private Block prepareActiveBlockParserForReplacement() { + // Note that we don't want to parse inlines, as it's getting replaced. BlockParser old = deactivateBlockParser().blockParser; if (old instanceof ParagraphParser) { @@ -507,9 +525,10 @@ private void prepareActiveBlockParserForReplacement() { addDefinitionsFrom(paragraphParser); } - // TODO: Copy source positions of old block - + // 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(); } private Document finalizeAndProcess() { @@ -559,11 +578,11 @@ public CharSequence getParagraphContent() { private static class OpenBlockParser { private final BlockParser blockParser; - private int index; + private int sourceIndex; - OpenBlockParser(BlockParser blockParser, int index) { + OpenBlockParser(BlockParser blockParser, int sourceIndex) { this.blockParser = blockParser; - this.index = index; + this.sourceIndex = sourceIndex; } } } diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 1fe2cbea7..e0c23160e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -4,6 +4,7 @@ import org.commonmark.internal.util.LinkScanner; import org.commonmark.internal.util.Parsing; import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.SourceSpan; import java.util.ArrayList; import java.util.List; @@ -19,6 +20,7 @@ public class LinkReferenceDefinitionParser { private final StringBuilder paragraph = new StringBuilder(); private final List<LinkReferenceDefinition> definitions = new ArrayList<>(); + private final List<SourceSpan> sourceSpans = new ArrayList<>(); private StringBuilder label; private String normalizedLabel; @@ -70,10 +72,18 @@ public void parse(CharSequence line) { } } + public void addSourceSpan(SourceSpan sourceSpan) { + sourceSpans.add(sourceSpan); + } + CharSequence getParagraphContent() { return paragraph; } + List<SourceSpan> getParagraphSourceSpans() { + return sourceSpans; + } + List<LinkReferenceDefinition> getDefinitions() { finishReference(); return definitions; @@ -235,7 +245,10 @@ private void finishReference() { String d = Escaping.unescapeString(destination); String t = title != null ? Escaping.unescapeString(title.toString()) : null; - definitions.add(new LinkReferenceDefinition(normalizedLabel, d, t)); + LinkReferenceDefinition definition = new LinkReferenceDefinition(normalizedLabel, d, t); + definition.setSourceSpans(sourceSpans); + sourceSpans.clear(); + definitions.add(definition); label = null; referenceValid = false; diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index ee2899a93..09cf9fdec 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -3,6 +3,7 @@ import org.commonmark.node.Block; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.node.Paragraph; +import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.BlockContinue; @@ -13,7 +14,7 @@ public class ParagraphParser extends AbstractBlockParser { private final Paragraph block = new Paragraph(); - private LinkReferenceDefinitionParser linkReferenceDefinitionParser = new LinkReferenceDefinitionParser(); + private final LinkReferenceDefinitionParser linkReferenceDefinitionParser = new LinkReferenceDefinitionParser(); @Override public boolean canHaveLazyContinuationLines() { @@ -39,10 +40,19 @@ public void addLine(CharSequence line) { linkReferenceDefinitionParser.parse(line); } + @Override + public void addSourceSpan(SourceSpan sourceSpan) { + // Some source spans might belong to link reference definitions, others to the paragraph. + // The parser will handle that. + linkReferenceDefinitionParser.addSourceSpan(sourceSpan); + } + @Override public void closeBlock() { if (linkReferenceDefinitionParser.getParagraphContent().length() == 0) { block.unlink(); + } else { + block.getSourceSpans().addAll(linkReferenceDefinitionParser.getParagraphSourceSpans()); } } diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java index c3c49531b..3788f86f0 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java @@ -61,8 +61,8 @@ public int hashCode() { @Override public String toString() { return "SourceSpan{" + - "lineIndex=" + lineIndex + - ", columnIndex=" + columnIndex + + "line=" + lineIndex + + ", column=" + columnIndex + ", length=" + length + "}"; } 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 f806d105c..c66873dd1 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java @@ -1,6 +1,7 @@ package org.commonmark.parser.block; import org.commonmark.node.Block; +import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; public abstract class AbstractBlockParser implements BlockParser { @@ -24,6 +25,11 @@ public boolean canContain(Block childBlock) { public void addLine(CharSequence line) { } + @Override + public void addSourceSpan(SourceSpan sourceSpan) { + getBlock().getSourceSpans().add(sourceSpan); + } + @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 0c903198c..ca603ac8f 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java @@ -1,6 +1,7 @@ package org.commonmark.parser.block; import org.commonmark.node.Block; +import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; /** @@ -34,6 +35,13 @@ public interface BlockParser { void addLine(CharSequence line); + /** + * Add a source span of the currently parsed block. The default implementation in {@link AbstractBlockParser} adds + * it to the block. Unless you have some complicated parsing where you need to check source positions, you don't + * need to override this. + */ + void addSourceSpan(SourceSpan sourceSpan); + void closeBlock(); void parseInlines(InlineParser inlineParser); diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java index 4cb057b36..1b76ed5bd 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpanRenderer.java @@ -47,9 +47,10 @@ private static void appendMarkers(Map<Integer, List<String>> lineMarkers, int co private static class SourceSpanMarkersVisitor extends AbstractVisitor { + private static final String OPENING = "({[<⸢⸤"; + private static final String CLOSING = ")}]>⸣⸥"; + private final Map<Integer, Map<Integer, List<String>>> markers = new HashMap<>(); - private final String opening = "({[<⸢⸤"; - private final String closing = ")}]>⸣⸥"; private int markerIndex; @@ -61,8 +62,8 @@ public Map<Integer, Map<Integer, List<String>>> getMarkers() { protected void visitChildren(Node parent) { if (!parent.getSourceSpans().isEmpty()) { for (SourceSpan sourceSpan : parent.getSourceSpans()) { - String opener = String.valueOf(opening.charAt(markerIndex % opening.length())); - String closer = String.valueOf(closing.charAt(markerIndex % closing.length())); + 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); diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index 9e03c94da..3e2de2be2 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -5,6 +5,7 @@ import org.commonmark.node.Heading; import org.commonmark.node.HtmlBlock; import org.commonmark.node.IndentedCodeBlock; +import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.node.ListBlock; import org.commonmark.node.ListItem; import org.commonmark.node.Node; @@ -23,15 +24,11 @@ public class SourceSpansTest { private static final Parser PARSER = Parser.builder().build(); - // TODO: Tests for when column is within tab - - // TODO: Tests for where paragraph starts with link reference definitions - @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, 2, 3), SourceSpan.of(1, 2, 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)); @@ -40,14 +37,14 @@ public void paragraph() { @Test public void thematicBreak() { assertSpans("---\n", ThematicBreak.class, SourceSpan.of(0, 0, 3)); - assertSpans(" ---\n", ThematicBreak.class, SourceSpan.of(0, 2, 3)); + assertSpans(" ---\n", ThematicBreak.class, SourceSpan.of(0, 0, 5)); assertSpans("> ---\n", ThematicBreak.class, SourceSpan.of(0, 2, 3)); } @Test public void atxHeading() { assertSpans("# foo", Heading.class, SourceSpan.of(0, 0, 5)); - assertSpans(" # foo", Heading.class, SourceSpan.of(0, 1, 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)); } @@ -56,40 +53,42 @@ public void atxHeading() { 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, 2, 3), SourceSpan.of(1, 2, 3)); + 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)); } @Test public void indentedCodeBlock() { - assertSpans(" foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3)); - assertSpans(" foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 4)); - assertSpans("\tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 1, 3)); - assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 2, 3)); - assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 3, 3)); - assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3)); - assertSpans(" \tfoo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 4)); - assertSpans(" \t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 5)); - assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 1, 4)); - assertSpans("\t foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 1, 5)); - assertSpans(" foo\n bar\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3), SourceSpan.of(1, 4, 4)); - assertSpans(" foo\n\tbar\n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3), SourceSpan.of(1, 1, 3)); - assertSpans(" foo\n \n \n", IndentedCodeBlock.class, SourceSpan.of(0, 4, 3), SourceSpan.of(2, 4, 1)); - assertSpans("> foo\n", IndentedCodeBlock.class, SourceSpan.of(0, 6, 3)); + 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)); } @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)); + assertSpans("```\n foo\n```\n", FencedCodeBlock.class, + SourceSpan.of(0, 0, 3), SourceSpan.of(1, 0, 4), SourceSpan.of(2, 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)); 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(" ```\n foo\n ```\n", FencedCodeBlock.class, - SourceSpan.of(0, 3, 3), SourceSpan.of(1, 3, 3), SourceSpan.of(2, 3, 3)); + SourceSpan.of(0, 0, 6), SourceSpan.of(1, 0, 6), SourceSpan.of(2, 0, 6)); assertSpans(" ```\n foo\nfoo\n```\n", FencedCodeBlock.class, - SourceSpan.of(0, 1, 3), SourceSpan.of(1, 1, 3), SourceSpan.of(2, 0, 3), SourceSpan.of(3, 0, 3)); + SourceSpan.of(0, 0, 4), SourceSpan.of(1, 0, 4), SourceSpan.of(2, 0, 3), SourceSpan.of(3, 0, 3)); assertSpans("```info\nfoo\n```\n", FencedCodeBlock.class, SourceSpan.of(0, 0, 7), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3)); assertSpans("* ```\n foo\n ```\n", FencedCodeBlock.class, @@ -105,7 +104,10 @@ public void fencedCodeBlock() { @Test public void htmlBlock() { assertSpans("<div>\n", HtmlBlock.class, SourceSpan.of(0, 0, 5)); - assertSpans(" <div>\n foo\n </div>\n", HtmlBlock.class, SourceSpan.of(0, 0, 6), SourceSpan.of(1, 0, 4), SourceSpan.of(2, 0, 7)); + assertSpans(" <div>\n foo\n </div>\n", HtmlBlock.class, + SourceSpan.of(0, 0, 6), + SourceSpan.of(1, 0, 4), + SourceSpan.of(2, 0, 7)); assertSpans("* <div>\n", HtmlBlock.class, SourceSpan.of(0, 2, 5)); } @@ -114,8 +116,8 @@ 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, 1, 5)); - assertSpans(" > foo\n > bar\n", BlockQuote.class, SourceSpan.of(0, 3, 5), SourceSpan.of(1, 2, 5)); + 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)); // 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)); @@ -125,17 +127,17 @@ public void blockQuote() { @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, 2, 3)); + 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, 2, 5)); - assertSpans("* foo\n * bar\n", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 2, 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)); // 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, 2, 5), SourceSpan.of(2, 0, 3)); + assertSpans("* foo\n * bar\nbaz", ListBlock.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 7), SourceSpan.of(2, 0, 3)); Node document = PARSER.parse("* foo\n * bar\n"); ListBlock listBlock = (ListBlock) document.getFirstChild().getFirstChild().getLastChild(); @@ -145,11 +147,11 @@ public void listBlock() { @Test public void listItem() { assertSpans("* foo\n", ListItem.class, SourceSpan.of(0, 0, 5)); - assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 1, 5)); - assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 2, 5)); - assertSpans(" * foo\n", ListItem.class, SourceSpan.of(0, 3, 5)); - assertSpans("*\n foo\n", ListItem.class, SourceSpan.of(0, 0, 1), SourceSpan.of(1, 2, 3)); - assertSpans("*\n foo\n bar\n", ListItem.class, SourceSpan.of(0, 0, 1), SourceSpan.of(1, 2, 3), SourceSpan.of(2, 2, 3)); + 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)); // Lazy continuations @@ -157,11 +159,37 @@ public void listItem() { assertSpans("* foo\nbar\nbaz\n", ListItem.class, SourceSpan.of(0, 0, 5), SourceSpan.of(1, 0, 3), SourceSpan.of(2, 0, 3)); } + @Test + public void linkReferenceDefinition() { + // This is tricky due to how link reference definition parsing works. It is stripped from the paragraph if it's + // successfully parsed, otherwise it stays part of the paragraph. + Node document = PARSER.parse("[foo]: /url\ntext\n"); + + LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); + assertEquals(Arrays.asList(SourceSpan.of(0, 0, 11)), linkReferenceDefinition.getSourceSpans()); + + Paragraph paragraph = (Paragraph) document.getLastChild(); + assertEquals(Arrays.asList(SourceSpan.of(1, 0, 4)), paragraph.getSourceSpans()); + } + + @Test + public void linkReferenceDefinitionHeading() { + // This is probably the trickiest because we have a link reference definition at the start of a paragraph + // that gets replaced because of a heading. Phew. + Node document = PARSER.parse("[foo]: /url\nHeading\n===\n"); + + LinkReferenceDefinition linkReferenceDefinition = (LinkReferenceDefinition) document.getFirstChild(); + assertEquals(Arrays.asList(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()); + } + @Test public void visualCheck() { - assertEquals("(> {[* <foo>]})\n(> {[<bar>]})\n(> {⸢* ⸤baz⸥⸣})\n", + assertEquals("(> {[* <foo>]})\n(> {[ <bar>]})\n(> {⸢* ⸤baz⸥⸣})\n", visualizeSourceSpans("> * foo\n> bar\n> * baz\n")); - assertEquals("(> {[* <```>]})\n(> {[<foo>]})\n(> {[<```>]})\n", + assertEquals("(> {[* <```>]})\n(> {[ <foo>]})\n(> {[ <```>]})\n", visualizeSourceSpans("> * ```\n> foo\n> ```")); } From e86d5ab8ce966bbfc274305017dfbe1215ddce79 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 7 Sep 2020 16:20:34 +1000 Subject: [PATCH 425/815] Implement source spans for table nodes --- .../gfm/tables/internal/TableBlockParser.java | 112 ++++++++++++------ .../commonmark/ext/gfm/tables/TablesTest.java | 6 +- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 112764ba0..99214fcfd 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,26 +1,34 @@ package org.commonmark.ext.gfm.tables.internal; -import org.commonmark.ext.gfm.tables.*; +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.node.Block; import org.commonmark.node.Node; +import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; -import org.commonmark.parser.block.*; +import org.commonmark.parser.block.AbstractBlockParser; +import org.commonmark.parser.block.AbstractBlockParserFactory; +import org.commonmark.parser.block.BlockContinue; +import org.commonmark.parser.block.BlockStart; +import org.commonmark.parser.block.MatchedBlockParser; +import org.commonmark.parser.block.ParserState; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class TableBlockParser extends AbstractBlockParser { private final TableBlock block = new TableBlock(); - private final List<CharSequence> bodyLines = new ArrayList<>(); + private final List<CharSequence> rowLines = new ArrayList<>(); private final List<TableCell.Alignment> columns; - private final List<String> headerCells; - private boolean nextIsSeparatorLine = true; - - private TableBlockParser(List<TableCell.Alignment> columns, List<String> headerCells) { + private TableBlockParser(List<TableCell.Alignment> columns, CharSequence headerLine) { this.columns = columns; - this.headerCells = headerCells; + this.rowLines.add(headerLine); } @Override @@ -44,37 +52,45 @@ public BlockContinue tryContinue(ParserState state) { @Override public void addLine(CharSequence line) { - if (nextIsSeparatorLine) { - nextIsSeparatorLine = false; - } else { - bodyLines.add(line); - } + rowLines.add(line); } @Override public void parseInlines(InlineParser inlineParser) { - int headerColumns = headerCells.size(); + List<SourceSpan> sourceSpans = block.getSourceSpans(); + SourceSpan headerSourceSpan = !sourceSpans.isEmpty() ? sourceSpans.get(0) : null; Node head = new TableHead(); + if (headerSourceSpan != null) { + head.getSourceSpans().add(headerSourceSpan); + } block.appendChild(head); TableRow headerRow = new TableRow(); + headerRow.setSourceSpans(head.getSourceSpans()); head.appendChild(headerRow); + + List<CellSource> headerCells = split(rowLines.get(0), headerSourceSpan); + int headerColumns = headerCells.size(); for (int i = 0; i < headerColumns; i++) { - String cell = headerCells.get(i); + CellSource cell = headerCells.get(i); TableCell tableCell = parseCell(cell, i, inlineParser); tableCell.setHeader(true); headerRow.appendChild(tableCell); } - Node body = null; - for (CharSequence rowLine : bodyLines) { - List<String> cells = split(rowLine); + TableBody body = null; + // Body starts at index 2. 0 is header, 1 is separator. + for (int rowIndex = 2; rowIndex < rowLines.size(); rowIndex++) { + CharSequence rowLine = rowLines.get(rowIndex); + SourceSpan sourceSpan = sourceSpans.get(rowIndex); + List<CellSource> cells = split(rowLine, sourceSpan); TableRow row = new TableRow(); + row.getSourceSpans().add(sourceSpan); // Body can not have more columns than head for (int i = 0; i < headerColumns; i++) { - String cell = i < cells.size() ? cells.get(i) : ""; + CellSource cell = i < cells.size() ? cells.get(i) : new CellSource("", null); TableCell tableCell = parseCell(cell, i, inlineParser); row.appendChild(tableCell); } @@ -85,33 +101,35 @@ public void parseInlines(InlineParser inlineParser) { block.appendChild(body); } body.appendChild(row); + body.getSourceSpans().add(sourceSpan); } } - private TableCell parseCell(String cell, int column, InlineParser inlineParser) { + private TableCell parseCell(CellSource cell, int column, InlineParser inlineParser) { TableCell tableCell = new TableCell(); if (column < columns.size()) { tableCell.setAlignment(columns.get(column)); } - inlineParser.parse(cell.trim(), tableCell); + if (cell.sourceSpan != null) { + tableCell.setSourceSpans(Collections.singletonList(cell.sourceSpan)); + } + + inlineParser.parse(cell.content, tableCell); return tableCell; } - private static List<String> split(CharSequence input) { - String line = input.toString().trim(); - if (line.startsWith("|")) { - line = line.substring(1); - } - List<String> cells = new ArrayList<>(); + private static List<CellSource> split(CharSequence row, SourceSpan rowSourceSpan) { + int cellStart = row.charAt(0) == '|' ? 1 : 0; + List<CellSource> cells = new ArrayList<>(); StringBuilder sb = new StringBuilder(); - for (int i = 0; i < line.length(); i++) { - char c = line.charAt(i); + for (int i = cellStart; i < row.length(); i++) { + char c = row.charAt(i); switch (c) { case '\\': - if (i + 1 < line.length() && line.charAt(i + 1) == '|') { + if (i + 1 < row.length() && row.charAt(i + 1) == '|') { // Pipe is special for table parsing. An escaped pipe doesn't result in a new cell, but is // passed down to inline parsing as an unescaped pipe. Note that that applies even for the `\|` // in an input like `\\|` - in other words, table parsing doesn't support escaping backslashes. @@ -123,15 +141,19 @@ private static List<String> split(CharSequence input) { } break; case '|': - cells.add(sb.toString()); + String content = sb.toString(); + cells.add(CellSource.of(content, rowSourceSpan, cellStart)); sb.setLength(0); + // + 1 to skip the pipe itself for the next cell's span + cellStart = i + 1; break; default: sb.append(c); } } if (sb.length() > 0) { - cells.add(sb.toString()); + String content = sb.toString(); + cells.add(CellSource.of(content, rowSourceSpan, cellStart)); } return cells; } @@ -229,9 +251,9 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar CharSequence separatorLine = line.subSequence(state.getIndex(), line.length()); List<TableCell.Alignment> columns = parseSeparator(separatorLine); if (columns != null && !columns.isEmpty()) { - List<String> headerCells = split(paragraph); + List<CellSource> headerCells = split(paragraph, null); if (columns.size() >= headerCells.size()) { - return BlockStart.of(new TableBlockParser(columns, headerCells)) + return BlockStart.of(new TableBlockParser(columns, paragraph)) .atIndex(state.getIndex()) .replaceActiveBlockParser(); } @@ -241,4 +263,26 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } } + private static class CellSource { + + private final String content; + private final SourceSpan sourceSpan; + + public static CellSource of(String content, SourceSpan rowSourceSpan, int offset) { + if (!content.isEmpty()) { + SourceSpan sourceSpan = null; + if (rowSourceSpan != null) { + sourceSpan = SourceSpan.of(rowSourceSpan.getLineIndex(), rowSourceSpan.getColumnIndex() + offset, content.length()); + } + return new CellSource(content, sourceSpan); + } else { + return new CellSource(content, null); + } + } + + private CellSource(String content, SourceSpan sourceSpan) { + this.content = content; + this.sourceSpan = sourceSpan; + } + } } 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 b38358d8b..4aca197b2 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 @@ -666,7 +666,7 @@ public void sourceSpans() { assertEquals(Arrays.asList(SourceSpan.of(0, 4, 3)), headRowCell2.getSourceSpans()); TableBody body = (TableBody) block.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(2, 0, 4), SourceSpan.of(3, 1, 7), SourceSpan.of(4, 0, 3)), body.getSourceSpans()); + assertEquals(Arrays.asList(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()); @@ -676,10 +676,10 @@ public void sourceSpans() { assertEquals(Arrays.asList(SourceSpan.of(2, 3, 1)), bodyRow1Cell2.getSourceSpans()); TableRow bodyRow2 = (TableRow) body.getFirstChild().getNext(); - assertEquals(Arrays.asList(SourceSpan.of(3, 1, 7)), bodyRow2.getSourceSpans()); + assertEquals(Arrays.asList(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, 0, 2)), bodyRow2Cell1.getSourceSpans()); assertEquals(Arrays.asList(SourceSpan.of(3, 3, 4)), bodyRow2Cell2.getSourceSpans()); TableRow bodyRow3 = (TableRow) body.getLastChild(); From d766a568894682bc0774df6feeb745a549dd275a Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 7 Sep 2020 16:24:54 +1000 Subject: [PATCH 426/815] Return immutable collection from getSourceSpans Just better to not accidentally modify it. --- .../ext/gfm/tables/internal/TableBlockParser.java | 6 +++--- .../main/java/org/commonmark/internal/ParagraphParser.java | 2 +- commonmark/src/main/java/org/commonmark/node/Node.java | 7 ++++++- .../org/commonmark/parser/block/AbstractBlockParser.java | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 99214fcfd..5434dc18c 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 @@ -62,7 +62,7 @@ public void parseInlines(InlineParser inlineParser) { SourceSpan headerSourceSpan = !sourceSpans.isEmpty() ? sourceSpans.get(0) : null; Node head = new TableHead(); if (headerSourceSpan != null) { - head.getSourceSpans().add(headerSourceSpan); + head.addSourceSpan(headerSourceSpan); } block.appendChild(head); @@ -86,7 +86,7 @@ public void parseInlines(InlineParser inlineParser) { SourceSpan sourceSpan = sourceSpans.get(rowIndex); List<CellSource> cells = split(rowLine, sourceSpan); TableRow row = new TableRow(); - row.getSourceSpans().add(sourceSpan); + row.addSourceSpan(sourceSpan); // Body can not have more columns than head for (int i = 0; i < headerColumns; i++) { @@ -101,7 +101,7 @@ public void parseInlines(InlineParser inlineParser) { block.appendChild(body); } body.appendChild(row); - body.getSourceSpans().add(sourceSpan); + body.addSourceSpan(sourceSpan); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index 09cf9fdec..7e28cf0fb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -52,7 +52,7 @@ public void closeBlock() { if (linkReferenceDefinitionParser.getParagraphContent().length() == 0) { block.unlink(); } else { - block.getSourceSpans().addAll(linkReferenceDefinitionParser.getParagraphSourceSpans()); + block.setSourceSpans(linkReferenceDefinitionParser.getParagraphSourceSpans()); } } diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index 462acc545..6235f84ee 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -1,6 +1,7 @@ package org.commonmark.node; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public abstract class Node { @@ -35,13 +36,17 @@ public Node getParent() { } public List<SourceSpan> getSourceSpans() { - return sourceSpans; + return Collections.unmodifiableList(sourceSpans); } public void setSourceSpans(List<SourceSpan> sourceSpans) { this.sourceSpans = new ArrayList<>(sourceSpans); } + public void addSourceSpan(SourceSpan sourceSpan) { + this.sourceSpans.add(sourceSpan); + } + protected void setParent(Node parent) { this.parent = parent; } 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 c66873dd1..98fddd458 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java @@ -27,7 +27,7 @@ public void addLine(CharSequence line) { @Override public void addSourceSpan(SourceSpan sourceSpan) { - getBlock().getSourceSpans().add(sourceSpan); + getBlock().addSourceSpan(sourceSpan); } @Override From 8101aa1afd42525361629692c838c13db054d8bc Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 7 Sep 2020 20:14:36 +1000 Subject: [PATCH 427/815] Add more documentation, CHANGELOG --- CHANGELOG.md | 8 ++++ .../gfm/tables/internal/TableBlockParser.java | 2 +- .../commonmark/ext/gfm/tables/TablesTest.java | 7 ++- .../SourceSpanIntegrationTest.java | 25 ++++++++++ .../integration/SpecIntegrationTest.java | 8 ++-- .../commonmark/internal/DocumentParser.java | 21 ++++---- .../main/java/org/commonmark/node/Block.java | 3 ++ .../main/java/org/commonmark/node/Node.java | 48 ++++++++++++++----- .../java/org/commonmark/node/Paragraph.java | 3 ++ .../java/org/commonmark/node/SourceSpan.java | 21 +++++++- .../commonmark/parser/IncludeSourceSpans.java | 16 +++++++ .../java/org/commonmark/parser/Parser.java | 31 ++++++++++-- .../org/commonmark/test/SourceSpansTest.java | 3 +- 13 files changed, 164 insertions(+), 32 deletions(-) create mode 100644 commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e160aac..d035037dc 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 +### Added +- Support for including source spans on block nodes: + - Answer for "Where in the source input (line/column position and length) does this block come from?" + - Useful for things like editors that want to keep the input and rendered output scrolled to the same lines, + or start editing on the block that was selected. + - Use `includeSourceSpans` on `Parser.Builder` to enable, read data with `Node.getSourceSpans` + ## [0.15.2] - 2020-07-20 ### Fixed - image-attributes extension: Fix unexpected altering of text in case 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 5434dc18c..2952a8785 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 @@ -83,7 +83,7 @@ public void parseInlines(InlineParser inlineParser) { // Body starts at index 2. 0 is header, 1 is separator. for (int rowIndex = 2; rowIndex < rowLines.size(); rowIndex++) { CharSequence rowLine = rowLines.get(rowIndex); - SourceSpan sourceSpan = sourceSpans.get(rowIndex); + SourceSpan sourceSpan = rowIndex < sourceSpans.size() ? sourceSpans.get(rowIndex) : null; List<CellSource> cells = split(rowLine, sourceSpan); TableRow row = new TableRow(); row.addSourceSpan(sourceSpan); 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 4aca197b2..8af4a0ec7 100644 --- a/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java +++ b/commonmark-ext-gfm-tables/src/test/java/org/commonmark/ext/gfm/tables/TablesTest.java @@ -4,6 +4,7 @@ import org.commonmark.node.Node; import org.commonmark.node.SourceSpan; import org.commonmark.parser.Parser; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProviderContext; import org.commonmark.renderer.html.AttributeProviderFactory; @@ -648,7 +649,11 @@ public void setAttributes(Node node, String tagName, Map<String, String> attribu @Test public void sourceSpans() { - Node document = PARSER.parse("Abc|Def\n---|---\n|1|2\n 3|four|\n|||\n"); + Parser parser = Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS) + .build(); + 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), 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 new file mode 100644 index 000000000..6e51b0af5 --- /dev/null +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/SourceSpanIntegrationTest.java @@ -0,0 +1,25 @@ +package org.commonmark.integration; + +import org.commonmark.parser.IncludeSourceSpans; +import org.commonmark.parser.Parser; +import org.commonmark.testutil.example.Example; + +/** + * Spec and all extensions, with source spans enabed. + */ +public class SourceSpanIntegrationTest extends SpecIntegrationTest { + + protected static final Parser PARSER = Parser.builder() + .extensions(EXTENSIONS) + .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 ed2d22cde..13d5918b8 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 @@ -21,7 +21,7 @@ */ public class SpecIntegrationTest extends SpecTestCase { - private static final List<Extension> EXTENSIONS = Arrays.asList( + protected static final List<Extension> EXTENSIONS = Arrays.asList( AutolinkExtension.create(), ImageAttributesExtension.create(), InsExtension.create(), @@ -29,10 +29,10 @@ public class SpecIntegrationTest extends SpecTestCase { TablesExtension.create(), TaskListItemsExtension.create(), YamlFrontMatterExtension.create()); - private static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); + protected static final Parser PARSER = Parser.builder().extensions(EXTENSIONS).build(); // The spec says URL-escaping is optional, but the examples assume that it's enabled. - private static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).percentEncodeUrls(true).build(); - private static final Map<String, String> OVERRIDDEN_EXAMPLES = getOverriddenExamples(); + protected static final HtmlRenderer RENDERER = HtmlRenderer.builder().extensions(EXTENSIONS).percentEncodeUrls(true).build(); + protected static final Map<String, String> OVERRIDDEN_EXAMPLES = getOverriddenExamples(); public SpecIntegrationTest(Example example) { super(example); diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 6287d42d2..51791d90b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -15,6 +15,7 @@ import org.commonmark.node.ThematicBreak; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserFactory; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.block.BlockContinue; import org.commonmark.parser.block.BlockParser; import org.commonmark.parser.block.BlockParserFactory; @@ -92,6 +93,7 @@ public class DocumentParser implements ParserState { private final List<BlockParserFactory> blockParserFactories; private final InlineParserFactory inlineParserFactory; private final List<DelimiterProcessor> delimiterProcessors; + private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; private final Map<String, LinkReferenceDefinition> definitions = new LinkedHashMap<>(); @@ -99,10 +101,11 @@ public class DocumentParser implements ParserState { private final List<BlockParser> allBlockParsers = new ArrayList<>(); public DocumentParser(List<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory, - List<DelimiterProcessor> delimiterProcessors) { + List<DelimiterProcessor> delimiterProcessors, IncludeSourceSpans includeSourceSpans) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; this.delimiterProcessors = delimiterProcessors; + this.includeSourceSpans = includeSourceSpans; this.documentBlockParser = new DocumentBlockParser(); activateBlockParser(new OpenBlockParser(documentBlockParser, 0)); @@ -430,13 +433,15 @@ private void addLine() { } private void addSourceSpans() { - // Don't add source spans for Document itself (it would get the whole source text) - for (int i = 1; i < openBlockParsers.size(); i++) { - OpenBlockParser openBlockParser = openBlockParsers.get(i); - int blockIndex = openBlockParser.sourceIndex; - int length = line.length() - blockIndex; - if (length != 0) { - openBlockParser.blockParser.addSourceSpan(SourceSpan.of(lineIndex, blockIndex, length)); + if (includeSourceSpans != IncludeSourceSpans.NONE) { + // Don't add source spans for Document itself (it would get the whole source text) + for (int i = 1; i < openBlockParsers.size(); i++) { + OpenBlockParser openBlockParser = openBlockParsers.get(i); + int blockIndex = openBlockParser.sourceIndex; + int length = line.length() - blockIndex; + if (length != 0) { + openBlockParser.blockParser.addSourceSpan(SourceSpan.of(lineIndex, blockIndex, length)); + } } } } diff --git a/commonmark/src/main/java/org/commonmark/node/Block.java b/commonmark/src/main/java/org/commonmark/node/Block.java index e6a317d7c..753447c5c 100644 --- a/commonmark/src/main/java/org/commonmark/node/Block.java +++ b/commonmark/src/main/java/org/commonmark/node/Block.java @@ -1,5 +1,8 @@ package org.commonmark.node; +/** + * Block nodes such as paragraphs, list blocks, code blocks etc. + */ public abstract class Block extends Node { public Block getParent() { diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index 6235f84ee..f8a8cace7 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -4,6 +4,11 @@ import java.util.Collections; import java.util.List; +/** + * The base class of all CommonMark AST nodes ({@link Block} and inlines). + * <p> + * A node can have multiple children, and a parent (except for the root node). + */ public abstract class Node { private Node parent = null; @@ -11,7 +16,7 @@ public abstract class Node { private Node lastChild = null; private Node prev = null; private Node next = null; - private List<SourceSpan> sourceSpans = new ArrayList<>(); + private List<SourceSpan> sourceSpans = null; public abstract void accept(Visitor visitor); @@ -35,18 +40,6 @@ public Node getParent() { return parent; } - public List<SourceSpan> getSourceSpans() { - return Collections.unmodifiableList(sourceSpans); - } - - public void setSourceSpans(List<SourceSpan> sourceSpans) { - this.sourceSpans = new ArrayList<>(sourceSpans); - } - - public void addSourceSpan(SourceSpan sourceSpan) { - this.sourceSpans.add(sourceSpan); - } - protected void setParent(Node parent) { this.parent = parent; } @@ -121,6 +114,35 @@ public void insertBefore(Node sibling) { } } + + /** + * @return the source spans of this node if included by the parser, an empty list otherwise + */ + public List<SourceSpan> getSourceSpans() { + return sourceSpans != null ? Collections.unmodifiableList(sourceSpans) : Collections.<SourceSpan>emptyList(); + } + + /** + * Replace the current source spans with the provided list. + * + * @param sourceSpans the new source spans to set + */ + public void setSourceSpans(List<SourceSpan> sourceSpans) { + this.sourceSpans = new ArrayList<>(sourceSpans); + } + + /** + * Add a source span to the end of the list. + * + * @param sourceSpan the source span to add + */ + public void addSourceSpan(SourceSpan sourceSpan) { + if (sourceSpans == null) { + this.sourceSpans = new ArrayList<>(); + } + this.sourceSpans.add(sourceSpan); + } + @Override public String toString() { return getClass().getSimpleName() + "{" + toStringAttributes() + "}"; diff --git a/commonmark/src/main/java/org/commonmark/node/Paragraph.java b/commonmark/src/main/java/org/commonmark/node/Paragraph.java index 0c3f88f39..176eaaa76 100644 --- a/commonmark/src/main/java/org/commonmark/node/Paragraph.java +++ b/commonmark/src/main/java/org/commonmark/node/Paragraph.java @@ -1,5 +1,8 @@ package org.commonmark.node; +/** + * A paragraph block, contains inline nodes such as {@link Text} + */ public class Paragraph extends Block { @Override diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java index 3788f86f0..a643bd4dc 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java @@ -2,6 +2,25 @@ import java.util.Objects; +/** + * A source span references a snippet of text from the source input. + * <p> + * It has a starting position (line and column index) and a length of how many characters it spans. + * <p> + * For example, this CommonMark source text: + * <pre><code> + * > foo + * </code></pre> + * The {@link BlockQuote} node would have this source span: line 0, column 0, length 5. + * <p> + * The {@link Paragraph} node inside it would have: line 0, column 2, length 3. + * <p> + * If a block has multiple lines, it will have a source span for each line. + * <p> + * Note that the column index and length are measured in Java characters (UTF-16 code units). If you're outputting them + * to be consumed by another programming language, e.g. one that uses UTF-8 strings, you will need to translate them, + * otherwise characters such as emojis will result in incorrect positions. + */ public class SourceSpan { private final int lineIndex; @@ -33,7 +52,7 @@ public int getColumnIndex() { } /** - * @return length of the span + * @return length of the span in characters */ public int getLength() { return length; diff --git a/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java b/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java new file mode 100644 index 000000000..d6fc459eb --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java @@ -0,0 +1,16 @@ +package org.commonmark.parser; + +/** + * Whether to include {@link org.commonmark.node.SourceSpan} or not while parsing, + * see {@link Parser.Builder#includeSourceSpans(IncludeSourceSpans)}. + */ +public enum IncludeSourceSpans { + /** + * Do not include source spans. + */ + NONE, + /** + * Include source spans on {@link org.commonmark.node.Block} nodes. + */ + BLOCKS, +} diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 5e15158ad..a4d5c8531 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -4,7 +4,16 @@ import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.node.*; +import org.commonmark.node.Block; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.FencedCodeBlock; +import org.commonmark.node.Heading; +import org.commonmark.node.HtmlBlock; +import org.commonmark.node.IndentedCodeBlock; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.node.ListBlock; +import org.commonmark.node.Node; +import org.commonmark.node.ThematicBreak; import org.commonmark.parser.block.BlockParserFactory; import org.commonmark.parser.delimiter.DelimiterProcessor; @@ -31,12 +40,14 @@ public class Parser { private final List<DelimiterProcessor> delimiterProcessors; private final InlineParserFactory inlineParserFactory; private final List<PostProcessor> postProcessors; + private final IncludeSourceSpans includeSourceSpans; private Parser(Builder builder) { this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); this.inlineParserFactory = builder.getInlineParserFactory(); this.postProcessors = builder.postProcessors; 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. @@ -99,7 +110,7 @@ public Node parseReader(Reader input) throws IOException { } private DocumentParser createDocumentParser() { - return new DocumentParser(blockParserFactories, inlineParserFactory, delimiterProcessors); + return new DocumentParser(blockParserFactories, inlineParserFactory, delimiterProcessors, includeSourceSpans); } private Node postProcess(Node document) { @@ -118,6 +129,7 @@ public static class Builder { private final List<PostProcessor> postProcessors = new ArrayList<>(); private Set<Class<? extends Block>> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); private InlineParserFactory inlineParserFactory; + private IncludeSourceSpans includeSourceSpans = IncludeSourceSpans.NONE; /** * @return the configured {@link Parser} @@ -167,7 +179,7 @@ public Builder extensions(Iterable<? extends Extension> extensions) { * </pre> * * @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<Class<? extends Block>> enabledBlockTypes) { @@ -178,6 +190,19 @@ public Builder enabledBlockTypes(Set<Class<? extends Block>> enabledBlockTypes) return this; } + /** + * Whether to calculate {@link org.commonmark.node.SourceSpan} for {@link Node}. + * <p> + * By default, source spans are disabled. + * + * @param includeSourceSpans which kind of source spans should be included + * @return {@code this} + */ + public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { + this.includeSourceSpans = includeSourceSpans; + return this; + } + /** * Adds a custom block parser factory. * <p> diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index 3e2de2be2..f6796c925 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -13,6 +13,7 @@ import org.commonmark.node.SourceSpan; import org.commonmark.node.ThematicBreak; import org.commonmark.parser.Parser; +import org.commonmark.parser.IncludeSourceSpans; import org.junit.Test; import java.util.Arrays; @@ -22,7 +23,7 @@ public class SourceSpansTest { - private static final Parser PARSER = Parser.builder().build(); + private static final Parser PARSER = Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS).build(); @Test public void paragraph() { From db6aa0b802fb2101105a0565dcd0af0b7f29c286 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 7 Sep 2020 21:22:52 +1000 Subject: [PATCH 428/815] Remove unnecessary dependency --- commonmark-test-util/pom.xml | 7 ++----- .../test/java/org/commonmark/test/SourceSpansTest.java | 10 +++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 92046f159..cad82f51d 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.atlassian.commonmark</groupId> @@ -16,10 +17,6 @@ <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-library</artifactId> - </dependency> </dependencies> <build> diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index f6796c925..9fe1fac03 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -12,14 +12,14 @@ import org.commonmark.node.Paragraph; import org.commonmark.node.SourceSpan; import org.commonmark.node.ThematicBreak; -import org.commonmark.parser.Parser; import org.commonmark.parser.IncludeSourceSpans; +import org.commonmark.parser.Parser; import org.junit.Test; import java.util.Arrays; -import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class SourceSpansTest { @@ -99,7 +99,7 @@ public void fencedCodeBlock() { Node document = PARSER.parse("```\nfoo\n```\nbar\n"); Paragraph paragraph = (Paragraph) document.getLastChild(); - assertThat(paragraph.getSourceSpans(), contains(SourceSpan.of(3, 0, 3))); + assertEquals(Arrays.asList(SourceSpan.of(3, 0, 3)), paragraph.getSourceSpans()); } @Test @@ -142,7 +142,7 @@ public void listBlock() { Node document = PARSER.parse("* foo\n * bar\n"); ListBlock listBlock = (ListBlock) document.getFirstChild().getFirstChild().getLastChild(); - assertThat(listBlock.getSourceSpans(), contains(SourceSpan.of(1, 2, 5))); + assertEquals(Arrays.asList(SourceSpan.of(1, 2, 5)), listBlock.getSourceSpans()); } @Test From ccafb953f8fe8c12aa023de70178ab6a8c8e3572 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 8 Sep 2020 10:49:17 +1000 Subject: [PATCH 429/815] Remove unused dependencyManagement version --- pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pom.xml b/pom.xml index 2bc6e3e40..d81dc186a 100644 --- a/pom.xml +++ b/pom.xml @@ -143,11 +143,6 @@ <artifactId>junit</artifactId> <version>4.12</version> </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-library</artifactId> - <version>1.3</version> - </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> From 5790505b3544c44295b283815074900cfc0af1a9 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Sat, 25 Jul 2020 21:23:16 +1000 Subject: [PATCH 430/815] Start extracting inline parsers The end goal is to extract all current inline parsers, and then allow customizing inline parsing via the same infrastructure (similar to the current block parsing). A stretch goal is to move away from the parser input having to be a single contiguous String. This could speed up parsing and allow keeping track of source positions (line and column indexes) for inline nodes. --- .../commonmark/internal/InlineParserImpl.java | 94 +++++++------------ .../inline/BackslashInlineParser.java | 35 +++++++ .../internal/inline/InlineContentParser.java | 10 ++ .../internal/inline/InlineParserState.java | 6 ++ .../inline/LineBreakInlineContentParser.java | 36 +++++++ .../internal/inline/ParsedInline.java | 23 +++++ .../internal/inline/ParsedInlineImpl.java | 21 +++++ .../commonmark/internal/inline/Scanner.java | 31 ++++++ 8 files changed, 195 insertions(+), 61 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index dfb50149a..f42723d57 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -1,7 +1,7 @@ package org.commonmark.internal; -import org.commonmark.internal.inline.AsteriskDelimiterProcessor; -import org.commonmark.internal.inline.UnderscoreDelimiterProcessor; +import org.commonmark.internal.inline.Scanner; +import org.commonmark.internal.inline.*; import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.Html5Entities; import org.commonmark.internal.util.LinkScanner; @@ -15,7 +15,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class InlineParserImpl implements InlineParser { +public class InlineParserImpl implements InlineParser, InlineParserState { private static final String HTMLCOMMENT = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"; private static final String PROCESSINGINSTRUCTION = "[<][?].*?[?][>]"; @@ -30,8 +30,6 @@ public class InlineParserImpl implements InlineParser { private static final Pattern HTML_TAG = Pattern.compile('^' + HTMLTAG, Pattern.CASE_INSENSITIVE); - private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); - private static final Pattern ENTITY_HERE = Pattern.compile('^' + Escaping.ENTITY, Pattern.CASE_INSENSITIVE); private static final Pattern TICKS = Pattern.compile("`+"); @@ -50,12 +48,11 @@ public class InlineParserImpl implements InlineParser { private static final Pattern WHITESPACE = Pattern.compile("\\s+"); - private static final Pattern FINAL_SPACE = Pattern.compile(" *$"); - private final BitSet specialCharacters; private final BitSet delimiterCharacters; private final Map<Character, DelimiterProcessor> delimiterProcessors; private final InlineParserContext context; + private final Map<Character, List<InlineContentParser>> inlineParsers; private String input; private int index; @@ -73,10 +70,14 @@ public class InlineParserImpl implements InlineParser { public InlineParserImpl(InlineParserContext inlineParserContext) { this.delimiterProcessors = calculateDelimiterProcessors(inlineParserContext.getCustomDelimiterProcessors()); - this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); - this.specialCharacters = calculateSpecialCharacters(delimiterCharacters); this.context = inlineParserContext; + this.inlineParsers = new HashMap<>(); + this.inlineParsers.put('\n', Collections.<InlineContentParser>singletonList(new LineBreakInlineContentParser())); + this.inlineParsers.put('\\', Collections.<InlineContentParser>singletonList(new BackslashInlineParser())); + + this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); + this.specialCharacters = calculateSpecialCharacters(delimiterCharacters, inlineParsers.keySet()); } public static BitSet calculateDelimiterCharacters(Set<Character> characters) { @@ -87,10 +88,12 @@ public static BitSet calculateDelimiterCharacters(Set<Character> characters) { return bitSet; } - public static BitSet calculateSpecialCharacters(BitSet delimiterCharacters) { + public static BitSet calculateSpecialCharacters(BitSet delimiterCharacters, Set<Character> characters) { BitSet bitSet = new BitSet(); bitSet.or(delimiterCharacters); - bitSet.set('\n'); + for (Character c : characters) { + bitSet.set(c); + } bitSet.set('`'); bitSet.set('['); bitSet.set(']'); @@ -108,6 +111,12 @@ public static Map<Character, DelimiterProcessor> calculateDelimiterProcessors(Li return map; } + // TODO: The implementation shouldn't be public + @Override + public Scanner scanner() { + return new Scanner(input, index); + } + private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> map) { for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { char opening = delimiterProcessor.getOpeningCharacter(); @@ -190,14 +199,21 @@ private Node parseInline(Node previous) { return null; } + List<InlineContentParser> inlineParsers = this.inlineParsers.get(c); + if (inlineParsers != null) { + for (InlineContentParser inlineParser : inlineParsers) { + // TODO: Should we pass the whole previous node or can we make the API surface smaller? + ParsedInline parsedInline = inlineParser.tryParse(this, previous); + if (parsedInline instanceof ParsedInlineImpl) { + ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; + index += parsedInlineImpl.getConsumed(); + return parsedInlineImpl.getNode(); + } + } + } + Node node; switch (c) { - case '\n': - node = parseNewline(previous); - break; - case '\\': - node = parseBackslash(); - break; case '`': node = parseBackticks(); break; @@ -280,50 +296,6 @@ private void spnl() { match(SPNL); } - /** - * Parse a newline. If it was preceded by two spaces, return a hard line break; otherwise a soft line break. - */ - private Node parseNewline(Node previous) { - index++; // assume we're at a \n - - // Check previous text for trailing spaces. - // The "endsWith" is an optimization to avoid an RE match in the common case. - if (previous instanceof Text && ((Text) previous).getLiteral().endsWith(" ")) { - Text text = (Text) previous; - String literal = text.getLiteral(); - Matcher matcher = FINAL_SPACE.matcher(literal); - int spaces = matcher.find() ? matcher.end() - matcher.start() : 0; - if (spaces > 0) { - text.setLiteral(literal.substring(0, literal.length() - spaces)); - } - if (spaces >= 2) { - return new HardLineBreak(); - } else { - return new SoftLineBreak(); - } - } else { - return new SoftLineBreak(); - } - } - - /** - * Parse a backslash-escaped special character, adding either the escaped character, a hard line break - * (if the backslash is followed by a newline), or a literal backslash to the block's children. - */ - private Node parseBackslash() { - index++; - Node node; - if (peek() == '\n') { - node = new HardLineBreak(); - index++; - } else if (index < input.length() && ESCAPABLE.matcher(input.substring(index, index + 1)).matches()) { - node = text(input, index, index + 1); - index++; - } else { - node = text("\\"); - } - return node; - } /** * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java new file mode 100644 index 000000000..bcdc669c8 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -0,0 +1,35 @@ +package org.commonmark.internal.inline; + +import org.commonmark.internal.util.Escaping; +import org.commonmark.node.HardLineBreak; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + +import java.util.regex.Pattern; + +/** + * Parse a backslash-escaped special character, adding either the escaped character, a hard line break + * (if the backslash is followed by a newline), or a literal backslash to the block's children. + */ +public class BackslashInlineParser implements InlineContentParser { + + private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + Scanner scanner = inlineParserState.scanner(); + // Backslash + scanner.skip(); + + char next = scanner.peek(); + if (next == '\n') { + scanner.skip(); + return ParsedInline.of(new HardLineBreak(), scanner.consumed()); + } else if (ESCAPABLE.matcher(String.valueOf(next)).matches()) { + scanner.skip(); + return ParsedInline.of(new Text(String.valueOf(next)), scanner.consumed()); + } else { + return ParsedInline.of(new Text("\\"), scanner.consumed()); + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java new file mode 100644 index 000000000..76259c444 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java @@ -0,0 +1,10 @@ +package org.commonmark.internal.inline; + +import org.commonmark.node.Node; + +// TODO: I'd prefer if this was named InlineParser, but that's already public API, hmm... +public interface InlineContentParser { + + ParsedInline tryParse(InlineParserState inlineParserState, Node previous); + +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java b/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java new file mode 100644 index 000000000..9a6ef7d19 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java @@ -0,0 +1,6 @@ +package org.commonmark.internal.inline; + +public interface InlineParserState { + + Scanner scanner(); +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java new file mode 100644 index 000000000..1ca6ce531 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java @@ -0,0 +1,36 @@ +package org.commonmark.internal.inline; + +import org.commonmark.internal.util.Parsing; +import org.commonmark.node.HardLineBreak; +import org.commonmark.node.Node; +import org.commonmark.node.SoftLineBreak; +import org.commonmark.node.Text; + +/** + * Parse a newline. If it was preceded by two spaces, return a hard line break; otherwise a soft line break. + */ +public class LineBreakInlineContentParser implements InlineContentParser { + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + // Check previous text for trailing spaces. + // The "endsWith" is an optimization to avoid an RE match in the common case. + if (previous instanceof Text && ((Text) previous).getLiteral().endsWith(" ")) { + Text text = (Text) previous; + String literal = text.getLiteral(); + int last = literal.length() - 1; + int nonSpace = Parsing.skipBackwards(' ', literal, last, 0); + int spaces = last - nonSpace; + if (spaces > 0) { + text.setLiteral(literal.substring(0, literal.length() - spaces)); + } + if (spaces >= 2) { + return ParsedInline.of(new HardLineBreak(), 1); + } else { + return ParsedInline.of(new SoftLineBreak(), 1); + } + } else { + return ParsedInline.of(new SoftLineBreak(), 1); + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java new file mode 100644 index 000000000..d52caf096 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java @@ -0,0 +1,23 @@ +package org.commonmark.internal.inline; + +import org.commonmark.node.Node; + +public abstract class ParsedInline { + + protected ParsedInline() { + } + + public static ParsedInline none() { + return null; + } + + public static ParsedInline of(Node node, int consumed) { + if (node == null) { + throw new NullPointerException("node must not be null"); + } + if (consumed <= 0) { + throw new IllegalArgumentException("consumed must be greater than 0"); + } + return new ParsedInlineImpl(node, consumed); + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java new file mode 100644 index 000000000..a22163d15 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java @@ -0,0 +1,21 @@ +package org.commonmark.internal.inline; + +import org.commonmark.node.Node; + +public class ParsedInlineImpl extends ParsedInline { + private final Node node; + private final int consumed; + + public ParsedInlineImpl(Node node, int consumed) { + this.node = node; + this.consumed = consumed; + } + + public Node getNode() { + return node; + } + + public int getConsumed() { + return consumed; + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java new file mode 100644 index 000000000..16204a530 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -0,0 +1,31 @@ +package org.commonmark.internal.inline; + +public class Scanner { + + private final String input; + private int index; + private int consumed = 0; + + // TODO: Visibility + public Scanner(String input, int index) { + this.input = input; + this.index = index; + } + + public char peek() { + if (index >= input.length()) { + return '\0'; + } else { + return input.charAt(index); + } + } + + public void skip() { + index++; + consumed++; + } + + public int consumed() { + return consumed; + } +} From 15ed46759d6889a550ca2907757acbe6928a4ddc Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Sat, 25 Jul 2020 21:49:15 +1000 Subject: [PATCH 431/815] Backticks parser, still simple so far --- .../commonmark/internal/InlineParserImpl.java | 47 +------------------ .../inline/BackslashInlineParser.java | 6 +-- .../inline/BackticksInlineParser.java | 47 +++++++++++++++++++ .../inline/LineBreakInlineContentParser.java | 9 ++-- .../internal/inline/ParsedInline.java | 8 ++-- .../internal/inline/ParsedInlineImpl.java | 10 ++-- .../commonmark/internal/inline/Position.java | 14 ++++++ .../commonmark/internal/inline/Scanner.java | 33 +++++++++++-- 8 files changed, 110 insertions(+), 64 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/Position.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index f42723d57..2d4e732fc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -32,10 +32,6 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private static final Pattern ENTITY_HERE = Pattern.compile('^' + Escaping.ENTITY, Pattern.CASE_INSENSITIVE); - private static final Pattern TICKS = Pattern.compile("`+"); - - private static final Pattern TICKS_HERE = Pattern.compile("^`+"); - private static final Pattern EMAIL_AUTOLINK = 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])?)*)>"); @@ -75,6 +71,7 @@ public InlineParserImpl(InlineParserContext inlineParserContext) { this.inlineParsers = new HashMap<>(); this.inlineParsers.put('\n', Collections.<InlineContentParser>singletonList(new LineBreakInlineContentParser())); this.inlineParsers.put('\\', Collections.<InlineContentParser>singletonList(new BackslashInlineParser())); + this.inlineParsers.put('`', Collections.<InlineContentParser>singletonList(new BackticksInlineParser())); this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); this.specialCharacters = calculateSpecialCharacters(delimiterCharacters, inlineParsers.keySet()); @@ -94,10 +91,8 @@ public static BitSet calculateSpecialCharacters(BitSet delimiterCharacters, Set< for (Character c : characters) { bitSet.set(c); } - bitSet.set('`'); bitSet.set('['); bitSet.set(']'); - bitSet.set('\\'); bitSet.set('!'); bitSet.set('<'); bitSet.set('&'); @@ -206,7 +201,7 @@ private Node parseInline(Node previous) { ParsedInline parsedInline = inlineParser.tryParse(this, previous); if (parsedInline instanceof ParsedInlineImpl) { ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; - index += parsedInlineImpl.getConsumed(); + index = parsedInlineImpl.getPosition().getIndex(); return parsedInlineImpl.getNode(); } } @@ -214,9 +209,6 @@ private Node parseInline(Node previous) { Node node; switch (c) { - case '`': - node = parseBackticks(); - break; case '[': node = parseOpenBracket(); break; @@ -296,41 +288,6 @@ private void spnl() { match(SPNL); } - - /** - * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. - */ - private Node parseBackticks() { - String ticks = match(TICKS_HERE); - if (ticks == null) { - return null; - } - int afterOpenTicks = index; - String matched; - while ((matched = match(TICKS)) != null) { - if (matched.equals(ticks)) { - Code node = new Code(); - String content = input.substring(afterOpenTicks, index - ticks.length()); - content = content.replace('\n', ' '); - - // spec: If the resulting string both begins and ends with a space character, but does not consist - // entirely of space characters, a single space character is removed from the front and back. - if (content.length() >= 3 && - content.charAt(0) == ' ' && - content.charAt(content.length() - 1) == ' ' && - Parsing.hasNonSpace(content)) { - content = content.substring(1, content.length() - 1); - } - - node.setLiteral(content); - return node; - } - } - // If we got here, we didn't match a closing backtick sequence. - index = afterOpenTicks; - return text(ticks); - } - /** * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ 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 bcdc669c8..2a36c329a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -24,12 +24,12 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) char next = scanner.peek(); if (next == '\n') { scanner.skip(); - return ParsedInline.of(new HardLineBreak(), scanner.consumed()); + return ParsedInline.of(new HardLineBreak(), scanner.position()); } else if (ESCAPABLE.matcher(String.valueOf(next)).matches()) { scanner.skip(); - return ParsedInline.of(new Text(String.valueOf(next)), scanner.consumed()); + return ParsedInline.of(new Text(String.valueOf(next)), scanner.position()); } else { - return ParsedInline.of(new Text("\\"), scanner.consumed()); + return ParsedInline.of(new Text("\\"), scanner.position()); } } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java new file mode 100644 index 000000000..a7b17c46f --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -0,0 +1,47 @@ +package org.commonmark.internal.inline; + +import org.commonmark.internal.util.Parsing; +import org.commonmark.node.Code; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + +/** + * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. + */ +public class BackticksInlineParser implements InlineContentParser { + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + Scanner scanner = inlineParserState.scanner(); + Position start = scanner.position(); + int openingTicks = scanner.skip('`'); + Position afterOpening = scanner.position(); + + while (scanner.find('`')) { + Position beforeClosing = scanner.position(); + int count = scanner.skip('`'); + if (count == openingTicks) { + Code node = new Code(); + + String content = scanner.textBetween(afterOpening, beforeClosing); + content = content.replace('\n', ' '); + + // spec: If the resulting string both begins and ends with a space character, but does not consist + // entirely of space characters, a single space character is removed from the front and back. + if (content.length() >= 3 && + content.charAt(0) == ' ' && + content.charAt(content.length() - 1) == ' ' && + Parsing.hasNonSpace(content)) { + content = content.substring(1, content.length() - 1); + } + + node.setLiteral(content); + return ParsedInline.of(node, scanner.position()); + } + } + + // If we got here, we didn't find a matching closing backtick sequence. + String ticks = scanner.textBetween(start, afterOpening); + return ParsedInline.of(new Text(ticks), afterOpening); + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java index 1ca6ce531..2ef8f2328 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java @@ -13,6 +13,9 @@ public class LineBreakInlineContentParser implements InlineContentParser { @Override public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + Scanner scanner = inlineParserState.scanner(); + scanner.skip(); + // Check previous text for trailing spaces. // The "endsWith" is an optimization to avoid an RE match in the common case. if (previous instanceof Text && ((Text) previous).getLiteral().endsWith(" ")) { @@ -25,12 +28,12 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) text.setLiteral(literal.substring(0, literal.length() - spaces)); } if (spaces >= 2) { - return ParsedInline.of(new HardLineBreak(), 1); + return ParsedInline.of(new HardLineBreak(), scanner.position()); } else { - return ParsedInline.of(new SoftLineBreak(), 1); + return ParsedInline.of(new SoftLineBreak(), scanner.position()); } } else { - return ParsedInline.of(new SoftLineBreak(), 1); + return ParsedInline.of(new SoftLineBreak(), scanner.position()); } } } 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 d52caf096..7e6ece88e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInline.java @@ -11,13 +11,13 @@ public static ParsedInline none() { return null; } - public static ParsedInline of(Node node, int consumed) { + public static ParsedInline of(Node node, Position position) { if (node == null) { throw new NullPointerException("node must not be null"); } - if (consumed <= 0) { - throw new IllegalArgumentException("consumed must be greater than 0"); + if (position == null) { + throw new NullPointerException("position must not be null"); } - return new ParsedInlineImpl(node, consumed); + 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 a22163d15..aea325f27 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/ParsedInlineImpl.java @@ -4,18 +4,18 @@ public class ParsedInlineImpl extends ParsedInline { private final Node node; - private final int consumed; + private final Position position; - public ParsedInlineImpl(Node node, int consumed) { + ParsedInlineImpl(Node node, Position position) { this.node = node; - this.consumed = consumed; + this.position = position; } public Node getNode() { return node; } - public int getConsumed() { - return consumed; + public Position getPosition() { + return position; } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Position.java b/commonmark/src/main/java/org/commonmark/internal/inline/Position.java new file mode 100644 index 000000000..dff5e36df --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Position.java @@ -0,0 +1,14 @@ +package org.commonmark.internal.inline; + +public class Position { + final int index; + + Position(int index) { + this.index = index; + } + + // TODO: Move packages around so that this can stay package-private + public int getIndex() { + return index; + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 16204a530..599800845 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -1,10 +1,11 @@ package org.commonmark.internal.inline; +import org.commonmark.internal.util.Parsing; + public class Scanner { private final String input; private int index; - private int consumed = 0; // TODO: Visibility public Scanner(String input, int index) { @@ -22,10 +23,34 @@ public char peek() { public void skip() { index++; - consumed++; } - public int consumed() { - return consumed; + public int skip(char c) { + int count = 0; + while (peek() == c) { + count++; + skip(); + } + return count; + } + + public boolean find(char c) { + int newIndex = Parsing.find(c, input, index); + if (newIndex == -1) { + return false; + } else { + index = newIndex; + return true; + } + } + + // Don't expose the int index, because it would be good if we could switch input to a List<String> of lines later + // instead of one contiguous String. + public Position position() { + return new Position(index); + } + + public String textBetween(Position begin, Position end) { + return input.substring(begin.index, end.index); } } From 3b90833d866981fbb0ce342a7cbca6da0841fb4d Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Sun, 26 Jul 2020 17:34:58 +1000 Subject: [PATCH 432/815] Autolink parser --- .../commonmark/internal/InlineParserImpl.java | 32 +-------------- .../internal/inline/AutolinkInlineParser.java | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 2d4e732fc..dec9fe236 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -32,12 +32,6 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private static final Pattern ENTITY_HERE = Pattern.compile('^' + Escaping.ENTITY, Pattern.CASE_INSENSITIVE); - private static final Pattern EMAIL_AUTOLINK = Pattern - .compile("^<([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>"); - - private static final Pattern AUTOLINK = Pattern - .compile("^<[a-zA-Z][a-zA-Z0-9.+-]{1,31}:[^<>\u0000-\u0020]*>"); - private static final Pattern SPNL = Pattern.compile("^ *(?:\n *)?"); private static final Pattern UNICODE_WHITESPACE_CHAR = Pattern.compile("^[\\p{Zs}\t\r\n\f]"); @@ -72,6 +66,7 @@ public InlineParserImpl(InlineParserContext inlineParserContext) { this.inlineParsers.put('\n', Collections.<InlineContentParser>singletonList(new LineBreakInlineContentParser())); this.inlineParsers.put('\\', Collections.<InlineContentParser>singletonList(new BackslashInlineParser())); this.inlineParsers.put('`', Collections.<InlineContentParser>singletonList(new BackticksInlineParser())); + this.inlineParsers.put('<', Collections.<InlineContentParser>singletonList(new AutolinkInlineParser())); this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); this.specialCharacters = calculateSpecialCharacters(delimiterCharacters, inlineParsers.keySet()); @@ -219,10 +214,7 @@ private Node parseInline(Node previous) { node = parseCloseBracket(); break; case '<': - node = parseAutolink(); - if (node == null) { - node = parseHtmlInline(); - } + node = parseHtmlInline(); break; case '&': node = parseEntity(); @@ -533,26 +525,6 @@ int parseLinkLabel() { return contentLength + 2; } - /** - * Attempt to parse an autolink (URL or email in pointy brackets). - */ - private Node parseAutolink() { - String m; - if ((m = match(EMAIL_AUTOLINK)) != null) { - String dest = m.substring(1, m.length() - 1); - Link node = new Link("mailto:" + dest, null); - node.appendChild(new Text(dest)); - return node; - } else if ((m = match(AUTOLINK)) != null) { - String dest = m.substring(1, m.length() - 1); - Link node = new Link(dest, null); - node.appendChild(new Text(dest)); - return node; - } else { - return null; - } - } - /** * Attempt to parse inline HTML. */ diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java new file mode 100644 index 000000000..f71206fb5 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -0,0 +1,40 @@ +package org.commonmark.internal.inline; + +import org.commonmark.node.Link; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + +import java.util.regex.Pattern; + +/** + * Attempt to parse an autolink (URL or email in pointy brackets). + */ +public class AutolinkInlineParser implements InlineContentParser { + + private static final Pattern URI = Pattern + .compile("^[a-zA-Z][a-zA-Z0-9.+-]{1,31}:[^<>\u0000-\u0020]*$"); + + private static final Pattern EMAIL = Pattern + .compile("^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$"); + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + Scanner scanner = inlineParserState.scanner(); + scanner.skip(); + Position start = scanner.position(); + if (scanner.find('>')) { + String text = scanner.textBetween(start, scanner.position()); + scanner.skip(); + if (URI.matcher(text).matches()) { + Link node = new Link(text, null); + node.appendChild(new Text(text)); + return ParsedInline.of(node, scanner.position()); + } else if (EMAIL.matcher(text).matches()) { + Link node = new Link("mailto:" + text, null); + node.appendChild(new Text(text)); + return ParsedInline.of(node, scanner.position()); + } + } + return ParsedInline.none(); + } +} From 2b956ec96c47e6074e885235035a60b1e5d72523 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 27 Jul 2020 16:03:16 +1000 Subject: [PATCH 433/815] HTML inline parser Most complex one yet, but I think it's nice to get rid of the regexes. --- .../commonmark/internal/InlineParserImpl.java | 29 +-- .../internal/inline/AutolinkInlineParser.java | 2 +- .../inline/BackticksInlineParser.java | 2 +- .../internal/inline/HtmlInlineParser.java | 210 ++++++++++++++++++ .../commonmark/internal/inline/Scanner.java | 58 ++++- .../internal/util/AsciiMatcher.java | 52 +++++ .../commonmark/internal/util/CharMatcher.java | 6 + .../org/commonmark/internal/util/Parsing.java | 17 ++ .../commonmark/test/HtmlInlineParserTest.java | 27 +++ 9 files changed, 366 insertions(+), 37 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java create mode 100644 commonmark/src/main/java/org/commonmark/internal/util/CharMatcher.java create mode 100644 commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index dec9fe236..65661da32 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -5,7 +5,6 @@ import org.commonmark.internal.util.Escaping; import org.commonmark.internal.util.Html5Entities; 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; @@ -17,19 +16,10 @@ public class InlineParserImpl implements InlineParser, InlineParserState { - private static final String HTMLCOMMENT = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"; - private static final String PROCESSINGINSTRUCTION = "[<][?].*?[?][>]"; - private static final String DECLARATION = "<![A-Z]+\\s+[^>]*>"; - private static final String CDATA = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"; - private static final String HTMLTAG = "(?:" + Parsing.OPENTAG + "|" + Parsing.CLOSETAG + "|" + HTMLCOMMENT - + "|" + PROCESSINGINSTRUCTION + "|" + DECLARATION + "|" + CDATA + ")"; - private static final String ASCII_PUNCTUATION = "!\"#\\$%&'\\(\\)\\*\\+,\\-\\./:;<=>\\?@\\[\\\\\\]\\^_`\\{\\|\\}~"; private static final Pattern PUNCTUATION = Pattern .compile("^[" + ASCII_PUNCTUATION + "\\p{Pc}\\p{Pd}\\p{Pe}\\p{Pf}\\p{Pi}\\p{Po}\\p{Ps}]"); - private static final Pattern HTML_TAG = Pattern.compile('^' + HTMLTAG, Pattern.CASE_INSENSITIVE); - private static final Pattern ENTITY_HERE = Pattern.compile('^' + Escaping.ENTITY, Pattern.CASE_INSENSITIVE); private static final Pattern SPNL = Pattern.compile("^ *(?:\n *)?"); @@ -66,7 +56,7 @@ public InlineParserImpl(InlineParserContext inlineParserContext) { this.inlineParsers.put('\n', Collections.<InlineContentParser>singletonList(new LineBreakInlineContentParser())); this.inlineParsers.put('\\', Collections.<InlineContentParser>singletonList(new BackslashInlineParser())); this.inlineParsers.put('`', Collections.<InlineContentParser>singletonList(new BackticksInlineParser())); - this.inlineParsers.put('<', Collections.<InlineContentParser>singletonList(new AutolinkInlineParser())); + this.inlineParsers.put('<', Arrays.asList(new AutolinkInlineParser(), new HtmlInlineParser())); this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); this.specialCharacters = calculateSpecialCharacters(delimiterCharacters, inlineParsers.keySet()); @@ -213,9 +203,6 @@ private Node parseInline(Node previous) { case ']': node = parseCloseBracket(); break; - case '<': - node = parseHtmlInline(); - break; case '&': node = parseEntity(); break; @@ -525,20 +512,6 @@ int parseLinkLabel() { return contentLength + 2; } - /** - * Attempt to parse inline HTML. - */ - private Node parseHtmlInline() { - String m = match(HTML_TAG); - if (m != null) { - HtmlInline node = new HtmlInline(); - node.setLiteral(m); - return node; - } else { - return null; - } - } - /** * Attempt to parse a HTML style entity. */ 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 f71206fb5..8549a6d8d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -22,7 +22,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) Scanner scanner = inlineParserState.scanner(); scanner.skip(); Position start = scanner.position(); - if (scanner.find('>')) { + if (scanner.find('>') > 0) { String text = scanner.textBetween(start, scanner.position()); scanner.skip(); if (URI.matcher(text).matches()) { 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 a7b17c46f..2acca095e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -17,7 +17,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) int openingTicks = scanner.skip('`'); Position afterOpening = scanner.position(); - while (scanner.find('`')) { + while (scanner.find('`') > 0) { Position beforeClosing = scanner.position(); int count = scanner.skip('`'); if (count == openingTicks) { diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java new file mode 100644 index 000000000..cff7184ab --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -0,0 +1,210 @@ +package org.commonmark.internal.inline; + +import org.commonmark.internal.util.AsciiMatcher; +import org.commonmark.node.HtmlInline; +import org.commonmark.node.Node; + +/** + * Attempt to parse inline HTML. + */ +public class HtmlInlineParser implements InlineContentParser { + + private static final AsciiMatcher asciiLetter = AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); + + // spec: A tag name consists of an ASCII letter followed by zero or more ASCII letters, digits, or hyphens (-). + private static final AsciiMatcher tagNameStart = asciiLetter; + private static final AsciiMatcher tagNameContinue = tagNameStart.newBuilder().range('0', '9').c('-').build(); + + // spec: An attribute name consists of an ASCII letter, _, or :, followed by zero or more ASCII letters, digits, + // _, ., :, or -. (Note: This is the XML specification restricted to ASCII. HTML5 is laxer.) + private static final AsciiMatcher attributeStart = asciiLetter.newBuilder().c('_').c(':').build(); + private static final AsciiMatcher attributeContinue = attributeStart.newBuilder().range('0', '9').c('.').c('-').build(); + // spec: An unquoted attribute value is a nonempty string of characters not including whitespace, ", ', =, <, >, or `. + private static final AsciiMatcher attributeValueEnd = AsciiMatcher.builder() + .c(' ').c('\t').c('\n').c('\u000B').c('\f').c('\r') + .c('"').c('\'').c('=').c('<').c('>').c('`') + .build(); + + private static final AsciiMatcher declaration = AsciiMatcher.builder().range('A', 'Z').build(); + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + Scanner scanner = inlineParserState.scanner(); + Position start = scanner.position(); + // Skip over `<` + scanner.skip(); + + char c = scanner.peek(); + if (tagNameStart.matches(c)) { + if (tryOpenTag(scanner)) { + return htmlInline(start, scanner); + } + } else if (c == '/') { + if (tryClosingTag(scanner)) { + return htmlInline(start, scanner); + } + } else if (c == '?') { + if (tryProcessingInstruction(scanner)) { + return htmlInline(start, scanner); + } + } else if (c == '!') { + // comment, declaration or CDATA + scanner.skip(); + c = scanner.peek(); + if (c == '-') { + if (tryComment(scanner)) { + return htmlInline(start, scanner); + } + } else if (c == '[') { + if (tryCdata(scanner)) { + return htmlInline(start, scanner); + } + } else if (declaration.matches(c)) { + if (tryDeclaration(scanner)) { + return htmlInline(start, scanner); + } + } + } + + return ParsedInline.none(); + } + + private static ParsedInline htmlInline(Position start, Scanner scanner) { + HtmlInline node = new HtmlInline(); + node.setLiteral(scanner.textBetween(start, scanner.position())); + return ParsedInline.of(node, scanner.position()); + } + + private static boolean tryOpenTag(Scanner scanner) { + // spec: An open tag consists of a < character, a tag name, zero or more attributes, optional whitespace, + // an optional / character, and a > character. + scanner.skip(); + scanner.skip(tagNameContinue); + boolean whitespace = scanner.skipWhitespace() >= 1; + // spec: An attribute consists of whitespace, an attribute name, and an optional attribute value specification. + while (whitespace && scanner.skip(attributeStart) >= 1) { + scanner.skip(attributeContinue); + // spec: An attribute value specification consists of optional whitespace, a = character, + // optional whitespace, and an attribute value. + whitespace = scanner.skipWhitespace() >= 1; + if (scanner.skipOne('=')) { + scanner.skipWhitespace(); + char valueStart = scanner.peek(); + if (valueStart == '\'') { + scanner.skip(); + if (scanner.find('\'') < 0) { + return false; + } + scanner.skip(); + } else if (valueStart == '"') { + scanner.skip(); + if (scanner.find('"') < 0) { + return false; + } + scanner.skip(); + } else { + if (scanner.find(attributeValueEnd) <= 0) { + return false; + } + } + + // Whitespace is required between attributes + whitespace = scanner.skipWhitespace() >= 1; + } + } + + scanner.skipOne('/'); + return scanner.skipOne('>'); + } + + private static boolean tryClosingTag(Scanner scanner) { + // spec: A closing tag consists of the string </, a tag name, optional whitespace, and the character >. + scanner.skip(); + if (scanner.skip(tagNameStart) >= 1) { + scanner.skip(tagNameContinue); + scanner.skipWhitespace(); + return scanner.skipOne('>'); + } + return false; + } + + private static boolean tryProcessingInstruction(Scanner scanner) { + // spec: A processing instruction consists of the string <?, a string of characters not including the string ?>, + // and the string ?>. + scanner.skip(); + while (scanner.find('?') > 0) { + scanner.skip(); + if (scanner.skipOne('>')) { + return true; + } + } + return false; + } + + private static boolean tryComment(Scanner scanner) { + // spec: An HTML comment consists of <!-- + text + -->, where text does not start with > or ->, does not end + // with -, and does not contain --. (See the HTML5 spec.) + + // Skip first `-` + scanner.skip(); + if (!scanner.skipOne('-')) { + return false; + } + + if (scanner.skipOne('>')) { + return false; + } + + if (scanner.skipOne('-')) { + // Can't start with -> + if (scanner.skipOne('>')) { + return false; + } + // Empty comment + if (scanner.skipOne('-')) { + return scanner.skipOne('>'); + } + } + + while (scanner.find('-') >= 0) { + if (scanner.skipOne('-') && scanner.skipOne('-')) { + return scanner.skipOne('>'); + } + } + + return false; + } + + private static boolean tryCdata(Scanner scanner) { + // spec: A CDATA section consists of the string <![CDATA[, a string of characters not including the string ]]>, + // and the string ]]>. + + // Skip `[` + scanner.skip(); + + if (scanner.skipOne('C') && scanner.skipOne('D') && scanner.skipOne('A') && scanner.skipOne('T') && scanner.skipOne('A') + && scanner.skipOne('[')) { + while (scanner.find(']') >= 0) { + if (scanner.skipOne(']') && scanner.skipOne(']') && scanner.skipOne('>')) { + return true; + } + } + } + + return false; + } + + private static boolean tryDeclaration(Scanner scanner) { + // spec: A declaration consists of the string <!, a name consisting of one or more uppercase ASCII letters, + // whitespace, a string of characters not including the character >, and the character >. + scanner.skip(declaration); + if (scanner.skipWhitespace() <= 0) { + return false; + } + if (scanner.find('>') >= 0) { + scanner.skip(); + return true; + } + return false; + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 599800845..0aee95c8e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -1,5 +1,6 @@ package org.commonmark.internal.inline; +import org.commonmark.internal.util.CharMatcher; import org.commonmark.internal.util.Parsing; public class Scanner { @@ -25,6 +26,15 @@ public void skip() { index++; } + public boolean skipOne(char c) { + if (peek() == c) { + skip(); + return true; + } else { + return false; + } + } + public int skip(char c) { int count = 0; while (peek() == c) { @@ -34,13 +44,47 @@ public int skip(char c) { return count; } - public boolean find(char c) { - int newIndex = Parsing.find(c, input, index); - if (newIndex == -1) { - return false; - } else { - index = newIndex; - return true; + public int skip(CharMatcher matcher) { + int count = 0; + while (matcher.matches(peek())) { + count++; + skip(); + } + return count; + } + + public int skipWhitespace() { + int newIndex = Parsing.skipWhitespace(input, index, input.length()); + int count = newIndex - index; + index = newIndex; + return count; + } + + public int find(char c) { + int count = 0; + while (true) { + char cur = peek(); + if (cur == '\0') { + return -1; + } else if (cur == c) { + return count; + } + count++; + skip(); + } + } + + public int find(CharMatcher matcher) { + int count = 0; + while (true) { + char c = peek(); + if (c == '\0') { + return -1; + } else if (matcher.matches(c)) { + return count; + } + count++; + skip(); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java new file mode 100644 index 000000000..0e7ab345e --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java @@ -0,0 +1,52 @@ +package org.commonmark.internal.util; + +import java.util.BitSet; + +public class AsciiMatcher implements CharMatcher { + // TODO: Check if boolean[] is faster, see BitClass in java.util.regex.Pattern + private final BitSet set; + + private AsciiMatcher(Builder builder) { + this.set = builder.set; + } + + @Override + public boolean matches(char c) { + return set.get(c); + } + + public Builder newBuilder() { + return new Builder((BitSet) set.clone()); + } + + public static Builder builder() { + return new Builder(new BitSet()); + } + + public static class Builder { + private final BitSet set; + + 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 range(char from, char toInclusive) { + for (char c = from; c <= toInclusive; c++) { + c(c); + } + return this; + } + + public AsciiMatcher build() { + return new AsciiMatcher(this); + } + } +} diff --git a/commonmark/src/main/java/org/commonmark/internal/util/CharMatcher.java b/commonmark/src/main/java/org/commonmark/internal/util/CharMatcher.java new file mode 100644 index 000000000..de730e90d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/util/CharMatcher.java @@ -0,0 +1,6 @@ +package org.commonmark.internal.util; + +public interface CharMatcher { + + boolean matches(char c); +} 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 d429d9db0..6c59b7255 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -188,6 +188,23 @@ public static int skipSpaceTabBackwards(CharSequence s, int startIndex, int last return lastIndex - 1; } + public static int skipWhitespace(CharSequence s, int startIndex, int endIndex) { + for (int i = startIndex; i < endIndex; i++) { + switch (s.charAt(i)) { + case ' ': + case '\t': + case '\n': + case '\u000B': + case '\f': + case '\r': + break; + default: + return i; + } + } + return endIndex; + } + private static int findNonSpace(CharSequence s, int startIndex) { int length = s.length(); for (int i = startIndex; i < length; i++) { diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java new file mode 100644 index 000000000..0a406778b --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java @@ -0,0 +1,27 @@ +package org.commonmark.test; + +import org.junit.Test; + +public class HtmlInlineParserTest extends CoreRenderingTestCase { + + @Test + public void comment() { + assertRendering("inline <!---->", "<p>inline <!----></p>\n"); + assertRendering("inline <!-- -> -->", "<p>inline <!-- -> --></p>\n"); + assertRendering("inline <!--->-->", "<p>inline <!--->--></p>\n"); + } + + @Test + public void cdata() { + assertRendering("inline <![CDATA[]]>", "<p>inline <![CDATA[]]></p>\n"); + assertRendering("inline <![CDATA[ ] ]] ]]>", "<p>inline <![CDATA[ ] ]] ]]></p>\n"); + } + + @Test + public void declaration() { + // Whitespace is mandatory + assertRendering("inline <!FOO>", "<p>inline <!FOO></p>\n"); + assertRendering("inline <!FOO >", "<p>inline <!FOO ></p>\n"); + assertRendering("inline <!FOO 'bar'>", "<p>inline <!FOO 'bar'></p>\n"); + } +} From b336bf9b2dfea1b82034b17ab3a6c05d0a7672c4 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 27 Jul 2020 16:15:08 +1000 Subject: [PATCH 434/815] Remove unnecessary usage of regex for entities --- .../internal/util/Html5Entities.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) 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 5215a44df..523c596ed 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java @@ -5,24 +5,31 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class Html5Entities { private static final Map<String, String> NAMED_CHARACTER_REFERENCES = readEntities(); - private static final Pattern NUMERIC_PATTERN = Pattern.compile("^&#[Xx]?"); private static final String ENTITY_PATH = "/org/commonmark/internal/util/entities.properties"; public static String entityToString(String input) { - Matcher matcher = NUMERIC_PATTERN.matcher(input); + if (!input.startsWith("&") || !input.endsWith(";")) { + return input; + } + + String value = input.substring(1, input.length() - 1); + if (value.startsWith("#")) { + value = value.substring(1); + int base = 10; + if (value.startsWith("x") || value.startsWith("X")) { + value = value.substring(1); + base = 16; + } - if (matcher.find()) { - int base = matcher.end() == 2 ? 10 : 16; try { - int codePoint = Integer.parseInt(input.substring(matcher.end(), input.length() - 1), base); + int codePoint = Integer.parseInt(value, base); if (codePoint == 0) { return "\uFFFD"; } @@ -31,8 +38,7 @@ public static String entityToString(String input) { return "\uFFFD"; } } else { - String name = input.substring(1, input.length() - 1); - String s = NAMED_CHARACTER_REFERENCES.get(name); + String s = NAMED_CHARACTER_REFERENCES.get(value); if (s != null) { return s; } else { @@ -44,7 +50,7 @@ public static String entityToString(String input) { private static Map<String, String> readEntities() { Map<String, String> entities = new HashMap<>(); InputStream stream = Html5Entities.class.getResourceAsStream(ENTITY_PATH); - Charset charset = Charset.forName("UTF-8"); + Charset charset = StandardCharsets.UTF_8; try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, charset))) { String line; while ((line = bufferedReader.readLine()) != null) { From 47a59ade072199e16ec89a4b71f8585a461981f1 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 27 Jul 2020 16:32:15 +1000 Subject: [PATCH 435/815] Entity inline parser --- .../commonmark/internal/InlineParserImpl.java | 21 +------- .../internal/inline/EntityInlineParser.java | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 65661da32..2bc1ebf69 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -3,7 +3,6 @@ import org.commonmark.internal.inline.Scanner; import org.commonmark.internal.inline.*; import org.commonmark.internal.util.Escaping; -import org.commonmark.internal.util.Html5Entities; import org.commonmark.internal.util.LinkScanner; import org.commonmark.node.*; import org.commonmark.parser.InlineParser; @@ -20,8 +19,6 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private static final Pattern PUNCTUATION = Pattern .compile("^[" + ASCII_PUNCTUATION + "\\p{Pc}\\p{Pd}\\p{Pe}\\p{Pf}\\p{Pi}\\p{Po}\\p{Ps}]"); - private static final Pattern ENTITY_HERE = Pattern.compile('^' + Escaping.ENTITY, Pattern.CASE_INSENSITIVE); - private static final Pattern SPNL = Pattern.compile("^ *(?:\n *)?"); private static final Pattern UNICODE_WHITESPACE_CHAR = Pattern.compile("^[\\p{Zs}\t\r\n\f]"); @@ -56,6 +53,7 @@ public InlineParserImpl(InlineParserContext inlineParserContext) { this.inlineParsers.put('\n', Collections.<InlineContentParser>singletonList(new LineBreakInlineContentParser())); this.inlineParsers.put('\\', Collections.<InlineContentParser>singletonList(new BackslashInlineParser())); this.inlineParsers.put('`', Collections.<InlineContentParser>singletonList(new BackticksInlineParser())); + this.inlineParsers.put('&', Collections.<InlineContentParser>singletonList(new EntityInlineParser())); this.inlineParsers.put('<', Arrays.asList(new AutolinkInlineParser(), new HtmlInlineParser())); this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); @@ -79,8 +77,6 @@ public static BitSet calculateSpecialCharacters(BitSet delimiterCharacters, Set< bitSet.set('['); bitSet.set(']'); bitSet.set('!'); - bitSet.set('<'); - bitSet.set('&'); return bitSet; } @@ -203,9 +199,6 @@ private Node parseInline(Node previous) { case ']': node = parseCloseBracket(); break; - case '&': - node = parseEntity(); - break; default: boolean isDelimiter = delimiterCharacters.get(c); if (isDelimiter) { @@ -512,18 +505,6 @@ int parseLinkLabel() { return contentLength + 2; } - /** - * Attempt to parse a HTML style entity. - */ - private Node parseEntity() { - String m; - if ((m = match(ENTITY_HERE)) != null) { - return text(Html5Entities.entityToString(m)); - } else { - return null; - } - } - /** * Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string. */ diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java new file mode 100644 index 000000000..aef2d7850 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -0,0 +1,53 @@ +package org.commonmark.internal.inline; + +import org.commonmark.internal.util.AsciiMatcher; +import org.commonmark.internal.util.Html5Entities; +import org.commonmark.node.Node; +import org.commonmark.node.Text; + +/** + * Attempts to parse a HTML entity or numeric character reference. + */ +public class EntityInlineParser implements InlineContentParser { + + private static final AsciiMatcher hex = AsciiMatcher.builder().range('0', '9').range('A', 'F').range('a', 'f').build(); + private static final AsciiMatcher dec = AsciiMatcher.builder().range('0', '9').build(); + private static final AsciiMatcher entityStart = AsciiMatcher.builder().range('A', 'Z').range('a', 'z').build(); + private static final AsciiMatcher entityContinue = entityStart.newBuilder().range('0', '9').build(); + + @Override + public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + Scanner scanner = inlineParserState.scanner(); + Position start = scanner.position(); + // Skip `&` + scanner.skip(); + + char c = scanner.peek(); + if (c == '#') { + // Numeric + scanner.skip(); + if (scanner.skipOne('x') || scanner.skipOne('X')) { + int digits = scanner.skip(hex); + if (1 <= digits && digits <= 6 && scanner.skipOne(';')) { + return entity(scanner, start); + } + } else { + int digits = scanner.skip(dec); + if (1 <= digits && digits <= 7 && scanner.skipOne(';')) { + return entity(scanner, start); + } + } + } else if (entityStart.matches(c)) { + scanner.skip(entityContinue); + if (scanner.skipOne(';')) { + return entity(scanner, start); + } + } + + return ParsedInline.none(); + } + + private ParsedInline entity(Scanner scanner, Position start) { + return ParsedInline.of(new Text(Html5Entities.entityToString(scanner.textBetween(start, scanner.position()))), scanner.position()); + } +} From 3a6bec1bd0b3413d02804ba542cbdefe35735c28 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 27 Jul 2020 17:13:37 +1000 Subject: [PATCH 436/815] Rename methods --- .../internal/inline/AutolinkInlineParser.java | 4 +- .../inline/BackslashInlineParser.java | 6 +- .../inline/BackticksInlineParser.java | 4 +- .../internal/inline/EntityInlineParser.java | 18 ++-- .../internal/inline/HtmlInlineParser.java | 82 +++++++++---------- .../inline/LineBreakInlineContentParser.java | 2 +- .../commonmark/internal/inline/Scanner.java | 20 ++--- 7 files changed, 68 insertions(+), 68 deletions(-) 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 8549a6d8d..629ec6619 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -20,11 +20,11 @@ public class AutolinkInlineParser implements InlineContentParser { @Override public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { Scanner scanner = inlineParserState.scanner(); - scanner.skip(); + scanner.next(); Position start = scanner.position(); if (scanner.find('>') > 0) { String text = scanner.textBetween(start, scanner.position()); - scanner.skip(); + scanner.next(); if (URI.matcher(text).matches()) { Link node = new Link(text, null); node.appendChild(new Text(text)); 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 2a36c329a..cd87f7399 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -19,14 +19,14 @@ public class BackslashInlineParser implements InlineContentParser { public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { Scanner scanner = inlineParserState.scanner(); // Backslash - scanner.skip(); + scanner.next(); char next = scanner.peek(); if (next == '\n') { - scanner.skip(); + scanner.next(); return ParsedInline.of(new HardLineBreak(), scanner.position()); } else if (ESCAPABLE.matcher(String.valueOf(next)).matches()) { - scanner.skip(); + scanner.next(); return ParsedInline.of(new Text(String.valueOf(next)), scanner.position()); } else { return ParsedInline.of(new Text("\\"), scanner.position()); 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 2acca095e..5dedad8d3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -14,12 +14,12 @@ public class BackticksInlineParser implements InlineContentParser { public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { Scanner scanner = inlineParserState.scanner(); Position start = scanner.position(); - int openingTicks = scanner.skip('`'); + int openingTicks = scanner.matchMultiple('`'); Position afterOpening = scanner.position(); while (scanner.find('`') > 0) { Position beforeClosing = scanner.position(); - int count = scanner.skip('`'); + int count = scanner.matchMultiple('`'); if (count == openingTicks) { Code node = new Code(); 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 aef2d7850..79fe294d6 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -20,26 +20,26 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) Scanner scanner = inlineParserState.scanner(); Position start = scanner.position(); // Skip `&` - scanner.skip(); + scanner.next(); char c = scanner.peek(); if (c == '#') { // Numeric - scanner.skip(); - if (scanner.skipOne('x') || scanner.skipOne('X')) { - int digits = scanner.skip(hex); - if (1 <= digits && digits <= 6 && scanner.skipOne(';')) { + scanner.next(); + if (scanner.next('x') || scanner.next('X')) { + int digits = scanner.match(hex); + if (1 <= digits && digits <= 6 && scanner.next(';')) { return entity(scanner, start); } } else { - int digits = scanner.skip(dec); - if (1 <= digits && digits <= 7 && scanner.skipOne(';')) { + int digits = scanner.match(dec); + if (1 <= digits && digits <= 7 && scanner.next(';')) { return entity(scanner, start); } } } else if (entityStart.matches(c)) { - scanner.skip(entityContinue); - if (scanner.skipOne(';')) { + scanner.match(entityContinue); + if (scanner.next(';')) { return entity(scanner, start); } } 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 cff7184ab..ea4511ab3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -32,7 +32,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) Scanner scanner = inlineParserState.scanner(); Position start = scanner.position(); // Skip over `<` - scanner.skip(); + scanner.next(); char c = scanner.peek(); if (tagNameStart.matches(c)) { @@ -49,7 +49,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) } } else if (c == '!') { // comment, declaration or CDATA - scanner.skip(); + scanner.next(); c = scanner.peek(); if (c == '-') { if (tryComment(scanner)) { @@ -78,30 +78,30 @@ private static ParsedInline htmlInline(Position start, Scanner scanner) { private static boolean tryOpenTag(Scanner scanner) { // spec: An open tag consists of a < character, a tag name, zero or more attributes, optional whitespace, // an optional / character, and a > character. - scanner.skip(); - scanner.skip(tagNameContinue); - boolean whitespace = scanner.skipWhitespace() >= 1; + scanner.next(); + scanner.match(tagNameContinue); + boolean whitespace = scanner.whitespace() >= 1; // spec: An attribute consists of whitespace, an attribute name, and an optional attribute value specification. - while (whitespace && scanner.skip(attributeStart) >= 1) { - scanner.skip(attributeContinue); + while (whitespace && scanner.match(attributeStart) >= 1) { + scanner.match(attributeContinue); // spec: An attribute value specification consists of optional whitespace, a = character, // optional whitespace, and an attribute value. - whitespace = scanner.skipWhitespace() >= 1; - if (scanner.skipOne('=')) { - scanner.skipWhitespace(); + whitespace = scanner.whitespace() >= 1; + if (scanner.next('=')) { + scanner.whitespace(); char valueStart = scanner.peek(); if (valueStart == '\'') { - scanner.skip(); + scanner.next(); if (scanner.find('\'') < 0) { return false; } - scanner.skip(); + scanner.next(); } else if (valueStart == '"') { - scanner.skip(); + scanner.next(); if (scanner.find('"') < 0) { return false; } - scanner.skip(); + scanner.next(); } else { if (scanner.find(attributeValueEnd) <= 0) { return false; @@ -109,21 +109,21 @@ private static boolean tryOpenTag(Scanner scanner) { } // Whitespace is required between attributes - whitespace = scanner.skipWhitespace() >= 1; + whitespace = scanner.whitespace() >= 1; } } - scanner.skipOne('/'); - return scanner.skipOne('>'); + scanner.next('/'); + return scanner.next('>'); } private static boolean tryClosingTag(Scanner scanner) { // spec: A closing tag consists of the string </, a tag name, optional whitespace, and the character >. - scanner.skip(); - if (scanner.skip(tagNameStart) >= 1) { - scanner.skip(tagNameContinue); - scanner.skipWhitespace(); - return scanner.skipOne('>'); + scanner.next(); + if (scanner.match(tagNameStart) >= 1) { + scanner.match(tagNameContinue); + scanner.whitespace(); + return scanner.next('>'); } return false; } @@ -131,10 +131,10 @@ private static boolean tryClosingTag(Scanner scanner) { private static boolean tryProcessingInstruction(Scanner scanner) { // spec: A processing instruction consists of the string <?, a string of characters not including the string ?>, // and the string ?>. - scanner.skip(); + scanner.next(); while (scanner.find('?') > 0) { - scanner.skip(); - if (scanner.skipOne('>')) { + scanner.next(); + if (scanner.next('>')) { return true; } } @@ -146,29 +146,29 @@ private static boolean tryComment(Scanner scanner) { // with -, and does not contain --. (See the HTML5 spec.) // Skip first `-` - scanner.skip(); - if (!scanner.skipOne('-')) { + scanner.next(); + if (!scanner.next('-')) { return false; } - if (scanner.skipOne('>')) { + if (scanner.next('>')) { return false; } - if (scanner.skipOne('-')) { + if (scanner.next('-')) { // Can't start with -> - if (scanner.skipOne('>')) { + if (scanner.next('>')) { return false; } // Empty comment - if (scanner.skipOne('-')) { - return scanner.skipOne('>'); + if (scanner.next('-')) { + return scanner.next('>'); } } while (scanner.find('-') >= 0) { - if (scanner.skipOne('-') && scanner.skipOne('-')) { - return scanner.skipOne('>'); + if (scanner.next('-') && scanner.next('-')) { + return scanner.next('>'); } } @@ -180,12 +180,12 @@ private static boolean tryCdata(Scanner scanner) { // and the string ]]>. // Skip `[` - scanner.skip(); + scanner.next(); - if (scanner.skipOne('C') && scanner.skipOne('D') && scanner.skipOne('A') && scanner.skipOne('T') && scanner.skipOne('A') - && scanner.skipOne('[')) { + if (scanner.next('C') && scanner.next('D') && scanner.next('A') && scanner.next('T') && scanner.next('A') + && scanner.next('[')) { while (scanner.find(']') >= 0) { - if (scanner.skipOne(']') && scanner.skipOne(']') && scanner.skipOne('>')) { + if (scanner.next(']') && scanner.next(']') && scanner.next('>')) { return true; } } @@ -197,12 +197,12 @@ private static boolean tryCdata(Scanner scanner) { private static boolean tryDeclaration(Scanner scanner) { // spec: A declaration consists of the string <!, a name consisting of one or more uppercase ASCII letters, // whitespace, a string of characters not including the character >, and the character >. - scanner.skip(declaration); - if (scanner.skipWhitespace() <= 0) { + scanner.match(declaration); + if (scanner.whitespace() <= 0) { return false; } if (scanner.find('>') >= 0) { - scanner.skip(); + scanner.next(); return true; } return false; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java index 2ef8f2328..1b85c6eec 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java @@ -14,7 +14,7 @@ public class LineBreakInlineContentParser implements InlineContentParser { @Override public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { Scanner scanner = inlineParserState.scanner(); - scanner.skip(); + scanner.next(); // Check previous text for trailing spaces. // The "endsWith" is an optimization to avoid an RE match in the common case. diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 0aee95c8e..192285b1a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -22,38 +22,38 @@ public char peek() { } } - public void skip() { + public void next() { index++; } - public boolean skipOne(char c) { + public boolean next(char c) { if (peek() == c) { - skip(); + next(); return true; } else { return false; } } - public int skip(char c) { + public int matchMultiple(char c) { int count = 0; while (peek() == c) { count++; - skip(); + next(); } return count; } - public int skip(CharMatcher matcher) { + public int match(CharMatcher matcher) { int count = 0; while (matcher.matches(peek())) { count++; - skip(); + next(); } return count; } - public int skipWhitespace() { + public int whitespace() { int newIndex = Parsing.skipWhitespace(input, index, input.length()); int count = newIndex - index; index = newIndex; @@ -70,7 +70,7 @@ public int find(char c) { return count; } count++; - skip(); + next(); } } @@ -84,7 +84,7 @@ public int find(CharMatcher matcher) { return count; } count++; - skip(); + next(); } } From 6ea9ae53ac02d61bf2c5aa6b7af7e038f376fa94 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 27 Jul 2020 21:09:16 +1000 Subject: [PATCH 437/815] Use Scanner for link parsing too Getting really close to not needing `match` in InlineParserImpl anymore! --- .../commonmark/internal/InlineParserImpl.java | 79 +++++----- .../LinkReferenceDefinitionParser.java | 139 +++++++++--------- .../internal/inline/AutolinkInlineParser.java | 2 +- .../inline/BackticksInlineParser.java | 4 +- .../internal/inline/EntityInlineParser.java | 3 +- .../internal/inline/HtmlInlineParser.java | 3 +- .../commonmark/internal/inline/Scanner.java | 14 +- .../commonmark/internal/util/Escaping.java | 6 - .../commonmark/internal/util/LinkScanner.java | 131 +++++++++-------- .../org/commonmark/internal/util/Parsing.java | 72 +++++---- .../internal/inline/ScannerTest.java | 20 +++ 11 files changed, 263 insertions(+), 210 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 2bc1ebf69..ae6598c91 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -93,6 +93,10 @@ public Scanner scanner() { return new Scanner(input, index); } + private void setPosition(Position position) { + index = position.getIndex(); + } + private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> map) { for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { char opening = delimiterProcessor.getOpeningCharacter(); @@ -182,7 +186,7 @@ private Node parseInline(Node previous) { ParsedInline parsedInline = inlineParser.tryParse(this, previous); if (parsedInline instanceof ParsedInlineImpl) { ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; - index = parsedInlineImpl.getPosition().getIndex(); + setPosition(parsedInlineImpl.getPosition()); return parsedInlineImpl.getNode(); } } @@ -372,21 +376,18 @@ private Node parseCloseBracket() { if (!isLinkOrImage) { // See if there's a link label like `[bar]` or `[]` - int beforeLabel = index; - parseLinkLabel(); - int labelLength = index - beforeLabel; - String ref = null; - if (labelLength > 2) { - ref = input.substring(beforeLabel, beforeLabel + labelLength); - } else if (!opener.bracketAfter) { + String ref = parseLinkLabel(); + 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 = input.substring(opener.index, startIndex); + // Strip '[' and ']' + ref = ref.substring(1, ref.length() - 1); } if (ref != null) { - String label = Escaping.normalizeReference(ref); + String label = Escaping.normalizeLabelContent(ref); LinkReferenceDefinition definition = context.getLinkReferenceDefinition(label); if (definition != null) { dest = definition.getDestination(); @@ -451,20 +452,23 @@ private void removeLastBracket() { * Attempt to parse link destination, returning the string or null if no match. */ private String parseLinkDestination() { - int afterDest = LinkScanner.scanLinkDestination(input, index); - if (afterDest == -1) { + Scanner scanner = scanner(); + char delimiter = scanner.peek(); + Position start = scanner.position(); + if (!LinkScanner.scanLinkDestination(scanner)) { return null; } String dest; - if (peek() == '<') { + if (delimiter == '<') { // chop off surrounding <..>: - dest = input.substring(index + 1, afterDest - 1); + CharSequence rawDestination = scanner.textBetween(start, scanner.position()); + dest = rawDestination.subSequence(1, rawDestination.length() - 1).toString(); } else { - dest = input.substring(index, afterDest); + dest = scanner.textBetween(start, scanner.position()).toString(); } - index = afterDest; + setPosition(scanner.position()); return Escaping.unescapeString(dest); } @@ -472,37 +476,46 @@ private String parseLinkDestination() { * Attempt to parse link title (sans quotes), returning the string or null if no match. */ private String parseLinkTitle() { - int afterTitle = LinkScanner.scanLinkTitle(input, index); - if (afterTitle == -1) { + Scanner scanner = scanner(); + Position start = scanner.position(); + if (!LinkScanner.scanLinkTitle(scanner)) { return null; } // chop off ', " or parens - String title = input.substring(index + 1, afterTitle - 1); - index = afterTitle; + CharSequence rawTitle = scanner.textBetween(start, scanner.position()); + String title = rawTitle.subSequence(1, rawTitle.length() - 1).toString(); + setPosition(scanner.position()); return Escaping.unescapeString(title); } /** - * Attempt to parse a link label, returning number of characters parsed. + * Attempt to parse a link label, returning the label between the brackets or null. */ - int parseLinkLabel() { - if (index >= input.length() || input.charAt(index) != '[') { - return 0; + String parseLinkLabel() { + Scanner scanner = scanner(); + if (!scanner.next('[')) { + return null; } - int startContent = index + 1; - int endContent = LinkScanner.scanLinkLabelContent(input, startContent); - // spec: A link label can have at most 999 characters inside the square brackets. - int contentLength = endContent - startContent; - if (endContent == -1 || contentLength > 999) { - return 0; + Position start = scanner.position(); + if (!LinkScanner.scanLinkLabelContent(scanner)) { + return null; + } + Position end = scanner.position(); + + if (!scanner.next(']')) { + return null; } - if (endContent >= input.length() || input.charAt(endContent) != ']') { - return 0; + + String content = scanner.textBetween(start, end).toString(); + // spec: A link label can have at most 999 characters inside the square brackets. + if (content.length() > 999) { + return null; } - index = endContent + 1; - return contentLength + 2; + + setPosition(scanner.position()); + return content; } /** diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index e0c23160e..a6cd57228 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -1,8 +1,9 @@ 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.internal.util.Parsing; import org.commonmark.node.LinkReferenceDefinition; import org.commonmark.node.SourceSpan; @@ -35,8 +36,9 @@ public void parse(CharSequence line) { } paragraph.append(line); - int i = 0; - while (i < line.length()) { + Scanner scanner = new Scanner(line, 0); + while (scanner.hasNext()) { + boolean success; switch (state) { case PARAGRAPH: { // We're in a paragraph now. Link reference definitions can only appear at the beginning, so once @@ -44,28 +46,31 @@ public void parse(CharSequence line) { return; } case START_DEFINITION: { - i = startDefinition(line, i); + success = startDefinition(scanner); break; } case LABEL: { - i = label(line, i); + success = label(scanner); break; } case DESTINATION: { - i = destination(line, i); + success = destination(scanner); break; } case START_TITLE: { - i = startTitle(line, i); + success = startTitle(scanner); break; } case TITLE: { - i = title(line, i); + success = title(scanner); break; } + default: { + throw new IllegalStateException("Unknown parsing state: " + state); + } } - // -1 is returned if parsing failed, which means we fall back to treating text as a paragraph. - if (i == -1) { + // Parsing failed, which means we fall back to treating text as a paragraph. + if (!success) { state = State.PARAGRAPH; return; } @@ -93,96 +98,95 @@ State getState() { return state; } - private int startDefinition(CharSequence line, int i) { - i = Parsing.skipSpaceTab(line, i, line.length()); - if (i >= line.length() || line.charAt(i) != '[') { - return -1; + private boolean startDefinition(Scanner scanner) { + scanner.whitespace(); + if (!scanner.next('[')) { + return false; } state = State.LABEL; label = new StringBuilder(); - int labelStart = i + 1; - if (labelStart >= line.length()) { + if (!scanner.hasNext()) { label.append('\n'); } - - return labelStart; + return true; } - private int label(CharSequence line, int i) { - int afterLabel = LinkScanner.scanLinkLabelContent(line, i); - if (afterLabel == -1) { - return -1; + private boolean label(Scanner scanner) { + Position start = scanner.position(); + if (!LinkScanner.scanLinkLabelContent(scanner)) { + return false; } - label.append(line, i, afterLabel); + label.append(scanner.textBetween(start, scanner.position())); - if (afterLabel >= line.length()) { + if (!scanner.hasNext()) { // label might continue on next line label.append('\n'); - return afterLabel; - } else if (line.charAt(afterLabel) == ']') { - int colon = afterLabel + 1; + return true; + } else if (scanner.next(']')) { // end of label - if (colon >= line.length() || line.charAt(colon) != ':') { - return -1; + if (!scanner.next(':')) { + return false; } // spec: A link label can have at most 999 characters inside the square brackets. if (label.length() > 999) { - return -1; + return false; } String normalizedLabel = Escaping.normalizeLabelContent(label.toString()); if (normalizedLabel.isEmpty()) { - return -1; + return false; } this.normalizedLabel = normalizedLabel; state = State.DESTINATION; - return Parsing.skipSpaceTab(line, colon + 1, line.length()); + scanner.whitespace(); + return true; } else { - return -1; + return false; } } - private int destination(CharSequence line, int i) { - i = Parsing.skipSpaceTab(line, i, line.length()); - int afterDestination = LinkScanner.scanLinkDestination(line, i); - if (afterDestination == -1) { - return -1; + private boolean destination(Scanner scanner) { + scanner.whitespace(); + Position start = scanner.position(); + if (!LinkScanner.scanLinkDestination(scanner)) { + return false; } - destination = (line.charAt(i) == '<') - ? line.subSequence(i + 1, afterDestination - 1).toString() - : line.subSequence(i, afterDestination).toString(); + String rawDestination = scanner.textBetween(start, scanner.position()).toString(); + destination = rawDestination.startsWith("<") ? + rawDestination.substring(1, rawDestination.length() - 1) : + rawDestination; - int afterSpace = Parsing.skipSpaceTab(line, afterDestination, line.length()); - if (afterSpace >= line.length()) { + int whitespace = scanner.whitespace(); + if (!scanner.hasNext()) { // Destination was at end of line, so this is a valid reference for sure (and maybe a title). // If not at end of line, wait for title to be valid first. referenceValid = true; paragraph.setLength(0); - } else if (afterSpace == afterDestination) { + } else if (whitespace == 0) { // spec: The title must be separated from the link destination by whitespace - return -1; + return false; } state = State.START_TITLE; - return afterSpace; + return true; } - private int startTitle(CharSequence line, int i) { - i = Parsing.skipSpaceTab(line, i, line.length()); - if (i >= line.length()) { + private boolean startTitle(Scanner scanner) { + scanner.whitespace(); + if (!scanner.hasNext()) { state = State.START_DEFINITION; - return i; + return true; } titleDelimiter = '\0'; - char c = line.charAt(i); + char c = scanner.peek(); switch (c) { case '"': case '\'': @@ -196,8 +200,8 @@ private int startTitle(CharSequence line, int i) { if (titleDelimiter != '\0') { state = State.TITLE; title = new StringBuilder(); - i++; - if (i == line.length()) { + scanner.next(); + if (!scanner.hasNext()) { title.append('\n'); } } else { @@ -205,29 +209,30 @@ private int startTitle(CharSequence line, int i) { // There might be another reference instead, try that for the same character. state = State.START_DEFINITION; } - return i; + return true; } - private int title(CharSequence line, int i) { - int afterTitle = LinkScanner.scanLinkTitleContent(line, i, titleDelimiter); - if (afterTitle == -1) { + private boolean title(Scanner scanner) { + Position start = scanner.position(); + if (!LinkScanner.scanLinkTitleContent(scanner, titleDelimiter)) { // Invalid title, stop - return -1; + return false; } - title.append(line.subSequence(i, afterTitle)); + title.append(scanner.textBetween(start, scanner.position())); - if (afterTitle >= line.length()) { - // Title still going, continue on next line + if (!scanner.hasNext()) { + // Title ran until the end of line, so continue on next line (until we find the delimiter) title.append('\n'); - return afterTitle; + return true; } - int afterTitleDelimiter = afterTitle + 1; - int afterSpace = Parsing.skipSpaceTab(line, afterTitleDelimiter, line.length()); - if (afterSpace != line.length()) { + // Skip delimiter character + scanner.next(); + scanner.whitespace(); + if (scanner.hasNext()) { // spec: No further non-whitespace characters may occur on the line. - return -1; + return false; } referenceValid = true; finishReference(); @@ -235,7 +240,7 @@ private int title(CharSequence line, int i) { // See if there's another definition. state = State.START_DEFINITION; - return afterSpace; + return true; } private void finishReference() { 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 629ec6619..acf55a796 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -23,7 +23,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) scanner.next(); Position start = scanner.position(); if (scanner.find('>') > 0) { - String text = scanner.textBetween(start, scanner.position()); + String text = scanner.textBetween(start, scanner.position()).toString(); scanner.next(); if (URI.matcher(text).matches()) { Link node = new Link(text, null); 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 5dedad8d3..9979bde86 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -23,7 +23,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) if (count == openingTicks) { Code node = new Code(); - String content = scanner.textBetween(afterOpening, beforeClosing); + String content = scanner.textBetween(afterOpening, beforeClosing).toString(); content = content.replace('\n', ' '); // spec: If the resulting string both begins and ends with a space character, but does not consist @@ -41,7 +41,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) } // If we got here, we didn't find a matching closing backtick sequence. - String ticks = scanner.textBetween(start, afterOpening); + String ticks = scanner.textBetween(start, afterOpening).toString(); return ParsedInline.of(new Text(ticks), afterOpening); } } 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 79fe294d6..d44ee4217 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -48,6 +48,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) } private ParsedInline entity(Scanner scanner, Position start) { - return ParsedInline.of(new Text(Html5Entities.entityToString(scanner.textBetween(start, scanner.position()))), scanner.position()); + String text = scanner.textBetween(start, scanner.position()).toString(); + return ParsedInline.of(new Text(Html5Entities.entityToString(text)), scanner.position()); } } 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 ea4511ab3..4c25c3e58 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -70,8 +70,9 @@ public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) } private static ParsedInline htmlInline(Position start, Scanner scanner) { + String text = scanner.textBetween(start, scanner.position()).toString(); HtmlInline node = new HtmlInline(); - node.setLiteral(scanner.textBetween(start, scanner.position())); + node.setLiteral(text); return ParsedInline.of(node, scanner.position()); } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 192285b1a..cb87248d1 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -5,11 +5,11 @@ public class Scanner { - private final String input; + private final CharSequence input; private int index; // TODO: Visibility - public Scanner(String input, int index) { + public Scanner(CharSequence input, int index) { this.input = input; this.index = index; } @@ -22,6 +22,10 @@ public char peek() { } } + public boolean hasNext() { + return index < input.length(); + } + public void next() { index++; } @@ -94,7 +98,9 @@ public Position position() { return new Position(index); } - public String textBetween(Position begin, Position end) { - return input.substring(begin.index, end.index); + // For cases where the caller appends the result to a StringBuilder, we could offer another method to avoid some + // unnecessary copying. + public CharSequence textBetween(Position begin, Position end) { + return input.subSequence(begin.index, end.index); } } 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 15197556c..2b34f6190 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -111,12 +111,6 @@ public static String percentEncodeUrl(String s) { return replaceAll(ESCAPE_IN_URI, s, URI_REPLACER); } - public static String normalizeReference(String input) { - // Strip '[' and ']' - String stripped = input.substring(1, input.length() - 1); - return normalizeLabelContent(stripped); - } - public static String normalizeLabelContent(String input) { String trimmed = input.trim(); String lowercase = trimmed.toLowerCase(Locale.ROOT); 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 f25cd59e5..3ca34c5f0 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/LinkScanner.java @@ -1,69 +1,76 @@ package org.commonmark.internal.util; +import org.commonmark.internal.inline.Scanner; + public class LinkScanner { /** - * Attempt to scan the contents of a link label (inside the brackets), returning the position after the content or - * -1. The returned position can either be the closing {@code ]}, or the end of the line if the label continues on + * Attempt to scan the contents of a link label (inside the brackets), stopping after the content or returning false. + * The stopped position can bei either the closing {@code ]}, or the end of the line if the label continues on * the next line. */ - public static int scanLinkLabelContent(CharSequence input, int start) { - for (int i = start; i < input.length(); i++) { - char c = input.charAt(i); - switch (c) { + public static boolean scanLinkLabelContent(Scanner scanner) { + while (scanner.hasNext()) { + switch (scanner.peek()) { case '\\': - if (Parsing.isEscapable(input, i + 1)) { - i += 1; + scanner.next(); + if (Parsing.isEscapable(scanner.peek())) { + scanner.next(); } break; case ']': - return i; + return true; case '[': // spec: Unescaped square bracket characters are not allowed inside the opening and closing // square brackets of link labels. - return -1; + return false; + default: + scanner.next(); } } - return input.length(); + return true; } /** - * Attempt to scan a link destination, returning the position after the destination or -1. + * Attempt to scan a link destination, stopping after the destination or returning false. */ - public static int scanLinkDestination(CharSequence input, int start) { - if (start >= input.length()) { - return -1; + public static boolean scanLinkDestination(Scanner scanner) { + if (!scanner.hasNext()) { + return false; } - if (input.charAt(start) == '<') { - for (int i = start + 1; i < input.length(); i++) { - char c = input.charAt(i); - switch (c) { + if (scanner.next('<')) { + while (scanner.hasNext()) { + switch (scanner.peek()) { case '\\': - if (Parsing.isEscapable(input, i + 1)) { - i += 1; + scanner.next(); + if (Parsing.isEscapable(scanner.peek())) { + scanner.next(); } break; case '\n': case '<': - return -1; + return false; case '>': - return i + 1; + scanner.next(); + return true; + default: + scanner.next(); } } - return -1; + return false; } else { - return scanLinkDestinationWithBalancedParens(input, start); + return scanLinkDestinationWithBalancedParens(scanner); } } - public static int scanLinkTitle(CharSequence input, int start) { - if (start >= input.length()) { - return -1; + public static boolean scanLinkTitle(Scanner scanner) { + if (!scanner.hasNext()) { + return false; } char endDelimiter; - switch (input.charAt(start)) { + switch (scanner.peek()) { case '"': endDelimiter = '"'; break; @@ -74,75 +81,83 @@ public static int scanLinkTitle(CharSequence input, int start) { endDelimiter = ')'; break; default: - return -1; + return false; } + scanner.next(); - int afterContent = scanLinkTitleContent(input, start + 1, endDelimiter); - if (afterContent == -1) { - return -1; + if (!scanLinkTitleContent(scanner, endDelimiter)) { + return false; } - - if (afterContent >= input.length() || input.charAt(afterContent) != endDelimiter) { - // missing or wrong end delimiter - return -1; + if (!scanner.hasNext()) { + return false; } - - return afterContent + 1; + scanner.next(); + return true; } - public static int scanLinkTitleContent(CharSequence input, int start, char endDelimiter) { - for (int i = start; i < input.length(); i++) { - char c = input.charAt(i); - if (c == '\\' && Parsing.isEscapable(input, i + 1)) { - i += 1; + public static boolean scanLinkTitleContent(Scanner scanner, char endDelimiter) { + while (scanner.hasNext()) { + char c = scanner.peek(); + if (c == '\\') { + scanner.next(); + if (Parsing.isEscapable(scanner.peek())) { + scanner.next(); + } } else if (c == endDelimiter) { - return i; + return true; } else if (endDelimiter == ')' && c == '(') { // unescaped '(' in title within parens is invalid - return -1; + return false; + } else { + scanner.next(); } } - return input.length(); + return true; } // spec: a nonempty sequence of characters that does not start with <, does not include ASCII space or control // characters, and includes parentheses only if (a) they are backslash-escaped or (b) they are part of a balanced // pair of unescaped parentheses - private static int scanLinkDestinationWithBalancedParens(CharSequence input, int start) { + private static boolean scanLinkDestinationWithBalancedParens(Scanner scanner) { int parens = 0; - for (int i = start; i < input.length(); i++) { - char c = input.charAt(i); + boolean empty = true; + while (scanner.hasNext()) { + char c = scanner.peek(); switch (c) { - case '\0': case ' ': - return i != start ? i : -1; + return !empty; case '\\': - if (Parsing.isEscapable(input, i + 1)) { - i += 1; + scanner.next(); + if (Parsing.isEscapable(scanner.peek())) { + scanner.next(); } break; case '(': parens++; // Limit to 32 nested parens for pathological cases if (parens > 32) { - return -1; + return false; } + scanner.next(); break; case ')': if (parens == 0) { - return i; + return true; } else { parens--; } + scanner.next(); break; default: // or control character if (Character.isISOControl(c)) { - return i != start ? i : -1; + return !empty; } + scanner.next(); break; } + empty = false; } - return input.length(); + return true; } } 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 6c59b7255..10be523bb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -72,43 +72,41 @@ public static boolean isSpaceOrTab(CharSequence s, int index) { return false; } - public static boolean isEscapable(CharSequence s, int index) { - if (index < s.length()) { - switch (s.charAt(index)) { - 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; - } + 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; } diff --git a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java new file mode 100644 index 000000000..ed8e958ad --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java @@ -0,0 +1,20 @@ +package org.commonmark.internal.inline; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ScannerTest { + + @Test + public void testNext() { + Scanner scanner = new Scanner("foo bar", 4); + assertEquals('b', scanner.peek()); + scanner.next(); + assertEquals('a', scanner.peek()); + scanner.next(); + assertEquals('r', scanner.peek()); + scanner.next(); + assertEquals('\0', scanner.peek()); + } +} From 9b56ee4c723ea87879f468be8f27bade256806cd Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 28 Jul 2020 20:26:18 +1000 Subject: [PATCH 438/815] Rewrite remaining code in InlineParserImpl to use Scanner --- .../java/org/commonmark/internal/Bracket.java | 18 +- .../commonmark/internal/InlineParserImpl.java | 213 +++++++----------- .../commonmark/internal/inline/Scanner.java | 9 + 3 files changed, 100 insertions(+), 140 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index 70a8a6e25..f66a79279 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -1,5 +1,6 @@ package org.commonmark.internal; +import org.commonmark.internal.inline.Position; import org.commonmark.node.Text; /** @@ -8,7 +9,10 @@ public class Bracket { public final Text node; - public final int index; + /** + * The position of the content (after the opening bracket) + */ + public final Position contentPosition; public final boolean image; /** @@ -31,17 +35,17 @@ public class Bracket { */ public boolean bracketAfter = false; - static public Bracket link(Text node, int index, Bracket previous, Delimiter previousDelimiter) { - return new Bracket(node, index, previous, previousDelimiter, false); + static public Bracket link(Text node, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(node, contentPosition, previous, previousDelimiter, false); } - static public Bracket image(Text node, int index, Bracket previous, Delimiter previousDelimiter) { - return new Bracket(node, index, previous, previousDelimiter, true); + static public Bracket image(Text node, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(node, contentPosition, previous, previousDelimiter, true); } - private Bracket(Text node, int index, Bracket previous, Delimiter previousDelimiter, boolean image) { + private Bracket(Text node, Position contentPosition, Bracket previous, Delimiter previousDelimiter, boolean image) { this.node = node; - this.index = index; + this.contentPosition = contentPosition; this.image = image; this.previous = previous; this.previousDelimiter = previousDelimiter; diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index ae6598c91..cd04ced92 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -10,7 +10,6 @@ import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.*; -import java.util.regex.Matcher; import java.util.regex.Pattern; public class InlineParserImpl implements InlineParser, InlineParserState { @@ -19,12 +18,8 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private static final Pattern PUNCTUATION = Pattern .compile("^[" + ASCII_PUNCTUATION + "\\p{Pc}\\p{Pd}\\p{Pe}\\p{Pf}\\p{Pi}\\p{Po}\\p{Ps}]"); - private static final Pattern SPNL = Pattern.compile("^ *(?:\n *)?"); - private static final Pattern UNICODE_WHITESPACE_CHAR = Pattern.compile("^[\\p{Zs}\t\r\n\f]"); - private static final Pattern WHITESPACE = Pattern.compile("\\s+"); - private final BitSet specialCharacters; private final BitSet delimiterCharacters; private final Map<Character, DelimiterProcessor> delimiterProcessors; @@ -93,10 +88,6 @@ public Scanner scanner() { return new Scanner(input, index); } - private void setPosition(Position position) { - index = position.getIndex(); - } - private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> map) { for (DelimiterProcessor delimiterProcessor : delimiterProcessors) { char opening = delimiterProcessor.getOpeningCharacter(); @@ -152,6 +143,10 @@ public void parse(String content, Node block) { mergeChildTextNodes(block); } + void setPosition(Position position) { + index = position.getIndex(); + } + void reset(String content) { this.input = content; this.index = 0; @@ -159,22 +154,18 @@ void reset(String content) { this.lastBracket = null; } - - private Text text(String text, int beginIndex, int endIndex) { - return new Text(text.substring(beginIndex, endIndex)); - } - private Text text(String text) { return new Text(text); } /** - * Parse the next inline element in subject, advancing input index. + * Parse the next inline element in subject, advancing our position. * On success, return the new inline node. * On failure, return null. */ private Node parseInline(Node previous) { - char c = peek(); + Scanner scanner = scanner(); + char c = scanner.peek(); if (c == '\0') { return null; } @@ -216,7 +207,8 @@ private Node parseInline(Node previous) { if (node != null) { return node; } else { - index++; + scanner.next(); + setPosition(scanner.position()); // When we get here, it's only for a single special character that turned out to not have a special meaning. // So we shouldn't have a single surrogate here, hence it should be ok to turn it into a String. String literal = String.valueOf(c); @@ -224,46 +216,6 @@ private Node parseInline(Node previous) { } } - /** - * If RE matches at current index in the input, advance index and return the match; otherwise return null. - */ - private String match(Pattern re) { - if (index >= input.length()) { - return null; - } - try { - Matcher matcher = re.matcher(input); - matcher.region(index, input.length()); - boolean m = matcher.find(); - if (m) { - index = matcher.end(); - return matcher.group(); - } else { - return null; - } - } catch (StackOverflowError e) { - return null; - } - } - - /** - * Returns the char at the current input index, or {@code '\0'} in case there are no more characters. - */ - private char peek() { - if (index < input.length()) { - return input.charAt(index); - } else { - return '\0'; - } - } - - /** - * Parse zero or more space characters, including at most one newline. - */ - private void spnl() { - match(SPNL); - } - /** * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ @@ -272,16 +224,13 @@ private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimit if (res == null) { return null; } - int length = res.count; - int startIndex = index; - index += length; - Text node = text(input, startIndex, index); + Text node = res.text; // Add entry to stack for this opener lastDelimiter = new Delimiter(node, delimiterChar, res.canOpen, res.canClose, lastDelimiter); - lastDelimiter.length = length; - lastDelimiter.originalLength = length; + lastDelimiter.length = res.count; + lastDelimiter.originalLength = res.count; if (lastDelimiter.previous != null) { lastDelimiter.previous.next = lastDelimiter; } @@ -293,13 +242,15 @@ private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimit * Add open bracket to delimiter stack and add a text node to block's children. */ private Node parseOpenBracket() { - int startIndex = index; - index++; + Scanner scanner = scanner(); + scanner.next(); + Position start = scanner.position(); + setPosition(start); Text node = text("["); // Add entry to stack for this opener - addBracket(Bracket.link(node, startIndex, lastBracket, lastDelimiter)); + addBracket(Bracket.link(node, start, lastBracket, lastDelimiter)); return node; } @@ -309,18 +260,18 @@ private Node parseOpenBracket() { * Otherwise just add a text node. */ private Node parseBang() { - int startIndex = index; - index++; - if (peek() == '[') { - index++; - + Scanner scanner = scanner(); + scanner.next(); + if (scanner.next('[')) { Text node = text("!["); // Add entry to stack for this opener - addBracket(Bracket.image(node, startIndex + 1, lastBracket, lastDelimiter)); + addBracket(Bracket.image(node, scanner.position(), lastBracket, lastDelimiter)); + setPosition(scanner.position()); return node; } else { + setPosition(scanner.position()); return text("!"); } } @@ -330,8 +281,10 @@ private Node parseBang() { * plain [ character. If there is a matching delimiter, remove it from the delimiter stack. */ private Node parseCloseBracket() { - index++; - int startIndex = index; + Scanner scanner = scanner(); + Position beforeClose = scanner.position(); + scanner.next(); + setPosition(scanner.position()); // Get previous `[` or `![` Bracket opener = lastBracket; @@ -347,43 +300,41 @@ private Node parseCloseBracket() { } // Check to see if we have a link/image - String dest = null; String title = null; - boolean isLinkOrImage = false; // Maybe a inline link like `[foo](/uri "title")` - if (peek() == '(') { - index++; - spnl(); - if ((dest = parseLinkDestination()) != null) { - spnl(); + if (scanner.next('(')) { + scanner.whitespace(); + dest = parseLinkDestination(scanner); + if (dest != null) { + int whitespace = scanner.whitespace(); // title needs a whitespace before - if (WHITESPACE.matcher(input.substring(index - 1, index)).matches()) { - title = parseLinkTitle(); - spnl(); + if (whitespace >= 1) { + title = parseLinkTitle(scanner); + scanner.whitespace(); } - if (peek() == ')') { - index++; - isLinkOrImage = true; - } else { - index = startIndex; + 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 = scanner(); + dest = null; + title = null; } } } - // Maybe a reference link like `[foo][bar]`, `[foo][]` or `[foo]` - if (!isLinkOrImage) { - + // 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(); + 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 = input.substring(opener.index, startIndex); - // Strip '[' and ']' - ref = ref.substring(1, ref.length() - 1); + ref = scanner.textBetween(opener.contentPosition, beforeClose).toString(); } if (ref != null) { @@ -392,12 +343,11 @@ private Node parseCloseBracket() { if (definition != null) { dest = definition.getDestination(); title = definition.getTitle(); - isLinkOrImage = true; } } } - if (isLinkOrImage) { + if (dest != null) { // If we got here, open is a potential opener Node linkOrImage = opener.image ? new Image(dest, title) : new Link(dest, title); @@ -427,10 +377,12 @@ private Node parseCloseBracket() { } } + setPosition(scanner.position()); + return linkOrImage; - } else { // no link or image - index = startIndex; + } else { + // No link or image, parse just the bracket as text and continue removeLastBracket(); return text("]"); @@ -451,8 +403,7 @@ private void removeLastBracket() { /** * Attempt to parse link destination, returning the string or null if no match. */ - private String parseLinkDestination() { - Scanner scanner = scanner(); + private String parseLinkDestination(Scanner scanner) { char delimiter = scanner.peek(); Position start = scanner.position(); if (!LinkScanner.scanLinkDestination(scanner)) { @@ -468,15 +419,13 @@ private String parseLinkDestination() { dest = scanner.textBetween(start, scanner.position()).toString(); } - setPosition(scanner.position()); return Escaping.unescapeString(dest); } /** * Attempt to parse link title (sans quotes), returning the string or null if no match. */ - private String parseLinkTitle() { - Scanner scanner = scanner(); + private String parseLinkTitle(Scanner scanner) { Position start = scanner.position(); if (!LinkScanner.scanLinkTitle(scanner)) { return null; @@ -485,15 +434,13 @@ private String parseLinkTitle() { // chop off ', " or parens CharSequence rawTitle = scanner.textBetween(start, scanner.position()); String title = rawTitle.subSequence(1, rawTitle.length() - 1).toString(); - setPosition(scanner.position()); return Escaping.unescapeString(title); } /** * Attempt to parse a link label, returning the label between the brackets or null. */ - String parseLinkLabel() { - Scanner scanner = scanner(); + String parseLinkLabel(Scanner scanner) { if (!scanner.next('[')) { return null; } @@ -514,24 +461,26 @@ String parseLinkLabel() { return null; } - setPosition(scanner.position()); return content; } /** - * Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string. + * Parse a run of non-special characters as plain text. */ private Node parseString() { - int begin = index; - int length = input.length(); - while (index != length) { - if (specialCharacters.get(input.charAt(index))) { + Scanner scanner = scanner(); + Position start = scanner.position(); + while (scanner.hasNext()) { + if (specialCharacters.get(scanner.peek())) { break; } - index++; + scanner.next(); } - if (begin != index) { - return text(input, begin, index); + + String text = scanner.textBetween(start, scanner.position()).toString(); + if (!text.isEmpty()) { + setPosition(scanner.position()); + return text(text); } else { return null; } @@ -544,25 +493,20 @@ private Node parseString() { * @return information about delimiter run, or {@code null} */ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { - int startIndex = index; + Scanner scanner = scanner(); + char charBefore = scanner.peekPrevious(); + Position start = scanner.position(); - int delimiterCount = 0; - while (peek() == delimiterChar) { - delimiterCount++; - index++; - } + int delimiterCount = scanner.matchMultiple(delimiterChar); if (delimiterCount < delimiterProcessor.getMinLength()) { - index = startIndex; + setPosition(start); return null; } - String before = startIndex == 0 ? "\n" : - input.substring(startIndex - 1, startIndex); - - char charAfter = peek(); - String after = charAfter == '\0' ? "\n" : - String.valueOf(charAfter); + char charAfter = scanner.peek(); + String before = charBefore == '\0' ? "\n" : String.valueOf(charBefore); + String after = charAfter == '\0' ? "\n" : String.valueOf(charAfter); // We could be more lazy here, in most cases we don't need to do every match case. boolean beforeIsPunctuation = PUNCTUATION.matcher(before).matches(); @@ -584,8 +528,9 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingCharacter(); } - index = startIndex; - return new DelimiterData(delimiterCount, canOpen, canClose); + setPosition(scanner.position()); + String text = scanner.textBetween(start, scanner.position()).toString(); + return new DelimiterData(delimiterCount, canOpen, canClose, new Text(text)); } private void processDelimiters(Delimiter stackBottom) { @@ -789,11 +734,13 @@ private static class DelimiterData { final int count; final boolean canClose; final boolean canOpen; + final Text text; - DelimiterData(int count, boolean canOpen, boolean canClose) { + DelimiterData(int count, boolean canOpen, boolean canClose, Text text) { this.count = count; this.canOpen = canOpen; this.canClose = canClose; + this.text = text; } } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index cb87248d1..3db930e0b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -22,6 +22,15 @@ public char peek() { } } + public char peekPrevious() { + int prev = index - 1; + if (prev >= 0 && prev < input.length()) { + return input.charAt(prev); + } else { + return '\0'; + } + } + public boolean hasNext() { return index < input.length(); } From cc85e20569c931cc72bf0bd8fd5660cdcf69b312 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 30 Jul 2020 20:41:48 +1000 Subject: [PATCH 439/815] Use a list of lines instead of a single string for inline parsing This is a big one, and was only possible because of the new Scanner infrastructure. This has a couple of advantages: * Less string copying and garbage because we no longer need to have all of a block's content in a single contiguous string, we can keep the lines we use for block parsing directly * Because we have individual lines, we could also keep source positions and get source positions for inline nodes :wow:! --- .../gfm/tables/internal/TableBlockParser.java | 20 +-- .../commonmark/internal/DocumentParser.java | 19 ++- .../commonmark/internal/HeadingParser.java | 40 +++--- .../commonmark/internal/InlineParserImpl.java | 127 +++++++++++------- .../LinkReferenceDefinitionParser.java | 29 ++-- .../commonmark/internal/ListBlockParser.java | 2 +- .../commonmark/internal/ParagraphParser.java | 12 +- .../internal/inline/AutolinkInlineParser.java | 2 +- .../inline/BackslashInlineParser.java | 2 +- .../inline/BackticksInlineParser.java | 2 +- .../internal/inline/EntityInlineParser.java | 2 +- .../internal/inline/HtmlInlineParser.java | 3 +- .../internal/inline/InlineContentParser.java | 4 +- .../inline/LineBreakInlineContentParser.java | 39 ------ .../commonmark/internal/inline/Position.java | 9 +- .../commonmark/internal/inline/Scanner.java | 108 ++++++++++++--- .../org/commonmark/internal/util/Parsing.java | 22 ++- .../org/commonmark/parser/InlineParser.java | 7 +- .../parser/block/MatchedBlockParser.java | 9 +- .../LinkReferenceDefinitionParserTest.java | 34 +++-- .../internal/inline/ScannerTest.java | 68 +++++++++- .../java/org/commonmark/test/ParserTest.java | 2 +- .../org/commonmark/test/SpecialInputTest.java | 7 + 23 files changed, 358 insertions(+), 211 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java 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 2952a8785..107aa2167 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,20 +1,11 @@ package org.commonmark.ext.gfm.tables.internal; -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.Block; import org.commonmark.node.Node; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; -import org.commonmark.parser.block.AbstractBlockParser; -import org.commonmark.parser.block.AbstractBlockParserFactory; -import org.commonmark.parser.block.BlockContinue; -import org.commonmark.parser.block.BlockStart; -import org.commonmark.parser.block.MatchedBlockParser; -import org.commonmark.parser.block.ParserState; +import org.commonmark.parser.block.*; import java.util.ArrayList; import java.util.Collections; @@ -116,7 +107,7 @@ private TableCell parseCell(CellSource cell, int column, InlineParser inlinePars tableCell.setSourceSpans(Collections.singletonList(cell.sourceSpan)); } - inlineParser.parse(cell.content, tableCell); + inlineParser.parse(Collections.<CharSequence>singletonList(cell.content), tableCell); return tableCell; } @@ -246,11 +237,12 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { CharSequence line = state.getLine(); - CharSequence paragraph = matchedBlockParser.getParagraphContent(); - if (paragraph != null && paragraph.toString().contains("|") && !paragraph.toString().contains("\n")) { + List<CharSequence> paragraphLines = matchedBlockParser.getParagraphLines(); + if (paragraphLines.size() == 1 && paragraphLines.get(0).toString().contains("|")) { CharSequence separatorLine = line.subSequence(state.getIndex(), line.length()); List<TableCell.Alignment> columns = parseSeparator(separatorLine); if (columns != null && !columns.isEmpty()) { + CharSequence paragraph = paragraphLines.get(0); List<CellSource> headerCells = split(paragraph, null); if (columns.size() >= headerCells.size()) { return BlockStart.of(new TableBlockParser(columns, paragraph)) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 51791d90b..5c3cdfd3d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -425,7 +425,10 @@ private void addLine() { } sb.append(rest); content = sb.toString(); + } else if (index == 0) { + content = line; } else { + // TODO: Maybe we should bring back Subsequence here? content = line.subSequence(index, line.length()); } getActiveBlockParser().addLine(content); @@ -458,9 +461,8 @@ private BlockStartImpl findBlockStart(BlockParser blockParser) { } /** - * Finalize a block. Close it and do any necessary postprocessing, e.g. creating string_content from strings, - * setting the 'tight' or 'loose' status of a list, and parsing the beginnings of paragraphs for reference - * definitions. + * 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) { @@ -567,17 +569,12 @@ public BlockParser getMatchedBlockParser() { } @Override - public CharSequence getParagraphContent() { + public List<CharSequence> getParagraphLines() { if (matchedBlockParser instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) matchedBlockParser; - CharSequence content = paragraphParser.getContentString(); - if (content.length() == 0) { - return null; - } - - return content; + return Collections.unmodifiableList(paragraphParser.getParagraphLines()); } - return null; + return Collections.emptyList(); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 2b72ba236..88bf002f0 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -6,12 +6,15 @@ import org.commonmark.parser.InlineParser; import org.commonmark.parser.block.*; +import java.util.Collections; +import java.util.List; + public class HeadingParser extends AbstractBlockParser { private final Heading block = new Heading(); - private final String content; + private final List<CharSequence> content; - public HeadingParser(int level, String content) { + public HeadingParser(int level, List<CharSequence> content) { block.setLevel(level); this.content = content; } @@ -49,10 +52,9 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar int setextHeadingLevel = getSetextHeadingLevel(line, nextNonSpace); if (setextHeadingLevel > 0) { - CharSequence paragraph = matchedBlockParser.getParagraphContent(); - if (paragraph != null) { - String content = paragraph.toString(); - return BlockStart.of(new HeadingParser(setextHeadingLevel, content)) + List<CharSequence> paragraph = matchedBlockParser.getParagraphLines(); + if (!paragraph.isEmpty()) { + return BlockStart.of(new HeadingParser(setextHeadingLevel, paragraph)) .atIndex(line.length()) .replaceActiveBlockParser(); } @@ -73,25 +75,29 @@ private static HeadingParser getAtxHeading(CharSequence line, int index) { return null; } - int start = index + level; - if (start >= line.length()) { + int afterMarker = index + level; + if (afterMarker >= line.length()) { // End of line after markers is an empty heading - return new HeadingParser(level, ""); + return new HeadingParser(level, Collections.<CharSequence>emptyList()); } - char next = line.charAt(start); + char next = line.charAt(afterMarker); if (!(next == ' ' || next == '\t')) { return null; } - int beforeSpace = Parsing.skipSpaceTabBackwards(line, line.length() - 1, start); - int beforeHash = Parsing.skipBackwards('#', line, beforeSpace, start); - int beforeTrailer = Parsing.skipSpaceTabBackwards(line, beforeHash, start); - if (beforeTrailer != beforeHash) { - return new HeadingParser(level, line.subSequence(start, beforeTrailer + 1).toString()); - } else { - return new HeadingParser(level, line.subSequence(start, beforeSpace + 1).toString()); + int start = Parsing.skipSpaceTab(line, afterMarker, line.length()); + + int beforeSpace = Parsing.skipSpaceTabBackwards(line, line.length() - 1, afterMarker); + int beforeHash = Parsing.skipBackwards('#', line, beforeSpace, afterMarker); + int beforeTrailer = Parsing.skipSpaceTabBackwards(line, beforeHash, afterMarker); + // Trailing `#` need to be separated with at least one space/tab, otherwise they are part of the content. + int end = (beforeTrailer < beforeHash) ? beforeTrailer + 1 : beforeSpace + 1; + if (start >= end) { + // Empty, e.g. `### ###` + return new HeadingParser(level, Collections.<CharSequence>emptyList()); } + return new HeadingParser(level, Collections.singletonList(line.subSequence(start, end))); } // spec: A setext heading underline is a sequence of = characters or a sequence of - characters, with no more than diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index cd04ced92..eee25af40 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -4,6 +4,7 @@ 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; @@ -26,8 +27,11 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private final InlineParserContext context; private final Map<Character, List<InlineContentParser>> inlineParsers; - private String input; + // TODO: Should we just keep a scanner here instead? + private List<CharSequence> lines; + private int lineIndex; private int index; + private int trailingSpaces; /** * Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different @@ -45,7 +49,6 @@ public InlineParserImpl(InlineParserContext inlineParserContext) { this.context = inlineParserContext; this.inlineParsers = new HashMap<>(); - this.inlineParsers.put('\n', Collections.<InlineContentParser>singletonList(new LineBreakInlineContentParser())); this.inlineParsers.put('\\', Collections.<InlineContentParser>singletonList(new BackslashInlineParser())); this.inlineParsers.put('`', Collections.<InlineContentParser>singletonList(new BackticksInlineParser())); this.inlineParsers.put('&', Collections.<InlineContentParser>singletonList(new EntityInlineParser())); @@ -72,6 +75,7 @@ public static BitSet calculateSpecialCharacters(BitSet delimiterCharacters, Set< bitSet.set('['); bitSet.set(']'); bitSet.set('!'); + bitSet.set('\n'); return bitSet; } @@ -85,7 +89,7 @@ public static Map<Character, DelimiterProcessor> calculateDelimiterProcessors(Li // TODO: The implementation shouldn't be public @Override public Scanner scanner() { - return new Scanner(input, index); + return new Scanner(lines, lineIndex, index); } private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> map) { @@ -122,16 +126,14 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr } /** - * Parse content in block into inline children, using reference map to resolve references. + * Parse content in block into inline children, appending them to the block node. */ @Override - public void parse(String content, Node block) { - reset(content.trim()); + public void parse(List<CharSequence> lines, Node block) { + reset(lines); - Node previous = null; while (true) { - Node node = parseInline(previous); - previous = node; + Node node = parseInline(); if (node != null) { block.appendChild(node); } else { @@ -144,12 +146,15 @@ public void parse(String content, Node block) { } void setPosition(Position position) { + lineIndex = position.getLineIndex(); index = position.getIndex(); } - void reset(String content) { - this.input = content; + void reset(List<CharSequence> lines) { + this.lines = lines; + this.lineIndex = 0; this.index = 0; + this.trailingSpaces = 0; this.lastDelimiter = null; this.lastBracket = null; } @@ -163,7 +168,7 @@ private Text text(String text) { * On success, return the new inline node. * On failure, return null. */ - private Node parseInline(Node previous) { + private Node parseInline() { Scanner scanner = scanner(); char c = scanner.peek(); if (c == '\0') { @@ -173,8 +178,7 @@ private Node parseInline(Node previous) { List<InlineContentParser> inlineParsers = this.inlineParsers.get(c); if (inlineParsers != null) { for (InlineContentParser inlineParser : inlineParsers) { - // TODO: Should we pass the whole previous node or can we make the API surface smaller? - ParsedInline parsedInline = inlineParser.tryParse(this, previous); + ParsedInline parsedInline = inlineParser.tryParse(this); if (parsedInline instanceof ParsedInlineImpl) { ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; setPosition(parsedInlineImpl.getPosition()); @@ -183,37 +187,44 @@ private Node parseInline(Node previous) { } } - Node node; switch (c) { case '[': - node = parseOpenBracket(); - break; + return parseOpenBracket(); case '!': - node = parseBang(); - break; + return parseBang(); case ']': - node = parseCloseBracket(); - break; - default: - boolean isDelimiter = delimiterCharacters.get(c); - if (isDelimiter) { - DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c); - node = parseDelimiters(delimiterProcessor, c); - } else { - node = parseString(); - } - break; + return parseCloseBracket(); + case '\n': + return parseLineBreak(); } - if (node != null) { - return node; - } else { - scanner.next(); - setPosition(scanner.position()); - // When we get here, it's only for a single special character that turned out to not have a special meaning. - // So we shouldn't have a single surrogate here, hence it should be ok to turn it into a String. - String literal = String.valueOf(c); - return text(literal); + + boolean isDelimiter = delimiterCharacters.get(c); + if (isDelimiter) { + DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c); + Node delimiterNode = parseDelimiters(delimiterProcessor, c); + if (delimiterNode != null) { + return delimiterNode; + } } + + // If we get here, even for a special/delimiter character, we will just treat it as text. + return parseText(); +// } else { +// node = parseString(); +// } +// break; +// +// Node node; +// if (node != null) { +// return node; +// } else { +// scanner.next(); +// setPosition(scanner.position()); +// // When we get here, it's only for a single special character that turned out to not have a special meaning. +// // So we shouldn't have a single surrogate here, hence it should be ok to turn it into a String. +// String literal = String.valueOf(c); +// return text(literal); +// } } /** @@ -464,12 +475,25 @@ String parseLinkLabel(Scanner scanner) { return content; } + private Node parseLineBreak() { + Scanner scanner = scanner(); + scanner.next(); + setPosition(scanner.position()); + + if (trailingSpaces >= 2) { + return new HardLineBreak(); + } else { + return new SoftLineBreak(); + } + } + /** - * Parse a run of non-special characters as plain text. + * Parse the next character as plain text, and possibly more if the following characters are non-special. */ - private Node parseString() { + private Node parseText() { Scanner scanner = scanner(); Position start = scanner.position(); + scanner.next(); while (scanner.hasNext()) { if (specialCharacters.get(scanner.peek())) { break; @@ -478,12 +502,21 @@ private Node parseString() { } String text = scanner.textBetween(start, scanner.position()).toString(); - if (!text.isEmpty()) { - setPosition(scanner.position()); - return text(text); - } else { - return null; - } + setPosition(scanner.position()); + + char c = scanner.peek(); + 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(' ', text, text.length() - 1, 0) + 1; + trailingSpaces = text.length() - end; + text = text.substring(0, end); + } else if (c == '\0') { + // For the last line, both tabs and spaces are trimmed for some reason (checked with commonmark.js). + int end = Parsing.skipSpaceTabBackwards(text, text.length() - 1, 0) + 1; + text = text.substring(0, end); + } + + return text(text); } /** diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index a6cd57228..7f257343b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -8,6 +8,7 @@ import org.commonmark.node.SourceSpan; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -19,7 +20,7 @@ public class LinkReferenceDefinitionParser { private State state = State.START_DEFINITION; - private final StringBuilder paragraph = new StringBuilder(); + private final List<CharSequence> paragraphLines = new ArrayList<>(); private final List<LinkReferenceDefinition> definitions = new ArrayList<>(); private final List<SourceSpan> sourceSpans = new ArrayList<>(); @@ -31,20 +32,17 @@ public class LinkReferenceDefinitionParser { private boolean referenceValid = false; public void parse(CharSequence line) { - if (paragraph.length() != 0) { - paragraph.append('\n'); + paragraphLines.add(line); + if (state == State.PARAGRAPH) { + // We're in a paragraph now. Link reference definitions can only appear at the beginning, so once + // we're in a paragraph, there's no going back. + return; } - paragraph.append(line); - Scanner scanner = new Scanner(line, 0); + Scanner scanner = new Scanner(Collections.singletonList(line), 0, 0); while (scanner.hasNext()) { boolean success; switch (state) { - case PARAGRAPH: { - // We're in a paragraph now. Link reference definitions can only appear at the beginning, so once - // we're in a paragraph, there's no going back. - return; - } case START_DEFINITION: { success = startDefinition(scanner); break; @@ -81,8 +79,11 @@ public void addSourceSpan(SourceSpan sourceSpan) { sourceSpans.add(sourceSpan); } - CharSequence getParagraphContent() { - return paragraph; + /** + * @return the lines that are normal paragraph content, without newlines + */ + List<CharSequence> getParagraphLines() { + return paragraphLines; } List<SourceSpan> getParagraphSourceSpans() { @@ -168,7 +169,7 @@ private boolean destination(Scanner scanner) { // Destination was at end of line, so this is a valid reference for sure (and maybe a title). // If not at end of line, wait for title to be valid first. referenceValid = true; - paragraph.setLength(0); + paragraphLines.clear(); } else if (whitespace == 0) { // spec: The title must be separated from the link destination by whitespace return false; @@ -236,7 +237,7 @@ private boolean title(Scanner scanner) { } referenceValid = true; finishReference(); - paragraph.setLength(0); + paragraphLines.clear(); // See if there's another definition. state = State.START_DEFINITION; diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index de1558f92..1538ca41b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -210,7 +210,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } int markerIndex = state.getNextNonSpaceIndex(); int markerColumn = state.getColumn() + state.getIndent(); - boolean inParagraph = matchedBlockParser.getParagraphContent() != null; + boolean inParagraph = !matchedBlockParser.getParagraphLines().isEmpty(); ListData listData = parseList(state.getLine(), markerIndex, markerColumn, inParagraph); if (listData == null) { return BlockStart.none(); diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index 7e28cf0fb..8962ec7da 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -49,7 +49,7 @@ public void addSourceSpan(SourceSpan sourceSpan) { @Override public void closeBlock() { - if (linkReferenceDefinitionParser.getParagraphContent().length() == 0) { + if (linkReferenceDefinitionParser.getParagraphLines().isEmpty()) { block.unlink(); } else { block.setSourceSpans(linkReferenceDefinitionParser.getParagraphSourceSpans()); @@ -58,14 +58,14 @@ public void closeBlock() { @Override public void parseInlines(InlineParser inlineParser) { - CharSequence content = linkReferenceDefinitionParser.getParagraphContent(); - if (content.length() > 0) { - inlineParser.parse(content.toString(), block); + List<CharSequence> lines = linkReferenceDefinitionParser.getParagraphLines(); + if (!lines.isEmpty()) { + inlineParser.parse(lines, block); } } - public CharSequence getContentString() { - return linkReferenceDefinitionParser.getParagraphContent(); + public List<CharSequence> getParagraphLines() { + return linkReferenceDefinitionParser.getParagraphLines(); } public List<LinkReferenceDefinition> getDefinitions() { 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 acf55a796..7b62d1257 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -18,7 +18,7 @@ public class AutolinkInlineParser implements InlineContentParser { .compile("^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$"); @Override - public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); scanner.next(); Position start = scanner.position(); 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 cd87f7399..f57a67a74 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackslashInlineParser.java @@ -16,7 +16,7 @@ public class BackslashInlineParser implements InlineContentParser { private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE); @Override - public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); // Backslash scanner.next(); 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 9979bde86..f00079793 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -11,7 +11,7 @@ public class BackticksInlineParser implements InlineContentParser { @Override - public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); Position start = scanner.position(); int openingTicks = scanner.matchMultiple('`'); 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 d44ee4217..f0c330002 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -16,7 +16,7 @@ public class EntityInlineParser implements InlineContentParser { private static final AsciiMatcher entityContinue = entityStart.newBuilder().range('0', '9').build(); @Override - public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); Position start = scanner.position(); // Skip `&` 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 4c25c3e58..6abe12487 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -2,7 +2,6 @@ import org.commonmark.internal.util.AsciiMatcher; import org.commonmark.node.HtmlInline; -import org.commonmark.node.Node; /** * Attempt to parse inline HTML. @@ -28,7 +27,7 @@ public class HtmlInlineParser implements InlineContentParser { private static final AsciiMatcher declaration = AsciiMatcher.builder().range('A', 'Z').build(); @Override - public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { + public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); Position start = scanner.position(); // Skip over `<` 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 76259c444..dc8c43640 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java @@ -1,10 +1,8 @@ package org.commonmark.internal.inline; -import org.commonmark.node.Node; - // TODO: I'd prefer if this was named InlineParser, but that's already public API, hmm... public interface InlineContentParser { - ParsedInline tryParse(InlineParserState inlineParserState, Node previous); + ParsedInline tryParse(InlineParserState inlineParserState); } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java deleted file mode 100644 index 1b85c6eec..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/inline/LineBreakInlineContentParser.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.commonmark.internal.inline; - -import org.commonmark.internal.util.Parsing; -import org.commonmark.node.HardLineBreak; -import org.commonmark.node.Node; -import org.commonmark.node.SoftLineBreak; -import org.commonmark.node.Text; - -/** - * Parse a newline. If it was preceded by two spaces, return a hard line break; otherwise a soft line break. - */ -public class LineBreakInlineContentParser implements InlineContentParser { - - @Override - public ParsedInline tryParse(InlineParserState inlineParserState, Node previous) { - Scanner scanner = inlineParserState.scanner(); - scanner.next(); - - // Check previous text for trailing spaces. - // The "endsWith" is an optimization to avoid an RE match in the common case. - if (previous instanceof Text && ((Text) previous).getLiteral().endsWith(" ")) { - Text text = (Text) previous; - String literal = text.getLiteral(); - int last = literal.length() - 1; - int nonSpace = Parsing.skipBackwards(' ', literal, last, 0); - int spaces = last - nonSpace; - if (spaces > 0) { - text.setLiteral(literal.substring(0, literal.length() - spaces)); - } - if (spaces >= 2) { - return ParsedInline.of(new HardLineBreak(), scanner.position()); - } else { - return ParsedInline.of(new SoftLineBreak(), scanner.position()); - } - } else { - return ParsedInline.of(new SoftLineBreak(), scanner.position()); - } - } -} diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Position.java b/commonmark/src/main/java/org/commonmark/internal/inline/Position.java index dff5e36df..4a54cbce6 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Position.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Position.java @@ -1,12 +1,19 @@ package org.commonmark.internal.inline; public class Position { + final int lineIndex; final int index; - Position(int index) { + Position(int lineIndex, int index) { + this.lineIndex = lineIndex; this.index = index; } + // TODO: Move packages around so that this can stay package-private + public int getLineIndex() { + return lineIndex; + } + // TODO: Move packages around so that this can stay package-private public int getIndex() { return index; diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 3db930e0b..b144ba52a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -1,42 +1,83 @@ package org.commonmark.internal.inline; import org.commonmark.internal.util.CharMatcher; -import org.commonmark.internal.util.Parsing; + +import java.util.List; public class Scanner { - private final CharSequence input; + // Lines without newlines at the end. The scanner will yield `\n` between lines because they're significant for + // parsing and the final output. There is no `\n` after the last line. + private final List<CharSequence> lines; + // Which line we're at. + private int lineIndex; + // The index within the line. If index == length(), we pretend that there's a `\n` and only advance after we yield + // that. private int index; + // Current line or "" if at the end of the lines (using "" instead of null saves a null check) + private CharSequence line = ""; + private int lineLength = 0; + // TODO: Visibility - public Scanner(CharSequence input, int index) { - this.input = input; + public Scanner(List<CharSequence> lines, int lineIndex, int index) { + this.lines = lines; + this.lineIndex = lineIndex; this.index = index; + if (!lines.isEmpty()) { + line = lines.get(lineIndex); + lineLength = line.length(); + } } public char peek() { - if (index >= input.length()) { - return '\0'; + if (index < lineLength) { + return line.charAt(index); } else { - return input.charAt(index); + if (lineIndex < lines.size() - 1) { + return '\n'; + } else { + // Don't return newline for end of last line + return '\0'; + } } } public char peekPrevious() { - int prev = index - 1; - if (prev >= 0 && prev < input.length()) { - return input.charAt(prev); + if (index > 0) { + int prev = index - 1; + return line.charAt(prev); } else { - return '\0'; + if (lineIndex > 0) { + return '\n'; + } else { + return '\0'; + } } } public boolean hasNext() { - return index < input.length(); + if (index < lineLength) { + return true; + } else { + // No newline at end of last line + return lineIndex < lines.size() - 1; + } } public void next() { index++; + if (index > lineLength) { + lineIndex++; + if (lineIndex < lines.size()) { + line = lines.get(lineIndex); + lineLength = line.length(); + } else { + line = ""; + lineLength = 0; + } + index = 0; + } } public boolean next(char c) { @@ -67,10 +108,22 @@ public int match(CharMatcher matcher) { } public int whitespace() { - int newIndex = Parsing.skipWhitespace(input, index, input.length()); - int count = newIndex - index; - index = newIndex; - return count; + int count = 0; + while (true) { + switch (peek()) { + case ' ': + case '\t': + case '\n': + case '\u000B': + case '\f': + case '\r': + count++; + next(); + break; + default: + return count; + } + } } public int find(char c) { @@ -104,12 +157,31 @@ public int find(CharMatcher matcher) { // Don't expose the int index, because it would be good if we could switch input to a List<String> of lines later // instead of one contiguous String. public Position position() { - return new Position(index); + return new Position(lineIndex, index); } // For cases where the caller appends the result to a StringBuilder, we could offer another method to avoid some // unnecessary copying. public CharSequence textBetween(Position begin, Position end) { - return input.subSequence(begin.index, end.index); + if (begin.lineIndex == end.lineIndex) { + // Shortcut for common case of text from a single line + return lines.get(begin.lineIndex).subSequence(begin.index, end.index); + } else { + StringBuilder sb = new StringBuilder(); + + CharSequence firstLine = lines.get(begin.lineIndex); + sb.append(firstLine.subSequence(begin.index, firstLine.length())); + sb.append('\n'); + + // Lines between begin and end (we are appending the full line) + for (int line = begin.lineIndex + 1; line < end.lineIndex; line++) { + sb.append(lines.get(line)); + sb.append('\n'); + } + + CharSequence lastLine = lines.get(end.lineIndex); + sb.append(lastLine.subSequence(0, end.index)); + return sb.toString(); + } } } 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 10be523bb..eb7dddb0d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -120,18 +120,16 @@ public static CharSequence prepareLine(CharSequence line) { int length = line.length(); for (int i = 0; i < length; i++) { char c = line.charAt(i); - switch (c) { - case '\0': - if (sb == null) { - sb = new StringBuilder(length); - sb.append(line, 0, i); - } - sb.append('\uFFFD'); - break; - default: - if (sb != null) { - sb.append(c); - } + 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); + } } } diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java index 492c3cc8a..291fd4900 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java @@ -2,14 +2,17 @@ import org.commonmark.node.Node; +import java.util.List; + /** * Parser for inline content (text, links, emphasized text, etc). */ public interface InlineParser { /** - * @param input the content to parse as inline + * @param lines the content to parse as inline * @param node the node to append resulting nodes to (as children) */ - void parse(String input, Node node); + // TODO: Should we use a better type here, one that will be able to include source positions? + void parse(List<CharSequence> lines, Node node); } 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 d4cd9d471..41ba2712b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java @@ -1,5 +1,7 @@ package org.commonmark.parser.block; +import java.util.List; + /** * Open block parser that was last matched during the continue phase. This is different from the currently active * block parser, as an unmatched block is only closed when a new block is started. @@ -10,11 +12,10 @@ public interface MatchedBlockParser { BlockParser getMatchedBlockParser(); /** - * Returns the current content of the paragraph if the matched block is a paragraph. The content can be multiple - * lines separated by {@code '\n'}. + * Returns the current paragraph lines if the matched block is a paragraph. * - * @return paragraph content or {@code null} + * @return paragraph content or an empty list */ - CharSequence getParagraphContent(); + List<CharSequence> getParagraphLines(); } diff --git a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java index f0bdef492..3b9ae83dc 100644 --- a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java @@ -12,9 +12,7 @@ public class LinkReferenceDefinitionParserTest { @Test public void testStartLabel() { - parser.parse("["); - assertEquals(State.LABEL, parser.getState()); - assertEquals("[", parser.getParagraphContent().toString()); + assertState("[", State.LABEL, "["); } @Test @@ -25,7 +23,7 @@ public void testStartNoLabel() { parser.parse("a"); parser.parse("["); assertEquals(State.PARAGRAPH, parser.getState()); - assertEquals("a\n[", parser.getParagraphContent().toString()); + assertParagraphLines("a\n[", parser); } @Test @@ -80,7 +78,7 @@ public void testDestination() { LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); parser.parse("[foo]: /url"); assertEquals(State.START_TITLE, parser.getState()); - assertEquals("", parser.getParagraphContent().toString()); + assertParagraphLines("", parser); assertEquals(1, parser.getDefinitions().size()); assertDef(parser.getDefinitions().get(0), "foo", "/url", null); @@ -99,7 +97,7 @@ public void testTitle() { LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); parser.parse("[foo]: /url 'title'"); assertEquals(State.START_DEFINITION, parser.getState()); - assertEquals("", parser.getParagraphContent().toString()); + assertParagraphLines("", parser); assertEquals(1, parser.getDefinitions().size()); assertDef(parser.getDefinitions().get(0), "foo", "/url", "title"); @@ -110,12 +108,12 @@ public void testTitleStartWhitespace() { LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); parser.parse("[foo]: /url"); assertEquals(State.START_TITLE, parser.getState()); - assertEquals("", parser.getParagraphContent().toString()); + assertParagraphLines("", parser); parser.parse(" "); assertEquals(State.START_DEFINITION, parser.getState()); - assertEquals(" ", parser.getParagraphContent().toString()); + assertParagraphLines(" ", parser); assertEquals(1, parser.getDefinitions().size()); assertDef(parser.getDefinitions().get(0), "foo", "/url", null); @@ -126,17 +124,17 @@ public void testTitleMultiline() { LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); parser.parse("[foo]: /url 'two"); assertEquals(State.TITLE, parser.getState()); - assertEquals("[foo]: /url 'two", parser.getParagraphContent().toString()); + assertParagraphLines("[foo]: /url 'two", parser); assertEquals(0, parser.getDefinitions().size()); parser.parse("lines"); assertEquals(State.TITLE, parser.getState()); - assertEquals("[foo]: /url 'two\nlines", parser.getParagraphContent().toString()); + assertParagraphLines("[foo]: /url 'two\nlines", parser); assertEquals(0, parser.getDefinitions().size()); parser.parse("'"); assertEquals(State.START_DEFINITION, parser.getState()); - assertEquals("", parser.getParagraphContent().toString()); + assertParagraphLines("", parser); assertEquals(1, parser.getDefinitions().size()); assertDef(parser.getDefinitions().get(0), "foo", "/url", "two\nlines\n"); @@ -168,7 +166,7 @@ private static void assertState(String input, State state, String paragraphConte LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); parser.parse(input); assertEquals(state, parser.getState()); - assertEquals(paragraphContent, parser.getParagraphContent().toString()); + assertParagraphLines(paragraphContent, parser); } private static void assertDef(LinkReferenceDefinition def, String label, String destination, String title) { @@ -176,4 +174,16 @@ private static void assertDef(LinkReferenceDefinition def, String label, String assertEquals(destination, def.getDestination()); assertEquals(title, def.getTitle()); } + + private static void assertParagraphLines(String expectedContent, LinkReferenceDefinitionParser parser) { + StringBuilder sb = new StringBuilder(); + for (CharSequence line : parser.getParagraphLines()) { + if (sb.length() != 0) { + sb.append('\n'); + } + sb.append(line); + } + String actual = sb.toString(); + assertEquals(expectedContent, actual); + } } diff --git a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java index ed8e958ad..798330e8d 100644 --- a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java @@ -2,13 +2,16 @@ import org.junit.Test; -import static org.junit.Assert.assertEquals; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; public class ScannerTest { - + @Test public void testNext() { - Scanner scanner = new Scanner("foo bar", 4); + Scanner scanner = new Scanner(Collections.<CharSequence>singletonList("foo bar"), 0, 4); assertEquals('b', scanner.peek()); scanner.next(); assertEquals('a', scanner.peek()); @@ -17,4 +20,63 @@ public void testNext() { scanner.next(); assertEquals('\0', scanner.peek()); } + + @Test + public void testMultipleLines() { + Scanner scanner = new Scanner(Arrays.<CharSequence>asList("ab", "cde"), 0, 0); + assertTrue(scanner.hasNext()); + assertEquals('\0', scanner.peekPrevious()); + assertEquals('a', scanner.peek()); + scanner.next(); + + assertTrue(scanner.hasNext()); + assertEquals('a', scanner.peekPrevious()); + assertEquals('b', scanner.peek()); + scanner.next(); + + assertTrue(scanner.hasNext()); + assertEquals('b', scanner.peekPrevious()); + assertEquals('\n', scanner.peek()); + scanner.next(); + + assertTrue(scanner.hasNext()); + assertEquals('\n', scanner.peekPrevious()); + assertEquals('c', scanner.peek()); + scanner.next(); + + assertTrue(scanner.hasNext()); + assertEquals('c', scanner.peekPrevious()); + assertEquals('d', scanner.peek()); + scanner.next(); + + assertTrue(scanner.hasNext()); + assertEquals('d', scanner.peekPrevious()); + assertEquals('e', scanner.peek()); + scanner.next(); + + assertFalse(scanner.hasNext()); + assertEquals('e', scanner.peekPrevious()); + assertEquals('\0', scanner.peek()); + } + + @Test + public void testTextBetween() { + Scanner scanner = new Scanner(Arrays.<CharSequence>asList("ab", "cde"), 0, 0); + Position start = scanner.position(); + scanner.next(); + assertEquals("a", scanner.textBetween(start, scanner.position())); + Position afterA = scanner.position(); + scanner.next(); + assertEquals("ab", scanner.textBetween(start, scanner.position())); + scanner.next(); + assertEquals("ab\n", scanner.textBetween(start, scanner.position())); + scanner.next(); + assertEquals("ab\nc", scanner.textBetween(start, scanner.position())); + scanner.next(); + assertEquals("ab\ncd", scanner.textBetween(start, scanner.position())); + scanner.next(); + assertEquals("ab\ncde", scanner.textBetween(start, scanner.position())); + + assertEquals("b\ncde", scanner.textBetween(afterA, scanner.position())); + } } diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index e058de378..216bdeefe 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -103,7 +103,7 @@ public void indentation() { public void inlineParser() { final InlineParser fakeInlineParser = new InlineParser() { @Override - public void parse(String input, Node node) { + public void parse(List<CharSequence> lines, Node node) { node.appendChild(new ThematicBreak()); } }; diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index a70127a72..2b19db3db 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -167,4 +167,11 @@ public void deeplyIndentedList() { "</li>\n" + "</ul>\n"); } + + @Test + public void trailingTabs() { + // The tab is not treated as 4 spaces here and so does not result in a hard line break, but is just preserved. + // This matches what commonmark.js did at the time of writing. + assertRendering("a\t\nb\n", "<p>a\t\nb</p>\n"); + } } From aa9590c7af7d6695f2e8c3f686dccfde06126644 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 3 Aug 2020 17:14:01 +1000 Subject: [PATCH 440/815] Keep a single Scanner instance This avoids a lot of copying (we still need to copy the position though). In exchange, we need to be more careful and reset the position if we want to backtrack. --- .../commonmark/internal/DocumentParser.java | 1 - .../commonmark/internal/InlineParserImpl.java | 69 +++++-------------- .../LinkReferenceDefinitionParser.java | 3 +- .../internal/inline/InlineParserState.java | 7 ++ .../commonmark/internal/inline/Scanner.java | 44 +++++++++--- 5 files changed, 62 insertions(+), 62 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 5c3cdfd3d..58b390b63 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -428,7 +428,6 @@ private void addLine() { } else if (index == 0) { content = line; } else { - // TODO: Maybe we should bring back Subsequence here? content = line.subSequence(index, line.length()); } getActiveBlockParser().addLine(content); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index eee25af40..5383d9827 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -27,10 +27,7 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private final InlineParserContext context; private final Map<Character, List<InlineContentParser>> inlineParsers; - // TODO: Should we just keep a scanner here instead? - private List<CharSequence> lines; - private int lineIndex; - private int index; + private Scanner scanner; private int trailingSpaces; /** @@ -86,10 +83,9 @@ public static Map<Character, DelimiterProcessor> calculateDelimiterProcessors(Li return map; } - // TODO: The implementation shouldn't be public @Override public Scanner scanner() { - return new Scanner(lines, lineIndex, index); + return scanner; } private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> map) { @@ -145,15 +141,8 @@ public void parse(List<CharSequence> lines, Node block) { mergeChildTextNodes(block); } - void setPosition(Position position) { - lineIndex = position.getLineIndex(); - index = position.getIndex(); - } - void reset(List<CharSequence> lines) { - this.lines = lines; - this.lineIndex = 0; - this.index = 0; + this.scanner = Scanner.of(lines); this.trailingSpaces = 0; this.lastDelimiter = null; this.lastBracket = null; @@ -169,20 +158,23 @@ private Text text(String text) { * On failure, return null. */ private Node parseInline() { - Scanner scanner = scanner(); char c = scanner.peek(); if (c == '\0') { return null; } + Position position = scanner.position(); List<InlineContentParser> inlineParsers = this.inlineParsers.get(c); if (inlineParsers != null) { for (InlineContentParser inlineParser : inlineParsers) { ParsedInline parsedInline = inlineParser.tryParse(this); if (parsedInline instanceof ParsedInlineImpl) { ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; - setPosition(parsedInlineImpl.getPosition()); + scanner.setPosition(parsedInlineImpl.getPosition()); return parsedInlineImpl.getNode(); + } else { + // Reset position + scanner.setPosition(position); } } } @@ -209,22 +201,6 @@ private Node parseInline() { // If we get here, even for a special/delimiter character, we will just treat it as text. return parseText(); -// } else { -// node = parseString(); -// } -// break; -// -// Node node; -// if (node != null) { -// return node; -// } else { -// scanner.next(); -// setPosition(scanner.position()); -// // When we get here, it's only for a single special character that turned out to not have a special meaning. -// // So we shouldn't have a single surrogate here, hence it should be ok to turn it into a String. -// String literal = String.valueOf(c); -// return text(literal); -// } } /** @@ -253,10 +229,8 @@ private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimit * Add open bracket to delimiter stack and add a text node to block's children. */ private Node parseOpenBracket() { - Scanner scanner = scanner(); scanner.next(); Position start = scanner.position(); - setPosition(start); Text node = text("["); @@ -271,18 +245,14 @@ private Node parseOpenBracket() { * Otherwise just add a text node. */ private Node parseBang() { - Scanner scanner = scanner(); scanner.next(); if (scanner.next('[')) { Text node = text("!["); // Add entry to stack for this opener addBracket(Bracket.image(node, scanner.position(), lastBracket, lastDelimiter)); - - setPosition(scanner.position()); return node; } else { - setPosition(scanner.position()); return text("!"); } } @@ -292,10 +262,9 @@ private Node parseBang() { * plain [ character. If there is a matching delimiter, remove it from the delimiter stack. */ private Node parseCloseBracket() { - Scanner scanner = scanner(); Position beforeClose = scanner.position(); scanner.next(); - setPosition(scanner.position()); + Position afterClose = scanner.position(); // Get previous `[` or `![` Bracket opener = lastBracket; @@ -318,7 +287,9 @@ private Node parseCloseBracket() { if (scanner.next('(')) { scanner.whitespace(); dest = parseLinkDestination(scanner); - if (dest != null) { + if (dest == null) { + scanner.setPosition(afterClose); + } else { int whitespace = scanner.whitespace(); // title needs a whitespace before if (whitespace >= 1) { @@ -328,7 +299,7 @@ private Node parseCloseBracket() { 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 = scanner(); + scanner.setPosition(afterClose); dest = null; title = null; } @@ -341,6 +312,9 @@ private Node parseCloseBracket() { 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. @@ -388,14 +362,13 @@ private Node parseCloseBracket() { } } - setPosition(scanner.position()); - return linkOrImage; } else { // No link or image, parse just the bracket as text and continue removeLastBracket(); + scanner.setPosition(afterClose); return text("]"); } } @@ -476,9 +449,7 @@ String parseLinkLabel(Scanner scanner) { } private Node parseLineBreak() { - Scanner scanner = scanner(); scanner.next(); - setPosition(scanner.position()); if (trailingSpaces >= 2) { return new HardLineBreak(); @@ -491,7 +462,6 @@ private Node parseLineBreak() { * Parse the next character as plain text, and possibly more if the following characters are non-special. */ private Node parseText() { - Scanner scanner = scanner(); Position start = scanner.position(); scanner.next(); while (scanner.hasNext()) { @@ -502,7 +472,6 @@ private Node parseText() { } String text = scanner.textBetween(start, scanner.position()).toString(); - setPosition(scanner.position()); char c = scanner.peek(); if (c == '\n') { @@ -526,14 +495,13 @@ private Node parseText() { * @return information about delimiter run, or {@code null} */ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { - Scanner scanner = scanner(); char charBefore = scanner.peekPrevious(); Position start = scanner.position(); int delimiterCount = scanner.matchMultiple(delimiterChar); if (delimiterCount < delimiterProcessor.getMinLength()) { - setPosition(start); + scanner.setPosition(start); return null; } @@ -561,7 +529,6 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingCharacter(); } - setPosition(scanner.position()); String text = scanner.textBetween(start, scanner.position()).toString(); return new DelimiterData(delimiterCount, canOpen, canClose, new Text(text)); } diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 7f257343b..950fc31c8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -8,7 +8,6 @@ import org.commonmark.node.SourceSpan; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -39,7 +38,7 @@ public void parse(CharSequence line) { return; } - Scanner scanner = new Scanner(Collections.singletonList(line), 0, 0); + Scanner scanner = Scanner.of(line); while (scanner.hasNext()) { boolean success; switch (state) { 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 9a6ef7d19..f6cb6bf49 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineParserState.java @@ -2,5 +2,12 @@ public interface InlineParserState { + /** + * Return a scanner for the input for the current position (on the character that the inline parser registered + * interest for). + * <p> + * Note that this always returns the same instance, if you want to backtrack you need to use + * {@link Scanner#position()} and {@link Scanner#setPosition(Position)}. + */ Scanner scanner(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index b144ba52a..a96493d7c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -2,6 +2,7 @@ import org.commonmark.internal.util.CharMatcher; +import java.util.Collections; import java.util.List; public class Scanner { @@ -19,17 +20,24 @@ public class Scanner { private CharSequence line = ""; private int lineLength = 0; - // TODO: Visibility - public Scanner(List<CharSequence> lines, int lineIndex, int index) { + Scanner(List<CharSequence> lines, int lineIndex, int index) { this.lines = lines; this.lineIndex = lineIndex; this.index = index; if (!lines.isEmpty()) { - line = lines.get(lineIndex); - lineLength = line.length(); + checkPosition(lineIndex, index); + setLine(lines.get(lineIndex)); } } + public static Scanner of(List<CharSequence> lines) { + return new Scanner(lines, 0, 0); + } + + public static Scanner of(CharSequence line) { + return new Scanner(Collections.singletonList(line), 0, 0); + } + public char peek() { if (index < lineLength) { return line.charAt(index); @@ -70,11 +78,9 @@ public void next() { if (index > lineLength) { lineIndex++; if (lineIndex < lines.size()) { - line = lines.get(lineIndex); - lineLength = line.length(); + setLine(lines.get(lineIndex)); } else { - line = ""; - lineLength = 0; + setLine(""); } index = 0; } @@ -160,6 +166,13 @@ public Position position() { return new Position(lineIndex, index); } + public void setPosition(Position position) { + checkPosition(position.lineIndex, position.index); + this.lineIndex = position.lineIndex; + this.index = position.index; + setLine(lines.get(this.lineIndex)); + } + // For cases where the caller appends the result to a StringBuilder, we could offer another method to avoid some // unnecessary copying. public CharSequence textBetween(Position begin, Position end) { @@ -184,4 +197,19 @@ public CharSequence textBetween(Position begin, Position end) { return sb.toString(); } } + + private void setLine(CharSequence line) { + this.line = line; + this.lineLength = line.length(); + } + + private void checkPosition(int lineIndex, int index) { + if (lineIndex < 0 || lineIndex >= lines.size()) { + throw new IllegalArgumentException("Line index " + lineIndex + " out of range, number of lines: " + lines.size()); + } + CharSequence line = lines.get(lineIndex); + if (index < 0 || index > line.length()) { + throw new IllegalArgumentException("Index " + index + " out of range, line length: " + line.length()); + } + } } From c5fc1f75e614461219df9dee9715f314a3b2df55 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 3 Aug 2020 17:26:32 +1000 Subject: [PATCH 441/815] Add Scanner.END constant --- .../commonmark/internal/InlineParserImpl.java | 8 ++++---- .../org/commonmark/internal/inline/Scanner.java | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 5383d9827..dedf68599 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -159,7 +159,7 @@ private Text text(String text) { */ private Node parseInline() { char c = scanner.peek(); - if (c == '\0') { + if (c == Scanner.END) { return null; } @@ -479,7 +479,7 @@ private Node parseText() { int end = Parsing.skipBackwards(' ', text, text.length() - 1, 0) + 1; trailingSpaces = text.length() - end; text = text.substring(0, end); - } else if (c == '\0') { + } 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(text, text.length() - 1, 0) + 1; text = text.substring(0, end); @@ -506,8 +506,8 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char } char charAfter = scanner.peek(); - String before = charBefore == '\0' ? "\n" : String.valueOf(charBefore); - String after = charAfter == '\0' ? "\n" : String.valueOf(charAfter); + String before = charBefore == Scanner.END ? "\n" : String.valueOf(charBefore); + String after = charAfter == Scanner.END ? "\n" : String.valueOf(charAfter); // We could be more lazy here, in most cases we don't need to do every match case. boolean beforeIsPunctuation = PUNCTUATION.matcher(before).matches(); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index a96493d7c..7c1076e2d 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -7,6 +7,14 @@ public class Scanner { + /** + * Character representing the end of input (or outside of the text in case of the "previous" methods). + * <p> + * Note that we can use NULL to represent this because CommonMark does not allow those in the input (we replace them + * in the beginning of parsing). + */ + public static final char END = '\0'; + // Lines without newlines at the end. The scanner will yield `\n` between lines because they're significant for // parsing and the final output. There is no `\n` after the last line. private final List<CharSequence> lines; @@ -46,7 +54,7 @@ public char peek() { return '\n'; } else { // Don't return newline for end of last line - return '\0'; + return END; } } } @@ -59,7 +67,7 @@ public char peekPrevious() { if (lineIndex > 0) { return '\n'; } else { - return '\0'; + return END; } } } @@ -136,7 +144,7 @@ public int find(char c) { int count = 0; while (true) { char cur = peek(); - if (cur == '\0') { + if (cur == Scanner.END) { return -1; } else if (cur == c) { return count; @@ -150,7 +158,7 @@ public int find(CharMatcher matcher) { int count = 0; while (true) { char c = peek(); - if (c == '\0') { + if (c == END) { return -1; } else if (matcher.matches(c)) { return count; From 62ee2b6435c3b29a1d4b5280d2fec99f9633af70 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 3 Aug 2020 17:53:25 +1000 Subject: [PATCH 442/815] Add Scanner.next(String) --- .../internal/inline/HtmlInlineParser.java | 24 ++++++---------- .../commonmark/internal/inline/Scanner.java | 28 +++++++++++++++++++ .../internal/inline/ScannerTest.java | 11 ++++++++ 3 files changed, 47 insertions(+), 16 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 6abe12487..1fe20ecb8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -151,24 +151,15 @@ private static boolean tryComment(Scanner scanner) { return false; } - if (scanner.next('>')) { + if (scanner.next('>') || scanner.next("->")) { return false; } - if (scanner.next('-')) { - // Can't start with -> - if (scanner.next('>')) { - return false; - } - // Empty comment - if (scanner.next('-')) { - return scanner.next('>'); - } - } - while (scanner.find('-') >= 0) { - if (scanner.next('-') && scanner.next('-')) { + if (scanner.next("--")) { return scanner.next('>'); + } else { + scanner.next(); } } @@ -182,11 +173,12 @@ private static boolean tryCdata(Scanner scanner) { // Skip `[` scanner.next(); - if (scanner.next('C') && scanner.next('D') && scanner.next('A') && scanner.next('T') && scanner.next('A') - && scanner.next('[')) { + if (scanner.next("CDATA[")) { while (scanner.find(']') >= 0) { - if (scanner.next(']') && scanner.next(']') && scanner.next('>')) { + if (scanner.next("]]>")) { return true; + } else { + scanner.next(); } } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 7c1076e2d..5f6b30f00 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -94,6 +94,12 @@ public void next() { } } + /** + * Check if the specified char is next and advance the position. + * + * @param c the char to check (including newline characters) + * @return true if matched and position was advanced, false otherwise + */ public boolean next(char c) { if (peek() == c) { next(); @@ -103,6 +109,28 @@ public boolean next(char c) { } } + /** + * Check if we have the specified content on the line and advanced the position. Note that if you want to match + * newline characters, use {@link #next(char)}. + * + * @param content the text content to match on a single line (excluding newline characters) + * @return true if matched and position was advanced, false otherwise + */ + public boolean next(String content) { + if (index < lineLength && index + content.length() <= lineLength) { + // Can't use startsWith because it's not available on CharSequence + for (int i = 0; i < content.length(); i++) { + if (line.charAt(index + i) != content.charAt(i)) { + return false; + } + } + index += content.length(); + return true; + } else { + return false; + } + } + public int matchMultiple(char c) { int count = 0; while (peek() == c) { diff --git a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java index 798330e8d..c68140a71 100644 --- a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java @@ -79,4 +79,15 @@ public void testTextBetween() { assertEquals("b\ncde", scanner.textBetween(afterA, scanner.position())); } + + @Test + public void nextString() { + Scanner scanner = Scanner.of(Arrays.<CharSequence>asList("hey ya", "hi")); + assertFalse(scanner.next("hoy")); + assertTrue(scanner.next("hey")); + assertTrue(scanner.next(' ')); + assertFalse(scanner.next("yo")); + assertTrue(scanner.next("ya")); + assertFalse(scanner.next(" ")); + } } From 733739ff5a262f1095820787dfc9ffe468bbba96 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 4 Aug 2020 15:50:04 +1000 Subject: [PATCH 443/815] Clean up some comments --- .../internal/inline/InlineContentParser.java | 2 -- .../org/commonmark/internal/inline/Position.java | 15 +++++---------- .../commonmark/internal/util/AsciiMatcher.java | 1 - 3 files changed, 5 insertions(+), 13 deletions(-) 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 dc8c43640..755ee3135 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/InlineContentParser.java @@ -1,8 +1,6 @@ package org.commonmark.internal.inline; -// TODO: I'd prefer if this was named InlineParser, but that's already public API, hmm... public interface InlineContentParser { ParsedInline tryParse(InlineParserState inlineParserState); - } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Position.java b/commonmark/src/main/java/org/commonmark/internal/inline/Position.java index 4a54cbce6..5f06a063a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Position.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Position.java @@ -1,6 +1,11 @@ package org.commonmark.internal.inline; +/** + * Position within a {@link Scanner}. This is intentionally kept opaque so as not to expose the internal structure of + * the Scanner. + */ public class Position { + final int lineIndex; final int index; @@ -8,14 +13,4 @@ public class Position { this.lineIndex = lineIndex; this.index = index; } - - // TODO: Move packages around so that this can stay package-private - public int getLineIndex() { - return lineIndex; - } - - // TODO: Move packages around so that this can stay package-private - public int getIndex() { - return index; - } } 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 0e7ab345e..82d83ca46 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/AsciiMatcher.java @@ -3,7 +3,6 @@ import java.util.BitSet; public class AsciiMatcher implements CharMatcher { - // TODO: Check if boolean[] is faster, see BitClass in java.util.regex.Pattern private final BitSet set; private AsciiMatcher(Builder builder) { From 868d81c471e5b463b67295afe4321048c3722f1f Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 8 Sep 2020 16:16:54 +1000 Subject: [PATCH 444/815] Remove TODO, fix trimming in tables after rebasing --- .../commonmark/ext/gfm/tables/internal/TableBlockParser.java | 2 +- .../src/main/java/org/commonmark/parser/InlineParser.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 107aa2167..77bd61b3a 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 @@ -107,7 +107,7 @@ private TableCell parseCell(CellSource cell, int column, InlineParser inlinePars tableCell.setSourceSpans(Collections.singletonList(cell.sourceSpan)); } - inlineParser.parse(Collections.<CharSequence>singletonList(cell.content), tableCell); + inlineParser.parse(Collections.<CharSequence>singletonList(cell.content.trim()), tableCell); return tableCell; } diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java index 291fd4900..cbebdc25a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java @@ -13,6 +13,5 @@ public interface InlineParser { * @param lines the content to parse as inline * @param node the node to append resulting nodes to (as children) */ - // TODO: Should we use a better type here, one that will be able to include source positions? void parse(List<CharSequence> lines, Node node); } From ee816826404c49f068e7ae79fbafe17ebdf84761 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 10 Sep 2020 20:50:04 +1000 Subject: [PATCH 445/815] WIP inline source spans --- .../gfm/tables/internal/TableBlockParser.java | 10 ++- .../internal/YamlFrontMatterBlockParser.java | 3 +- .../commonmark/internal/BlockQuoteParser.java | 6 +- .../internal/DocumentBlockParser.java | 3 +- .../commonmark/internal/DocumentParser.java | 63 +++++--------- .../internal/FencedCodeBlockParser.java | 7 +- .../commonmark/internal/HeadingParser.java | 83 ++++++++++++------- .../commonmark/internal/HtmlBlockParser.java | 9 +- .../internal/IndentedCodeBlockParser.java | 5 +- .../commonmark/internal/InlineParserImpl.java | 75 ++++++++++------- .../LinkReferenceDefinitionParser.java | 12 +-- .../commonmark/internal/ListBlockParser.java | 2 +- .../commonmark/internal/ParagraphParser.java | 8 +- .../org/commonmark/internal/SourceSpans.java | 13 --- .../internal/ThematicBreakParser.java | 2 +- .../internal/inline/AutolinkInlineParser.java | 33 +++++--- .../inline/BackticksInlineParser.java | 8 +- .../commonmark/internal/inline/Scanner.java | 64 +++++++------- .../org/commonmark/internal/util/Parsing.java | 17 ---- .../main/java/org/commonmark/node/Node.java | 6 +- .../commonmark/parser/IncludeSourceSpans.java | 4 + .../org/commonmark/parser/InlineParser.java | 6 +- .../org/commonmark/parser/SourceLine.java | 47 +++++++++++ .../org/commonmark/parser/SourceLines.java | 65 +++++++++++++++ .../parser/block/AbstractBlockParser.java | 3 +- .../commonmark/parser/block/BlockParser.java | 5 +- .../parser/block/MatchedBlockParser.java | 4 +- .../commonmark/parser/block/ParserState.java | 4 +- .../LinkReferenceDefinitionParserTest.java | 62 ++++++-------- .../internal/inline/ScannerTest.java | 73 +++++++++++++--- .../java/org/commonmark/test/ParserTest.java | 7 +- 31 files changed, 448 insertions(+), 261 deletions(-) delete mode 100644 commonmark/src/main/java/org/commonmark/internal/SourceSpans.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/SourceLine.java create mode 100644 commonmark/src/main/java/org/commonmark/parser/SourceLines.java 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 77bd61b3a..59eb89883 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 @@ -5,6 +5,7 @@ import org.commonmark.node.Node; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; import java.util.ArrayList; @@ -42,8 +43,9 @@ public BlockContinue tryContinue(ParserState state) { } @Override - public void addLine(CharSequence line) { - rowLines.add(line); + public void addLine(SourceLine line) { + // TODO: Need to preserve these for inlines + rowLines.add(line.getContent()); } @Override @@ -107,7 +109,9 @@ private TableCell parseCell(CellSource cell, int column, InlineParser inlinePars tableCell.setSourceSpans(Collections.singletonList(cell.sourceSpan)); } - inlineParser.parse(Collections.<CharSequence>singletonList(cell.content.trim()), tableCell); + // TODO: Need to fix + SourceLine line = SourceLine.of(cell.content.trim(), null); + inlineParser.parse(Collections.singletonList(line), tableCell); return tableCell; } 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 5612d9ffd..86d827876 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 @@ -5,6 +5,7 @@ import org.commonmark.internal.DocumentBlockParser; import org.commonmark.node.Block; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; import java.util.ArrayList; @@ -37,7 +38,7 @@ public Block getBlock() { } @Override - public void addLine(CharSequence line) { + public void addLine(SourceLine line) { } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java index 6b19f8aaf..00cdbc11e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/BlockQuoteParser.java @@ -30,7 +30,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(), nextNonSpace + 1)) { + if (Parsing.isSpaceOrTab(state.getLine().getContent(), nextNonSpace + 1)) { newColumn++; } return BlockContinue.atColumn(newColumn); @@ -40,7 +40,7 @@ public BlockContinue tryContinue(ParserState state) { } private static boolean isMarker(ParserState state, int index) { - CharSequence line = state.getLine(); + CharSequence line = state.getLine().getContent(); return state.getIndent() < Parsing.CODE_BLOCK_INDENT && index < line.length() && line.charAt(index) == '>'; } @@ -50,7 +50,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(), nextNonSpace + 1)) { + if (Parsing.isSpaceOrTab(state.getLine().getContent(), nextNonSpace + 1)) { newColumn++; } return BlockStart.of(new BlockQuoteParser()).atColumn(newColumn); diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java index 4a30544e7..db3d3854f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentBlockParser.java @@ -2,6 +2,7 @@ import org.commonmark.node.Block; import org.commonmark.node.Document; +import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.BlockContinue; import org.commonmark.parser.block.ParserState; @@ -31,7 +32,7 @@ public BlockContinue tryContinue(ParserState state) { } @Override - public void addLine(CharSequence line) { + public void addLine(SourceLine line) { } } diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 58b390b63..c07d7ba78 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -1,41 +1,15 @@ package org.commonmark.internal; import org.commonmark.internal.util.Parsing; -import org.commonmark.node.Block; -import org.commonmark.node.BlockQuote; -import org.commonmark.node.Document; -import org.commonmark.node.FencedCodeBlock; -import org.commonmark.node.Heading; -import org.commonmark.node.HtmlBlock; -import org.commonmark.node.IndentedCodeBlock; -import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.node.ListBlock; -import org.commonmark.node.Paragraph; -import org.commonmark.node.SourceSpan; -import org.commonmark.node.ThematicBreak; -import org.commonmark.parser.InlineParser; -import org.commonmark.parser.InlineParserFactory; -import org.commonmark.parser.IncludeSourceSpans; -import org.commonmark.parser.block.BlockContinue; -import org.commonmark.parser.block.BlockParser; -import org.commonmark.parser.block.BlockParserFactory; -import org.commonmark.parser.block.BlockStart; -import org.commonmark.parser.block.MatchedBlockParser; -import org.commonmark.parser.block.ParserState; +import org.commonmark.node.*; +import org.commonmark.parser.*; +import org.commonmark.parser.block.*; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; public class DocumentParser implements ParserState { @@ -165,12 +139,12 @@ public Document parse(Reader input) throws IOException { } @Override - public CharSequence getLine() { - return line; - } - - public int getLineIndex() { - return lineIndex; + public SourceLine getLine() { + SourceSpan sourceSpan = null; + if (includeSourceSpans != IncludeSourceSpans.NONE) { + sourceSpan = SourceSpan.of(lineIndex, index, line.length()); + } + return SourceLine.of(line, sourceSpan); } @Override @@ -399,11 +373,10 @@ private void setNewColumn(int newColumn) { private void advance() { char c = line.charAt(index); + index++; if (c == '\t') { - index++; column += Parsing.columnsToNextTabStop(column); } else { - index++; column++; } } @@ -430,7 +403,13 @@ private void addLine() { } else { content = line.subSequence(index, line.length()); } - getActiveBlockParser().addLine(content); + SourceSpan sourceSpan = null; +// SourceLine sourceLine = ; + if (includeSourceSpans == IncludeSourceSpans.BLOCKS_AND_INLINES) { + // TODO: This is not correct for expanded tabs + sourceSpan = SourceSpan.of(lineIndex, index, content.length()); + } + getActiveBlockParser().addLine(SourceLine.of(content, sourceSpan)); addSourceSpans(); } @@ -568,12 +547,12 @@ public BlockParser getMatchedBlockParser() { } @Override - public List<CharSequence> getParagraphLines() { + public SourceLines getParagraphLines() { if (matchedBlockParser instanceof ParagraphParser) { ParagraphParser paragraphParser = (ParagraphParser) matchedBlockParser; - return Collections.unmodifiableList(paragraphParser.getParagraphLines()); + return paragraphParser.getParagraphLines(); } - return Collections.emptyList(); + return SourceLines.empty(); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index e57cc7277..86eec3675 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -3,6 +3,7 @@ import org.commonmark.internal.util.Parsing; import org.commonmark.node.Block; import org.commonmark.node.FencedCodeBlock; +import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; import static org.commonmark.internal.util.Escaping.unescapeString; @@ -29,7 +30,7 @@ public Block getBlock() { public BlockContinue tryContinue(ParserState state) { int nextNonSpace = state.getNextNonSpaceIndex(); int newIndex = state.getIndex(); - CharSequence line = state.getLine(); + CharSequence line = state.getLine().getContent(); boolean closing = state.getIndent() < Parsing.CODE_BLOCK_INDENT && isClosing(line, nextNonSpace); if (closing) { // closing fence - we're at end of line, so we can finalize now @@ -47,7 +48,7 @@ public BlockContinue tryContinue(ParserState state) { } @Override - public void addLine(CharSequence line) { + public void addLine(SourceLine line) { if (firstLine == null) { firstLine = line.toString(); } else { @@ -73,7 +74,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } int nextNonSpace = state.getNextNonSpaceIndex(); - FencedCodeBlockParser blockParser = checkOpener(state.getLine(), nextNonSpace, indent); + FencedCodeBlockParser blockParser = checkOpener(state.getLine().getContent(), nextNonSpace, indent); if (blockParser != null) { return BlockStart.of(blockParser).atIndex(nextNonSpace + blockParser.block.getFenceLength()); } else { diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 88bf002f0..a7d856790 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -1,20 +1,21 @@ 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.block.*; -import java.util.Collections; -import java.util.List; - public class HeadingParser extends AbstractBlockParser { private final Heading block = new Heading(); - private final List<CharSequence> content; + private final SourceLines content; - public HeadingParser(int level, List<CharSequence> content) { + public HeadingParser(int level, SourceLines content) { block.setLevel(level); this.content = content; } @@ -43,19 +44,19 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar return BlockStart.none(); } - CharSequence line = state.getLine(); - int nextNonSpace = state.getNextNonSpaceIndex(); - HeadingParser atxHeading = getAtxHeading(line, nextNonSpace); + SourceLine line = state.getLine(); + HeadingParser atxHeading = getAtxHeading(line); if (atxHeading != null) { - return BlockStart.of(atxHeading).atIndex(line.length()); + return BlockStart.of(atxHeading).atIndex(line.getContent().length()); } - int setextHeadingLevel = getSetextHeadingLevel(line, nextNonSpace); + int nextNonSpace = state.getNextNonSpaceIndex(); + int setextHeadingLevel = getSetextHeadingLevel(line.getContent(), nextNonSpace); if (setextHeadingLevel > 0) { - List<CharSequence> paragraph = matchedBlockParser.getParagraphLines(); + SourceLines paragraph = matchedBlockParser.getParagraphLines(); if (!paragraph.isEmpty()) { return BlockStart.of(new HeadingParser(setextHeadingLevel, paragraph)) - .atIndex(line.length()) + .atIndex(line.getContent().length()) .replaceActiveBlockParser(); } } @@ -68,36 +69,62 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar // 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(CharSequence line, int index) { - int level = Parsing.skip('#', line, index, line.length()) - index; + private static HeadingParser getAtxHeading(SourceLine line) { + Scanner scanner = Scanner.of(SourceLines.of(line)); + scanner.whitespace(); + int level = scanner.matchMultiple('#'); if (level == 0 || level > 6) { return null; } - int afterMarker = index + level; - if (afterMarker >= line.length()) { + if (!scanner.hasNext()) { // End of line after markers is an empty heading - return new HeadingParser(level, Collections.<CharSequence>emptyList()); + return new HeadingParser(level, SourceLines.empty()); } - char next = line.charAt(afterMarker); + char next = scanner.peek(); if (!(next == ' ' || next == '\t')) { return null; } - int start = Parsing.skipSpaceTab(line, afterMarker, line.length()); + scanner.whitespace(); + Position start = scanner.position(); + Position end = start; + boolean hashCanEnd = true; + + while (scanner.hasNext()) { + char c = scanner.peek(); + switch (c) { + case '#': + if (hashCanEnd) { + scanner.matchMultiple('#'); + scanner.whitespace(); + // If there's other characters, the hashes and spaces were part of the heading + hashCanEnd = false; + } else { + scanner.next(); + end = scanner.position(); + } + break; + case ' ': + case '\t': + hashCanEnd = true; + scanner.next(); + break; + default: + hashCanEnd = false; + scanner.next(); + end = scanner.position(); + } + } - int beforeSpace = Parsing.skipSpaceTabBackwards(line, line.length() - 1, afterMarker); - int beforeHash = Parsing.skipBackwards('#', line, beforeSpace, afterMarker); - int beforeTrailer = Parsing.skipSpaceTabBackwards(line, beforeHash, afterMarker); - // Trailing `#` need to be separated with at least one space/tab, otherwise they are part of the content. - int end = (beforeTrailer < beforeHash) ? beforeTrailer + 1 : beforeSpace + 1; - if (start >= end) { - // Empty, e.g. `### ###` - return new HeadingParser(level, Collections.<CharSequence>emptyList()); + SourceLines source = scanner.textBetween(start, end); + String content = source.getContent(); + if (content.isEmpty()) { + return new HeadingParser(level, SourceLines.empty()); } - return new HeadingParser(level, Collections.singletonList(line.subSequence(start, end))); + return new HeadingParser(level, source); } // spec: A setext heading underline is a sequence of = characters or a sequence of - characters, with no more than diff --git a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java index 3b3a0e64f..fa1befa2a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -4,6 +4,7 @@ import org.commonmark.node.Block; import org.commonmark.node.HtmlBlock; import org.commonmark.node.Paragraph; +import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; import java.util.regex.Pattern; @@ -88,10 +89,10 @@ public BlockContinue tryContinue(ParserState state) { } @Override - public void addLine(CharSequence line) { - content.add(line); + public void addLine(SourceLine line) { + content.add(line.getContent()); - if (closingPattern != null && closingPattern.matcher(line).find()) { + if (closingPattern != null && closingPattern.matcher(line.getContent()).find()) { finished = true; } } @@ -107,7 +108,7 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { int nextNonSpace = state.getNextNonSpaceIndex(); - CharSequence line = state.getLine(); + CharSequence line = state.getLine().getContent(); if (state.getIndent() < 4 && line.charAt(nextNonSpace) == '<') { for (int blockType = 1; blockType <= 7; blockType++) { diff --git a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java index e90fdf6a9..af74a587c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/IndentedCodeBlockParser.java @@ -4,6 +4,7 @@ import org.commonmark.node.Block; import org.commonmark.node.IndentedCodeBlock; import org.commonmark.node.Paragraph; +import org.commonmark.parser.SourceLine; import org.commonmark.parser.block.*; import java.util.ArrayList; @@ -31,8 +32,8 @@ public BlockContinue tryContinue(ParserState state) { } @Override - public void addLine(CharSequence line) { - lines.add(line); + public void addLine(SourceLine line) { + lines.add(line.getContent()); } @Override diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index dedf68599..e4cd1e737 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -8,6 +8,7 @@ import org.commonmark.node.*; import org.commonmark.parser.InlineParser; import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.SourceLines; import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.*; @@ -125,7 +126,7 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr * Parse content in block into inline children, appending them to the block node. */ @Override - public void parse(List<CharSequence> lines, Node block) { + public void parse(SourceLines lines, Node block) { reset(lines); while (true) { @@ -141,15 +142,23 @@ public void parse(List<CharSequence> lines, Node block) { mergeChildTextNodes(block); } - void reset(List<CharSequence> lines) { + void reset(SourceLines lines) { this.scanner = Scanner.of(lines); this.trailingSpaces = 0; this.lastDelimiter = null; this.lastBracket = null; } - private Text text(String text) { - return new Text(text); + private Text text(String content, List<SourceSpan> sourceSpans) { + Text text = new Text(content); + text.setSourceSpans(sourceSpans); + return text; + } + + private Text text(SourceLines sourceLines) { + Text text = new Text(sourceLines.getContent()); + text.setSourceSpans(sourceLines.getSourceSpans()); + return text; } /** @@ -170,6 +179,7 @@ private Node parseInline() { ParsedInline parsedInline = inlineParser.tryParse(this); if (parsedInline instanceof ParsedInlineImpl) { ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; + // TODO: Should we set source spans here? Or let the inline parsers set it? scanner.setPosition(parsedInlineImpl.getPosition()); return parsedInlineImpl.getNode(); } else { @@ -229,13 +239,14 @@ private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimit * Add open bracket to delimiter stack and add a text node to block's children. */ private Node parseOpenBracket() { - scanner.next(); Position start = scanner.position(); + scanner.next(); + Position contentPosition = scanner.position(); - Text node = text("["); + Text node = text(scanner.textBetween(start, contentPosition)); // Add entry to stack for this opener - addBracket(Bracket.link(node, start, lastBracket, lastDelimiter)); + addBracket(Bracket.link(node, contentPosition, lastBracket, lastDelimiter)); return node; } @@ -245,15 +256,16 @@ private Node parseOpenBracket() { * Otherwise just add a text node. */ private Node parseBang() { + Position start = scanner.position(); scanner.next(); if (scanner.next('[')) { - Text node = text("!["); + Text node = text(scanner.textBetween(start, scanner.position())); // Add entry to stack for this opener addBracket(Bracket.image(node, scanner.position(), lastBracket, lastDelimiter)); return node; } else { - return text("!"); + return text(scanner.textBetween(start, scanner.position())); } } @@ -270,13 +282,13 @@ private Node parseCloseBracket() { Bracket opener = lastBracket; if (opener == null) { // No matching opener, just return a literal. - return text("]"); + return text(scanner.textBetween(beforeClose, afterClose)); } if (!opener.allowed) { // Matching opener but it's not allowed, just return a literal. removeLastBracket(); - return text("]"); + return text(scanner.textBetween(beforeClose, afterClose)); } // Check to see if we have a link/image @@ -319,7 +331,7 @@ private Node parseCloseBracket() { // 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.textBetween(opener.contentPosition, beforeClose).toString(); + ref = scanner.textBetween(opener.contentPosition, beforeClose).getContent(); } if (ref != null) { @@ -343,6 +355,8 @@ private Node parseCloseBracket() { node = next; } + // TODO: source positions + // Process delimiters such as emphasis inside link/image processDelimiters(opener.previousDelimiter); mergeChildTextNodes(linkOrImage); @@ -369,7 +383,7 @@ private Node parseCloseBracket() { removeLastBracket(); scanner.setPosition(afterClose); - return text("]"); + return text(scanner.textBetween(beforeClose, afterClose)); } } @@ -397,10 +411,10 @@ private String parseLinkDestination(Scanner scanner) { String dest; if (delimiter == '<') { // chop off surrounding <..>: - CharSequence rawDestination = scanner.textBetween(start, scanner.position()); - dest = rawDestination.subSequence(1, rawDestination.length() - 1).toString(); + String rawDestination = scanner.textBetween(start, scanner.position()).getContent(); + dest = rawDestination.substring(1, rawDestination.length() - 1); } else { - dest = scanner.textBetween(start, scanner.position()).toString(); + dest = scanner.textBetween(start, scanner.position()).getContent(); } return Escaping.unescapeString(dest); @@ -416,8 +430,8 @@ private String parseLinkTitle(Scanner scanner) { } // chop off ', " or parens - CharSequence rawTitle = scanner.textBetween(start, scanner.position()); - String title = rawTitle.subSequence(1, rawTitle.length() - 1).toString(); + String rawTitle = scanner.textBetween(start, scanner.position()).getContent(); + String title = rawTitle.substring(1, rawTitle.length() - 1); return Escaping.unescapeString(title); } @@ -439,7 +453,7 @@ String parseLinkLabel(Scanner scanner) { return null; } - String content = scanner.textBetween(start, end).toString(); + String content = scanner.textBetween(start, end).getContent(); // spec: A link label can have at most 999 characters inside the square brackets. if (content.length() > 999) { return null; @@ -471,21 +485,25 @@ private Node parseText() { scanner.next(); } - String text = scanner.textBetween(start, scanner.position()).toString(); + SourceLines source = scanner.textBetween(start, scanner.position()); + String content = source.getContent(); char c = scanner.peek(); 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(' ', text, text.length() - 1, 0) + 1; - trailingSpaces = text.length() - end; - text = text.substring(0, end); + int end = Parsing.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(text, text.length() - 1, 0) + 1; - text = text.substring(0, end); + int end = Parsing.skipSpaceTabBackwards(content, content.length() - 1, 0) + 1; + content = content.substring(0, end); } - return text(text); + Text text = new Text(content); + // TODO: Test + text.setSourceSpans(source.getSourceSpans()); + return text; } /** @@ -529,8 +547,8 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingCharacter(); } - String text = scanner.textBetween(start, scanner.position()).toString(); - return new DelimiterData(delimiterCount, canOpen, canClose, new Text(text)); + SourceLines source = scanner.textBetween(start, scanner.position()); + return new DelimiterData(delimiterCount, canOpen, canClose, text(source.getContent(), source.getSourceSpans())); } private void processDelimiters(Delimiter stackBottom) { @@ -724,6 +742,7 @@ private void mergeIfNeeded(Text first, Text last, int textLength) { node = node.getNext(); unlink.unlink(); } + // TODO: Need to merge sourcespans too String literal = sb.toString(); first.setLiteral(literal); } diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 950fc31c8..38f5cc4a9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -6,6 +6,8 @@ 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 java.util.ArrayList; import java.util.List; @@ -19,7 +21,7 @@ public class LinkReferenceDefinitionParser { private State state = State.START_DEFINITION; - private final List<CharSequence> paragraphLines = new ArrayList<>(); + private final List<SourceLine> paragraphLines = new ArrayList<>(); private final List<LinkReferenceDefinition> definitions = new ArrayList<>(); private final List<SourceSpan> sourceSpans = new ArrayList<>(); @@ -30,7 +32,7 @@ public class LinkReferenceDefinitionParser { private StringBuilder title; private boolean referenceValid = false; - public void parse(CharSequence line) { + public void parse(SourceLine line) { paragraphLines.add(line); if (state == State.PARAGRAPH) { // We're in a paragraph now. Link reference definitions can only appear at the beginning, so once @@ -38,7 +40,7 @@ public void parse(CharSequence line) { return; } - Scanner scanner = Scanner.of(line); + Scanner scanner = Scanner.of(SourceLines.of(line)); while (scanner.hasNext()) { boolean success; switch (state) { @@ -81,8 +83,8 @@ public void addSourceSpan(SourceSpan sourceSpan) { /** * @return the lines that are normal paragraph content, without newlines */ - List<CharSequence> getParagraphLines() { - return paragraphLines; + SourceLines getParagraphLines() { + return SourceLines.of(paragraphLines); } List<SourceSpan> getParagraphSourceSpans() { diff --git a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java index 1538ca41b..f6702518b 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListBlockParser.java @@ -211,7 +211,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar int markerIndex = state.getNextNonSpaceIndex(); int markerColumn = state.getColumn() + state.getIndent(); boolean inParagraph = !matchedBlockParser.getParagraphLines().isEmpty(); - ListData listData = parseList(state.getLine(), markerIndex, markerColumn, inParagraph); + ListData listData = parseList(state.getLine().getContent(), markerIndex, markerColumn, inParagraph); if (listData == null) { return BlockStart.none(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java index 8962ec7da..89328ef2a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ParagraphParser.java @@ -5,6 +5,8 @@ import org.commonmark.node.Paragraph; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.SourceLine; +import org.commonmark.parser.SourceLines; import org.commonmark.parser.block.AbstractBlockParser; import org.commonmark.parser.block.BlockContinue; import org.commonmark.parser.block.ParserState; @@ -36,7 +38,7 @@ public BlockContinue tryContinue(ParserState state) { } @Override - public void addLine(CharSequence line) { + public void addLine(SourceLine line) { linkReferenceDefinitionParser.parse(line); } @@ -58,13 +60,13 @@ public void closeBlock() { @Override public void parseInlines(InlineParser inlineParser) { - List<CharSequence> lines = linkReferenceDefinitionParser.getParagraphLines(); + SourceLines lines = linkReferenceDefinitionParser.getParagraphLines(); if (!lines.isEmpty()) { inlineParser.parse(lines, block); } } - public List<CharSequence> getParagraphLines() { + public SourceLines getParagraphLines() { return linkReferenceDefinitionParser.getParagraphLines(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/SourceSpans.java b/commonmark/src/main/java/org/commonmark/internal/SourceSpans.java deleted file mode 100644 index bf8715be8..000000000 --- a/commonmark/src/main/java/org/commonmark/internal/SourceSpans.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.commonmark.internal; - -import org.commonmark.node.SourceSpan; -import org.commonmark.parser.block.ParserState; - -public class SourceSpans { - - public static SourceSpan fromState(ParserState parserState, int startIndex) { -// int length = parserState.getLine().length(); -// return SourceSpan.of(parserState.getLineIndex(), startIndex, length - startIndex); - return null; - } -} diff --git a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java index 6d9edf761..88ad41ac9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ThematicBreakParser.java @@ -27,7 +27,7 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar return BlockStart.none(); } int nextNonSpace = state.getNextNonSpaceIndex(); - CharSequence line = state.getLine(); + CharSequence line = state.getLine().getContent(); if (isThematicBreak(line, nextNonSpace)) { return BlockStart.of(new ThematicBreakParser()).atIndex(line.length()); } else { 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 7b62d1257..9b9c26f48 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -1,8 +1,8 @@ package org.commonmark.internal.inline; import org.commonmark.node.Link; -import org.commonmark.node.Node; import org.commonmark.node.Text; +import org.commonmark.parser.SourceLines; import java.util.regex.Pattern; @@ -20,19 +20,30 @@ public class AutolinkInlineParser implements InlineContentParser { @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); + Position linkStart = scanner.position(); scanner.next(); - Position start = scanner.position(); + Position textStart = scanner.position(); if (scanner.find('>') > 0) { - String text = scanner.textBetween(start, scanner.position()).toString(); + SourceLines textSource = scanner.textBetween(textStart, scanner.position()); + String content = textSource.getContent(); scanner.next(); - if (URI.matcher(text).matches()) { - Link node = new Link(text, null); - node.appendChild(new Text(text)); - return ParsedInline.of(node, scanner.position()); - } else if (EMAIL.matcher(text).matches()) { - Link node = new Link("mailto:" + text, null); - node.appendChild(new Text(text)); - return ParsedInline.of(node, scanner.position()); + Position linkEnd = scanner.position(); + + String destination = null; + if (URI.matcher(content).matches()) { + destination = content; + } else if (EMAIL.matcher(content).matches()) { + destination = "mailto:" + content; + } + + if (destination != null) { + Link link = new Link(destination, null); + // TODO: tests + link.setSourceSpans(scanner.textBetween(linkStart, linkEnd).getSourceSpans()); + Text text = new Text(content); + text.setSourceSpans(textSource.getSourceSpans()); + link.appendChild(text); + return ParsedInline.of(link, scanner.position()); } } return ParsedInline.none(); 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 f00079793..42bd0b5c6 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -2,8 +2,8 @@ import org.commonmark.internal.util.Parsing; import org.commonmark.node.Code; -import org.commonmark.node.Node; import org.commonmark.node.Text; +import org.commonmark.parser.SourceLines; /** * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks. @@ -41,7 +41,9 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { } // If we got here, we didn't find a matching closing backtick sequence. - String ticks = scanner.textBetween(start, afterOpening).toString(); - return ParsedInline.of(new Text(ticks), afterOpening); + SourceLines source = scanner.textBetween(start, afterOpening); + Text text = new Text(source.getContent()); + text.setSourceSpans(source.getSourceSpans()); + return ParsedInline.of(text, afterOpening); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 5f6b30f00..5715e2e14 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -1,8 +1,10 @@ package org.commonmark.internal.inline; import org.commonmark.internal.util.CharMatcher; +import org.commonmark.node.SourceSpan; +import org.commonmark.parser.SourceLine; +import org.commonmark.parser.SourceLines; -import java.util.Collections; import java.util.List; public class Scanner { @@ -17,7 +19,7 @@ public class Scanner { // Lines without newlines at the end. The scanner will yield `\n` between lines because they're significant for // parsing and the final output. There is no `\n` after the last line. - private final List<CharSequence> lines; + private final List<SourceLine> lines; // Which line we're at. private int lineIndex; // The index within the line. If index == length(), we pretend that there's a `\n` and only advance after we yield @@ -25,10 +27,10 @@ public class Scanner { private int index; // Current line or "" if at the end of the lines (using "" instead of null saves a null check) - private CharSequence line = ""; + private SourceLine line = SourceLine.of("", null); private int lineLength = 0; - Scanner(List<CharSequence> lines, int lineIndex, int index) { + Scanner(List<SourceLine> lines, int lineIndex, int index) { this.lines = lines; this.lineIndex = lineIndex; this.index = index; @@ -38,17 +40,13 @@ public class Scanner { } } - public static Scanner of(List<CharSequence> lines) { - return new Scanner(lines, 0, 0); - } - - public static Scanner of(CharSequence line) { - return new Scanner(Collections.singletonList(line), 0, 0); + public static Scanner of(SourceLines lines) { + return new Scanner(lines.getLines(), 0, 0); } public char peek() { if (index < lineLength) { - return line.charAt(index); + return line.getContent().charAt(index); } else { if (lineIndex < lines.size() - 1) { return '\n'; @@ -62,7 +60,7 @@ public char peek() { public char peekPrevious() { if (index > 0) { int prev = index - 1; - return line.charAt(prev); + return line.getContent().charAt(prev); } else { if (lineIndex > 0) { return '\n'; @@ -88,7 +86,7 @@ public void next() { if (lineIndex < lines.size()) { setLine(lines.get(lineIndex)); } else { - setLine(""); + setLine(SourceLine.of("", null)); } index = 0; } @@ -120,7 +118,7 @@ public boolean next(String content) { if (index < lineLength && index + content.length() <= lineLength) { // Can't use startsWith because it's not available on CharSequence for (int i = 0; i < content.length(); i++) { - if (line.charAt(index + i) != content.charAt(i)) { + if (line.getContent().charAt(index + i) != content.charAt(i)) { return false; } } @@ -211,41 +209,47 @@ public void setPosition(Position position) { // For cases where the caller appends the result to a StringBuilder, we could offer another method to avoid some // unnecessary copying. - public CharSequence textBetween(Position begin, Position end) { + // TODO: Rename + public SourceLines textBetween(Position begin, Position end) { if (begin.lineIndex == end.lineIndex) { // Shortcut for common case of text from a single line - return lines.get(begin.lineIndex).subSequence(begin.index, end.index); + SourceLine line = lines.get(begin.lineIndex); + CharSequence newContent = line.getContent().subSequence(begin.index, end.index); + SourceSpan newSourceSpan = null; + SourceSpan sourceSpan = line.getSourceSpan(); + if (sourceSpan != null) { + newSourceSpan = SourceSpan.of(sourceSpan.getLineIndex(), sourceSpan.getColumnIndex() + begin.index, newContent.length()); + } + return SourceLines.of(SourceLine.of(newContent, newSourceSpan)); } else { - StringBuilder sb = new StringBuilder(); + SourceLines sourceLines = SourceLines.empty(); - CharSequence firstLine = lines.get(begin.lineIndex); - sb.append(firstLine.subSequence(begin.index, firstLine.length())); - sb.append('\n'); + SourceLine firstLine = lines.get(begin.lineIndex); + sourceLines.addLine(firstLine.substring(begin.index, firstLine.getContent().length())); // Lines between begin and end (we are appending the full line) for (int line = begin.lineIndex + 1; line < end.lineIndex; line++) { - sb.append(lines.get(line)); - sb.append('\n'); + sourceLines.addLine(lines.get(line)); } - CharSequence lastLine = lines.get(end.lineIndex); - sb.append(lastLine.subSequence(0, end.index)); - return sb.toString(); + SourceLine lastLine = lines.get(end.lineIndex); + sourceLines.addLine(lastLine.substring(0, end.index)); + return sourceLines; } } - private void setLine(CharSequence line) { + private void setLine(SourceLine line) { this.line = line; - this.lineLength = line.length(); + this.lineLength = line.getContent().length(); } private void checkPosition(int lineIndex, int index) { if (lineIndex < 0 || lineIndex >= lines.size()) { throw new IllegalArgumentException("Line index " + lineIndex + " out of range, number of lines: " + lines.size()); } - CharSequence line = lines.get(lineIndex); - if (index < 0 || index > line.length()) { - throw new IllegalArgumentException("Index " + index + " out of range, line length: " + line.length()); + SourceLine line = lines.get(lineIndex); + if (index < 0 || index > line.getContent().length()) { + throw new IllegalArgumentException("Index " + index + " out of range, line length: " + line.getContent().length()); } } } 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 eb7dddb0d..7c44ea6fe 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -184,23 +184,6 @@ public static int skipSpaceTabBackwards(CharSequence s, int startIndex, int last return lastIndex - 1; } - public static int skipWhitespace(CharSequence s, int startIndex, int endIndex) { - for (int i = startIndex; i < endIndex; i++) { - switch (s.charAt(i)) { - case ' ': - case '\t': - case '\n': - case '\u000B': - case '\f': - case '\r': - break; - default: - return i; - } - } - return endIndex; - } - private static int findNonSpace(CharSequence s, int startIndex) { int length = s.length(); for (int i = startIndex; i < length; i++) { diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index f8a8cace7..8ef608c8c 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -128,7 +128,11 @@ public List<SourceSpan> getSourceSpans() { * @param sourceSpans the new source spans to set */ public void setSourceSpans(List<SourceSpan> sourceSpans) { - this.sourceSpans = new ArrayList<>(sourceSpans); + if (sourceSpans.isEmpty()) { + this.sourceSpans = null; + } else { + this.sourceSpans = new ArrayList<>(sourceSpans); + } } /** diff --git a/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java b/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java index d6fc459eb..4ea8b5112 100644 --- a/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java +++ b/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java @@ -13,4 +13,8 @@ public enum IncludeSourceSpans { * Include source spans on {@link org.commonmark.node.Block} nodes. */ BLOCKS, + /** + * Include source spans on block nodes and inline nodes. + */ + BLOCKS_AND_INLINES, } diff --git a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java index cbebdc25a..49043a64f 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParser.java @@ -2,16 +2,14 @@ import org.commonmark.node.Node; -import java.util.List; - /** * Parser for inline content (text, links, emphasized text, etc). */ public interface InlineParser { /** - * @param lines the content to parse as inline + * @param lines the source content to parse as inline * @param node the node to append resulting nodes to (as children) */ - void parse(List<CharSequence> lines, Node node); + void parse(SourceLines lines, Node node); } diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java new file mode 100644 index 000000000..b2b1aff8d --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java @@ -0,0 +1,47 @@ +package org.commonmark.parser; + +import org.commonmark.node.SourceSpan; + +/** + * A line or part of a line from the input source. + */ +public class SourceLine { + + private final CharSequence content; + private final SourceSpan sourceSpan; + + public static SourceLine of(CharSequence content, SourceSpan sourceSpan) { + return new SourceLine(content, sourceSpan); + } + + private SourceLine(CharSequence content, SourceSpan sourceSpan) { + if (content == null) { + throw new NullPointerException("content must not be null"); + } + this.content = content; + this.sourceSpan = sourceSpan; + } + + public CharSequence getContent() { + return content; + } + + public SourceSpan getSourceSpan() { + return sourceSpan; + } + + public SourceLine substring(int beginIndex, int endIndex) { + // TODO: Check indices + // TODO: Tests + 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); + } + } + return SourceLine.of(newContent, newSourceSpan); + } +} diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLines.java b/commonmark/src/main/java/org/commonmark/parser/SourceLines.java new file mode 100644 index 000000000..23f2edb96 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLines.java @@ -0,0 +1,65 @@ +package org.commonmark.parser; + +import org.commonmark.node.SourceSpan; + +import java.util.ArrayList; +import java.util.List; + +/** + * A set of lines ({@link SourceLine}) from the input source. + */ +public class SourceLines { + + private final List<SourceLine> lines = new ArrayList<>(); + + public static SourceLines empty() { + return new SourceLines(); + } + + public static SourceLines of(SourceLine sourceLine) { + SourceLines sourceLines = new SourceLines(); + sourceLines.addLine(sourceLine); + return sourceLines; + } + + public static SourceLines of(List<SourceLine> sourceLines) { + SourceLines result = new SourceLines(); + result.lines.addAll(sourceLines); + return result; + } + + public void addLine(SourceLine sourceLine) { + lines.add(sourceLine); + } + + public List<SourceLine> getLines() { + return lines; + } + + public boolean isEmpty() { + // TODO: What if there's a single empty line? + return lines.isEmpty(); + } + + public String getContent() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < lines.size(); i++) { + if (i != 0) { + sb.append('\n'); + } + sb.append(lines.get(i).getContent()); + } + return sb.toString(); + } + + public List<SourceSpan> getSourceSpans() { + List<SourceSpan> sourceSpans = new ArrayList<>(); + for (SourceLine line : lines) { + SourceSpan sourceSpan = line.getSourceSpan(); + if (sourceSpan != null) { + sourceSpans.add(sourceSpan); + } + } + return sourceSpans; + } +} 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 98fddd458..3d4cbb77b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/AbstractBlockParser.java @@ -3,6 +3,7 @@ import org.commonmark.node.Block; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.SourceLine; public abstract class AbstractBlockParser implements BlockParser { @@ -22,7 +23,7 @@ public boolean canContain(Block childBlock) { } @Override - public void addLine(CharSequence line) { + public void addLine(SourceLine line) { } @Override 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 ca603ac8f..bdaaf0e47 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java @@ -3,6 +3,7 @@ import org.commonmark.node.Block; import org.commonmark.node.SourceSpan; import org.commonmark.parser.InlineParser; +import org.commonmark.parser.SourceLine; /** * Parser for a specific block node. @@ -22,7 +23,7 @@ public interface BlockParser { * Lazy continuation lines are lines that were rejected by this {@link #tryContinue(ParserState)} but didn't match * any other block parsers either. * <p> - * If true is returned here, those lines will get added via {@link #addLine(CharSequence)}. For false, the block is + * If true is returned here, those lines will get added via {@link #addLine(SourceLine)}. For false, the block is * closed instead. */ boolean canHaveLazyContinuationLines(); @@ -33,7 +34,7 @@ public interface BlockParser { BlockContinue tryContinue(ParserState parserState); - void addLine(CharSequence line); + void addLine(SourceLine line); /** * Add a source span of the currently parsed block. The default implementation in {@link AbstractBlockParser} adds 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 41ba2712b..1f2bcfb2a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/MatchedBlockParser.java @@ -1,6 +1,6 @@ package org.commonmark.parser.block; -import java.util.List; +import org.commonmark.parser.SourceLines; /** * Open block parser that was last matched during the continue phase. This is different from the currently active @@ -16,6 +16,6 @@ public interface MatchedBlockParser { * * @return paragraph content or an empty list */ - List<CharSequence> getParagraphLines(); + SourceLines getParagraphLines(); } diff --git a/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java b/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java index 8c63e964e..67bb6b608 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java @@ -1,5 +1,7 @@ package org.commonmark.parser.block; +import org.commonmark.parser.SourceLine; + /** * State of the parser that is used in block parsers. * <p><em>This interface is not intended to be implemented by clients.</em></p> @@ -9,7 +11,7 @@ public interface ParserState { /** * @return the current line */ - CharSequence getLine(); + SourceLine getLine(); /** * @return the current index within the line (0-based) diff --git a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java index 3b9ae83dc..d9a67b6fa 100644 --- a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java @@ -2,13 +2,14 @@ import org.commonmark.internal.LinkReferenceDefinitionParser.State; import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.parser.SourceLine; import org.junit.Test; import static org.junit.Assert.assertEquals; public class LinkReferenceDefinitionParserTest { - private LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); + private final LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); @Test public void testStartLabel() { @@ -20,8 +21,8 @@ public void testStartNoLabel() { // Not a label assertParagraph("a"); // Can not go back to parsing link reference definitions - parser.parse("a"); - parser.parse("["); + parse("a"); + parse("["); assertEquals(State.PARAGRAPH, parser.getState()); assertParagraphLines("a\n[", parser); } @@ -51,39 +52,36 @@ public void testLabelInvalid() { @Test public void testLabelMultiline() { - LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - parser.parse("[two"); + parse("[two"); assertEquals(State.LABEL, parser.getState()); - parser.parse("lines]:"); + parse("lines]:"); assertEquals(State.DESTINATION, parser.getState()); - parser.parse("/url"); + parse("/url"); assertEquals(State.START_TITLE, parser.getState()); assertDef(parser.getDefinitions().get(0), "two lines", "/url", null); } @Test public void testLabelStartsWithNewline() { - LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - parser.parse("["); + parse("["); assertEquals(State.LABEL, parser.getState()); - parser.parse("weird]:"); + parse("weird]:"); assertEquals(State.DESTINATION, parser.getState()); - parser.parse("/url"); + parse("/url"); assertEquals(State.START_TITLE, parser.getState()); assertDef(parser.getDefinitions().get(0), "weird", "/url", null); } @Test public void testDestination() { - LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - parser.parse("[foo]: /url"); + parse("[foo]: /url"); assertEquals(State.START_TITLE, parser.getState()); assertParagraphLines("", parser); assertEquals(1, parser.getDefinitions().size()); assertDef(parser.getDefinitions().get(0), "foo", "/url", null); - parser.parse("[bar]: </url2>"); + parse("[bar]: </url2>"); assertDef(parser.getDefinitions().get(1), "bar", "/url2", null); } @@ -94,8 +92,7 @@ public void testDestinationInvalid() { @Test public void testTitle() { - LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - parser.parse("[foo]: /url 'title'"); + parse("[foo]: /url 'title'"); assertEquals(State.START_DEFINITION, parser.getState()); assertParagraphLines("", parser); @@ -105,12 +102,11 @@ public void testTitle() { @Test public void testTitleStartWhitespace() { - LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - parser.parse("[foo]: /url"); + parse("[foo]: /url"); assertEquals(State.START_TITLE, parser.getState()); assertParagraphLines("", parser); - parser.parse(" "); + parse(" "); assertEquals(State.START_DEFINITION, parser.getState()); assertParagraphLines(" ", parser); @@ -121,18 +117,17 @@ public void testTitleStartWhitespace() { @Test public void testTitleMultiline() { - LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - parser.parse("[foo]: /url 'two"); + parse("[foo]: /url 'two"); assertEquals(State.TITLE, parser.getState()); assertParagraphLines("[foo]: /url 'two", parser); assertEquals(0, parser.getDefinitions().size()); - parser.parse("lines"); + parse("lines"); assertEquals(State.TITLE, parser.getState()); assertParagraphLines("[foo]: /url 'two\nlines", parser); assertEquals(0, parser.getDefinitions().size()); - parser.parse("'"); + parse("'"); assertEquals(State.START_DEFINITION, parser.getState()); assertParagraphLines("", parser); @@ -142,10 +137,9 @@ public void testTitleMultiline() { @Test public void testTitleMultiline2() { - LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - parser.parse("[foo]: /url '"); + parse("[foo]: /url '"); assertEquals(State.TITLE, parser.getState()); - parser.parse("title'"); + parse("title'"); assertEquals(State.START_DEFINITION, parser.getState()); assertDef(parser.getDefinitions().get(0), "foo", "/url", "\ntitle"); @@ -158,13 +152,18 @@ public void testTitleInvalid() { assertParagraph("[foo]: /url 'title' INVALID"); } + private void parse(String content) { + parser.parse(SourceLine.of(content, null)); + } + private static void assertParagraph(String input) { assertState(input, State.PARAGRAPH, input); } private static void assertState(String input, State state, String paragraphContent) { LinkReferenceDefinitionParser parser = new LinkReferenceDefinitionParser(); - parser.parse(input); + // TODO: Should we check things with source spans here? + parser.parse(SourceLine.of(input, null)); assertEquals(state, parser.getState()); assertParagraphLines(paragraphContent, parser); } @@ -176,14 +175,7 @@ private static void assertDef(LinkReferenceDefinition def, String label, String } private static void assertParagraphLines(String expectedContent, LinkReferenceDefinitionParser parser) { - StringBuilder sb = new StringBuilder(); - for (CharSequence line : parser.getParagraphLines()) { - if (sb.length() != 0) { - sb.append('\n'); - } - sb.append(line); - } - String actual = sb.toString(); + String actual = parser.getParagraphLines().getContent(); assertEquals(expectedContent, actual); } } diff --git a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java index c68140a71..e7728c000 100644 --- a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java @@ -1,5 +1,8 @@ package org.commonmark.internal.inline; +import org.commonmark.node.SourceSpan; +import org.commonmark.parser.SourceLine; +import org.commonmark.parser.SourceLines; import org.junit.Test; import java.util.Arrays; @@ -11,7 +14,9 @@ public class ScannerTest { @Test public void testNext() { - Scanner scanner = new Scanner(Collections.<CharSequence>singletonList("foo bar"), 0, 4); + Scanner scanner = new Scanner(Collections.singletonList( + SourceLine.of("foo bar", null)), + 0, 4); assertEquals('b', scanner.peek()); scanner.next(); assertEquals('a', scanner.peek()); @@ -23,7 +28,10 @@ public void testNext() { @Test public void testMultipleLines() { - Scanner scanner = new Scanner(Arrays.<CharSequence>asList("ab", "cde"), 0, 0); + Scanner scanner = new Scanner(Arrays.asList( + SourceLine.of("ab", null), + SourceLine.of("cde", null)), + 0, 0); assertTrue(scanner.hasNext()); assertEquals('\0', scanner.peekPrevious()); assertEquals('a', scanner.peek()); @@ -59,30 +67,73 @@ public void testMultipleLines() { assertEquals('\0', scanner.peek()); } + @Test public void testTextBetween() { - Scanner scanner = new Scanner(Arrays.<CharSequence>asList("ab", "cde"), 0, 0); + Scanner scanner = new Scanner(Arrays.asList( + SourceLine.of("ab", SourceSpan.of(10, 3, 2)), + SourceLine.of("cde", SourceSpan.of(11, 4, 3))), + 0, 0); + Position start = scanner.position(); + scanner.next(); - assertEquals("a", scanner.textBetween(start, scanner.position())); + assertSourceLines(scanner.textBetween(start, scanner.position()), + "a", + SourceSpan.of(10, 3, 1)); + Position afterA = scanner.position(); + scanner.next(); - assertEquals("ab", scanner.textBetween(start, scanner.position())); + assertSourceLines(scanner.textBetween(start, scanner.position()), + "ab", + SourceSpan.of(10, 3, 2)); + + Position afterB = scanner.position(); + scanner.next(); - assertEquals("ab\n", scanner.textBetween(start, scanner.position())); + assertSourceLines(scanner.textBetween(start, scanner.position()), + "ab\n", + SourceSpan.of(10, 3, 2)); + scanner.next(); - assertEquals("ab\nc", scanner.textBetween(start, scanner.position())); + assertSourceLines(scanner.textBetween(start, scanner.position()), + "ab\nc", + SourceSpan.of(10, 3, 2), + SourceSpan.of(11, 4, 1)); + scanner.next(); - assertEquals("ab\ncd", scanner.textBetween(start, scanner.position())); + assertSourceLines(scanner.textBetween(start, scanner.position()), + "ab\ncd", + SourceSpan.of(10, 3, 2), + SourceSpan.of(11, 4, 2)); + scanner.next(); - assertEquals("ab\ncde", scanner.textBetween(start, scanner.position())); + assertSourceLines(scanner.textBetween(start, scanner.position()), + "ab\ncde", + SourceSpan.of(10, 3, 2), + SourceSpan.of(11, 4, 3)); + + assertSourceLines(scanner.textBetween(afterA, scanner.position()), + "b\ncde", + SourceSpan.of(10, 4, 1), + SourceSpan.of(11, 4, 3)); + + assertSourceLines(scanner.textBetween(afterB, scanner.position()), + "\ncde", + SourceSpan.of(11, 4, 3)); + } - assertEquals("b\ncde", scanner.textBetween(afterA, scanner.position())); + private void assertSourceLines(SourceLines sourceLines, String expectedContent, SourceSpan... expectedSourceSpans) { + assertEquals(expectedContent, sourceLines.getContent()); + assertEquals(Arrays.asList(expectedSourceSpans), sourceLines.getSourceSpans()); } @Test public void nextString() { - Scanner scanner = Scanner.of(Arrays.<CharSequence>asList("hey ya", "hi")); + Scanner scanner = Scanner.of(SourceLines.of(Arrays.asList( + SourceLine.of("hey ya", null), + SourceLine.of("hi", null)))); assertFalse(scanner.next("hoy")); assertTrue(scanner.next("hey")); assertTrue(scanner.next(' ')); diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 216bdeefe..b3a8b2d3d 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -1,10 +1,7 @@ package org.commonmark.test; import org.commonmark.node.*; -import org.commonmark.parser.InlineParser; -import org.commonmark.parser.InlineParserContext; -import org.commonmark.parser.InlineParserFactory; -import org.commonmark.parser.Parser; +import org.commonmark.parser.*; import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.TestResources; @@ -103,7 +100,7 @@ public void indentation() { public void inlineParser() { final InlineParser fakeInlineParser = new InlineParser() { @Override - public void parse(List<CharSequence> lines, Node node) { + public void parse(SourceLines lines, Node node) { node.appendChild(new ThematicBreak()); } }; From 6f060a02fe020e6d135b6576f5a7d6f0bbfce860 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 11 Sep 2020 21:35:37 +1000 Subject: [PATCH 446/815] Tests passing :). Still need to write tests for the various inlines, fix TODOs --- .../gfm/tables/internal/TableBlockParser.java | 90 ++++++++----------- .../commonmark/ext/gfm/tables/TablesTest.java | 10 ++- .../internal/YamlFrontMatterBlockParser.java | 4 +- .../commonmark/internal/DocumentParser.java | 1 + .../internal/FencedCodeBlockParser.java | 4 +- .../commonmark/internal/HeadingParser.java | 12 +-- .../commonmark/internal/InlineParserImpl.java | 10 ++- .../LinkReferenceDefinitionParser.java | 6 +- .../internal/inline/AutolinkInlineParser.java | 4 - .../inline/BackticksInlineParser.java | 3 +- .../internal/inline/EntityInlineParser.java | 2 +- .../internal/inline/HtmlInlineParser.java | 2 +- .../commonmark/parser/block/ParserState.java | 2 +- .../java/org/commonmark/test/ParserTest.java | 2 +- 14 files changed, 72 insertions(+), 80 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index 59eb89883..df577e9f4 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,24 +1,25 @@ 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; import org.commonmark.parser.InlineParser; import org.commonmark.parser.SourceLine; +import org.commonmark.parser.SourceLines; import org.commonmark.parser.block.*; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class TableBlockParser extends AbstractBlockParser { private final TableBlock block = new TableBlock(); - private final List<CharSequence> rowLines = new ArrayList<>(); + private final List<SourceLine> rowLines = new ArrayList<>(); private final List<TableCell.Alignment> columns; - private TableBlockParser(List<TableCell.Alignment> columns, CharSequence headerLine) { + private TableBlockParser(List<TableCell.Alignment> columns, SourceLine headerLine) { this.columns = columns; this.rowLines.add(headerLine); } @@ -35,7 +36,7 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState state) { - if (state.getLine().toString().contains("|")) { + if (Parsing.find('|', state.getLine().getContent(), 0) != -1) { return BlockContinue.atIndex(state.getIndex()); } else { return BlockContinue.none(); @@ -44,8 +45,7 @@ public BlockContinue tryContinue(ParserState state) { @Override public void addLine(SourceLine line) { - // TODO: Need to preserve these for inlines - rowLines.add(line.getContent()); + rowLines.add(line); } @Override @@ -63,10 +63,10 @@ public void parseInlines(InlineParser inlineParser) { headerRow.setSourceSpans(head.getSourceSpans()); head.appendChild(headerRow); - List<CellSource> headerCells = split(rowLines.get(0), headerSourceSpan); + List<SourceLine> headerCells = split(rowLines.get(0)); int headerColumns = headerCells.size(); for (int i = 0; i < headerColumns; i++) { - CellSource cell = headerCells.get(i); + SourceLine cell = headerCells.get(i); TableCell tableCell = parseCell(cell, i, inlineParser); tableCell.setHeader(true); headerRow.appendChild(tableCell); @@ -75,15 +75,17 @@ public void parseInlines(InlineParser inlineParser) { TableBody body = null; // Body starts at index 2. 0 is header, 1 is separator. for (int rowIndex = 2; rowIndex < rowLines.size(); rowIndex++) { - CharSequence rowLine = rowLines.get(rowIndex); + SourceLine rowLine = rowLines.get(rowIndex); SourceSpan sourceSpan = rowIndex < sourceSpans.size() ? sourceSpans.get(rowIndex) : null; - List<CellSource> cells = split(rowLine, sourceSpan); + List<SourceLine> cells = split(rowLine); TableRow row = new TableRow(); - row.addSourceSpan(sourceSpan); + if (sourceSpan != null) { + row.addSourceSpan(sourceSpan); + } // Body can not have more columns than head for (int i = 0; i < headerColumns; i++) { - CellSource cell = i < cells.size() ? cells.get(i) : new CellSource("", null); + SourceLine cell = i < cells.size() ? cells.get(i) : SourceLine.of("", null); TableCell tableCell = parseCell(cell, i, inlineParser); row.appendChild(tableCell); } @@ -98,27 +100,29 @@ public void parseInlines(InlineParser inlineParser) { } } - private TableCell parseCell(CellSource cell, int column, InlineParser inlineParser) { + private TableCell parseCell(SourceLine cell, int column, InlineParser inlineParser) { TableCell tableCell = new TableCell(); + SourceSpan sourceSpan = cell.getSourceSpan(); + if (sourceSpan != null) { + tableCell.addSourceSpan(sourceSpan); + } if (column < columns.size()) { tableCell.setAlignment(columns.get(column)); } - if (cell.sourceSpan != null) { - tableCell.setSourceSpans(Collections.singletonList(cell.sourceSpan)); - } - - // TODO: Need to fix - SourceLine line = SourceLine.of(cell.content.trim(), null); - inlineParser.parse(Collections.singletonList(line), tableCell); + CharSequence content = cell.getContent(); + int start = Parsing.skipSpaceTab(content, 0, content.length()); + int end = Parsing.skipSpaceTabBackwards(content, content.length() - 1, start); + inlineParser.parse(SourceLines.of(cell.substring(start, end + 1)), tableCell); return tableCell; } - private static List<CellSource> split(CharSequence row, SourceSpan rowSourceSpan) { + private static List<SourceLine> split(SourceLine line) { + CharSequence row = line.getContent(); int cellStart = row.charAt(0) == '|' ? 1 : 0; - List<CellSource> cells = new ArrayList<>(); + List<SourceLine> cells = new ArrayList<>(); StringBuilder sb = new StringBuilder(); for (int i = cellStart; i < row.length(); i++) { char c = row.charAt(i); @@ -137,7 +141,8 @@ private static List<CellSource> split(CharSequence row, SourceSpan rowSourceSpan break; case '|': String content = sb.toString(); - cells.add(CellSource.of(content, rowSourceSpan, cellStart)); + + cells.add(SourceLine.of(content, line.substring(cellStart, i).getSourceSpan())); sb.setLength(0); // + 1 to skip the pipe itself for the next cell's span cellStart = i + 1; @@ -148,7 +153,7 @@ private static List<CellSource> split(CharSequence row, SourceSpan rowSourceSpan } if (sb.length() > 0) { String content = sb.toString(); - cells.add(CellSource.of(content, rowSourceSpan, cellStart)); + cells.add(SourceLine.of(content, line.substring(cellStart, line.getContent().length()).getSourceSpan())); } return cells; } @@ -240,14 +245,14 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - CharSequence line = state.getLine(); - List<CharSequence> paragraphLines = matchedBlockParser.getParagraphLines(); - if (paragraphLines.size() == 1 && paragraphLines.get(0).toString().contains("|")) { - CharSequence separatorLine = line.subSequence(state.getIndex(), line.length()); - List<TableCell.Alignment> columns = parseSeparator(separatorLine); + List<SourceLine> paragraphLines = matchedBlockParser.getParagraphLines().getLines(); + 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<TableCell.Alignment> columns = parseSeparator(separatorLine.getContent()); if (columns != null && !columns.isEmpty()) { - CharSequence paragraph = paragraphLines.get(0); - List<CellSource> headerCells = split(paragraph, null); + SourceLine paragraph = paragraphLines.get(0); + List<SourceLine> headerCells = split(paragraph); if (columns.size() >= headerCells.size()) { return BlockStart.of(new TableBlockParser(columns, paragraph)) .atIndex(state.getIndex()) @@ -258,27 +263,4 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar return BlockStart.none(); } } - - private static class CellSource { - - private final String content; - private final SourceSpan sourceSpan; - - public static CellSource of(String content, SourceSpan rowSourceSpan, int offset) { - if (!content.isEmpty()) { - SourceSpan sourceSpan = null; - if (rowSourceSpan != null) { - sourceSpan = SourceSpan.of(rowSourceSpan.getLineIndex(), rowSourceSpan.getColumnIndex() + offset, content.length()); - } - return new CellSource(content, sourceSpan); - } else { - return new CellSource(content, null); - } - } - - private CellSource(String content, SourceSpan sourceSpan) { - this.content = content; - this.sourceSpan = sourceSpan; - } - } } 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 8af4a0ec7..20fa1f70e 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 @@ -3,8 +3,8 @@ import org.commonmark.Extension; import org.commonmark.node.Node; import org.commonmark.node.SourceSpan; -import org.commonmark.parser.Parser; import org.commonmark.parser.IncludeSourceSpans; +import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.AttributeProviderContext; import org.commonmark.renderer.html.AttributeProviderFactory; @@ -651,7 +651,7 @@ public void setAttributes(Node node, String tagName, Map<String, String> attribu public void sourceSpans() { Parser parser = Parser.builder() .extensions(EXTENSIONS) - .includeSourceSpans(IncludeSourceSpans.BLOCKS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) .build(); Node document = parser.parse("Abc|Def\n---|---\n|1|2\n 3|four|\n|||\n"); @@ -668,7 +668,9 @@ public void sourceSpans() { 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()); 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()); @@ -678,14 +680,18 @@ public void sourceSpans() { 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()); TableRow bodyRow2 = (TableRow) body.getFirstChild().getNext(); assertEquals(Arrays.asList(SourceSpan.of(3, 0, 8)), bodyRow2.getSourceSpans()); TableCell bodyRow2Cell1 = (TableCell) bodyRow2.getFirstChild(); TableCell bodyRow2Cell2 = (TableCell) bodyRow2.getLastChild(); assertEquals(Arrays.asList(SourceSpan.of(3, 0, 2)), 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()); TableRow bodyRow3 = (TableRow) body.getLastChild(); assertEquals(Arrays.asList(SourceSpan.of(4, 0, 3)), bodyRow3.getSourceSpans()); 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 86d827876..da56801e0 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 @@ -43,7 +43,7 @@ public void addLine(SourceLine line) { @Override public BlockContinue tryContinue(ParserState parserState) { - final CharSequence line = parserState.getLine(); + final CharSequence line = parserState.getLine().getContent(); if (REGEX_END.matcher(line).matches()) { if (currentKey != null) { @@ -96,7 +96,7 @@ public void parseInlines(InlineParser inlineParser) { public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - CharSequence line = state.getLine(); + 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 && diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index c07d7ba78..1211f2230 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -142,6 +142,7 @@ public Document parse(Reader input) throws IOException { public SourceLine getLine() { SourceSpan sourceSpan = null; if (includeSourceSpans != IncludeSourceSpans.NONE) { + // TODO: is this wrong? Should be 0 shouldn't it? sourceSpan = SourceSpan.of(lineIndex, index, line.length()); } return SourceLine.of(line, sourceSpan); diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index 86eec3675..536d8beff 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -50,9 +50,9 @@ public BlockContinue tryContinue(ParserState state) { @Override public void addLine(SourceLine line) { if (firstLine == null) { - firstLine = line.toString(); + firstLine = line.getContent().toString(); } else { - otherLines.append(line); + otherLines.append(line.getContent()); otherLines.append('\n'); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index a7d856790..9e8c54075 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -45,12 +45,12 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar } SourceLine line = state.getLine(); - HeadingParser atxHeading = getAtxHeading(line); + int nextNonSpace = state.getNextNonSpaceIndex(); + HeadingParser atxHeading = getAtxHeading(line.substring(nextNonSpace, line.getContent().length())); if (atxHeading != null) { return BlockStart.of(atxHeading).atIndex(line.getContent().length()); } - int nextNonSpace = state.getNextNonSpaceIndex(); int setextHeadingLevel = getSetextHeadingLevel(line.getContent(), nextNonSpace); if (setextHeadingLevel > 0) { SourceLines paragraph = matchedBlockParser.getParagraphLines(); @@ -71,7 +71,6 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar // must be preceded by a space and may be followed by spaces only. private static HeadingParser getAtxHeading(SourceLine line) { Scanner scanner = Scanner.of(SourceLines.of(line)); - scanner.whitespace(); int level = scanner.matchMultiple('#'); if (level == 0 || level > 6) { @@ -99,9 +98,12 @@ private static HeadingParser getAtxHeading(SourceLine line) { case '#': if (hashCanEnd) { scanner.matchMultiple('#'); - scanner.whitespace(); + int whitespace = scanner.whitespace(); // If there's other characters, the hashes and spaces were part of the heading - hashCanEnd = false; + if (scanner.hasNext()) { + end = scanner.position(); + } + hashCanEnd = whitespace > 0; } else { scanner.next(); end = scanner.position(); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index e4cd1e737..2cc793698 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -29,6 +29,7 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private final Map<Character, List<InlineContentParser>> inlineParsers; private Scanner scanner; + private boolean includeSourceSpans; private int trailingSpaces; /** @@ -144,6 +145,7 @@ public void parse(SourceLines lines, Node block) { void reset(SourceLines lines) { this.scanner = Scanner.of(lines); + this.includeSourceSpans = !lines.getSourceSpans().isEmpty(); this.trailingSpaces = 0; this.lastDelimiter = null; this.lastBracket = null; @@ -179,9 +181,13 @@ private Node parseInline() { ParsedInline parsedInline = inlineParser.tryParse(this); if (parsedInline instanceof ParsedInlineImpl) { ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; - // TODO: Should we set source spans here? Or let the inline parsers set it? + Node node = parsedInlineImpl.getNode(); scanner.setPosition(parsedInlineImpl.getPosition()); - return parsedInlineImpl.getNode(); + // TODO: Should we set source spans here? Or let the inline parsers set it? + if (includeSourceSpans) { + node.setSourceSpans(scanner.textBetween(position, scanner.position()).getSourceSpans()); + } + return node; } else { // Reset position scanner.setPosition(position); diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 38f5cc4a9..6178d8b64 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -121,7 +121,7 @@ private boolean label(Scanner scanner) { return false; } - label.append(scanner.textBetween(start, scanner.position())); + label.append(scanner.textBetween(start, scanner.position()).getContent()); if (!scanner.hasNext()) { // label might continue on next line @@ -160,7 +160,7 @@ private boolean destination(Scanner scanner) { return false; } - String rawDestination = scanner.textBetween(start, scanner.position()).toString(); + String rawDestination = scanner.textBetween(start, scanner.position()).getContent(); destination = rawDestination.startsWith("<") ? rawDestination.substring(1, rawDestination.length() - 1) : rawDestination; @@ -221,7 +221,7 @@ private boolean title(Scanner scanner) { return false; } - title.append(scanner.textBetween(start, scanner.position())); + title.append(scanner.textBetween(start, scanner.position()).getContent()); if (!scanner.hasNext()) { // Title ran until the end of line, so continue on next line (until we find the delimiter) 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 9b9c26f48..3f0ba871e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -20,14 +20,12 @@ public class AutolinkInlineParser implements InlineContentParser { @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); - Position linkStart = scanner.position(); scanner.next(); Position textStart = scanner.position(); if (scanner.find('>') > 0) { SourceLines textSource = scanner.textBetween(textStart, scanner.position()); String content = textSource.getContent(); scanner.next(); - Position linkEnd = scanner.position(); String destination = null; if (URI.matcher(content).matches()) { @@ -38,8 +36,6 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { if (destination != null) { Link link = new Link(destination, null); - // TODO: tests - link.setSourceSpans(scanner.textBetween(linkStart, linkEnd).getSourceSpans()); Text text = new Text(content); text.setSourceSpans(textSource.getSourceSpans()); link.appendChild(text); 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 42bd0b5c6..07e35e6bc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -23,7 +23,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { if (count == openingTicks) { Code node = new Code(); - String content = scanner.textBetween(afterOpening, beforeClosing).toString(); + String content = scanner.textBetween(afterOpening, beforeClosing).getContent(); content = content.replace('\n', ' '); // spec: If the resulting string both begins and ends with a space character, but does not consist @@ -43,7 +43,6 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { // If we got here, we didn't find a matching closing backtick sequence. SourceLines source = scanner.textBetween(start, afterOpening); Text text = new Text(source.getContent()); - text.setSourceSpans(source.getSourceSpans()); return ParsedInline.of(text, afterOpening); } } 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 f0c330002..dce5438e9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -48,7 +48,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { } private ParsedInline entity(Scanner scanner, Position start) { - String text = scanner.textBetween(start, scanner.position()).toString(); + String text = scanner.textBetween(start, scanner.position()).getContent(); return ParsedInline.of(new Text(Html5Entities.entityToString(text)), scanner.position()); } } 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 1fe20ecb8..a60b0d7a8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -69,7 +69,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { } private static ParsedInline htmlInline(Position start, Scanner scanner) { - String text = scanner.textBetween(start, scanner.position()).toString(); + String text = scanner.textBetween(start, scanner.position()).getContent(); HtmlInline node = new HtmlInline(); node.setLiteral(text); return ParsedInline.of(node, scanner.position()); diff --git a/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java b/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java index 67bb6b608..7ec694238 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java @@ -9,7 +9,7 @@ public interface ParserState { /** - * @return the current line + * @return the current line being parsed (full line) */ SourceLine getLine(); diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index b3a8b2d3d..e486750aa 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -176,7 +176,7 @@ private static class DashBlockParserFactory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { - if (state.getLine().equals("---")) { + if (state.getLine().getContent().equals("---")) { return BlockStart.of(new DashBlockParser()); } return BlockStart.none(); From 988526909a6bd75220e1c9d063a0fac60dfe92c6 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 14 Sep 2020 14:11:06 +1000 Subject: [PATCH 447/815] WIP inline source spans tests --- .../java/org/commonmark/internal/Bracket.java | 21 ++- .../commonmark/internal/InlineParserImpl.java | 45 +++++-- .../org/commonmark/test/SourceSpansTest.java | 124 +++++++++++++++--- .../org/commonmark/test/SpecCoreTest.java | 2 +- 4 files changed, 156 insertions(+), 36 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/Bracket.java b/commonmark/src/main/java/org/commonmark/internal/Bracket.java index f66a79279..46296262f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Bracket.java +++ b/commonmark/src/main/java/org/commonmark/internal/Bracket.java @@ -9,10 +9,20 @@ public class Bracket { public final Text node; + + /** + * The position of the marker for the bracket (<code>[</code> or <code>![</code>) + */ + public final Position markerPosition; + /** * The position of the content (after the opening bracket) */ public final Position contentPosition; + + /** + * Whether this is an image or link. + */ public final boolean image; /** @@ -35,16 +45,17 @@ public class Bracket { */ public boolean bracketAfter = false; - static public Bracket link(Text node, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { - return new Bracket(node, contentPosition, previous, previousDelimiter, 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 image(Text node, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { - return new Bracket(node, contentPosition, previous, previousDelimiter, true); + static public Bracket image(Text node, Position markerPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter) { + return new Bracket(node, markerPosition, contentPosition, previous, previousDelimiter, true); } - private Bracket(Text node, Position contentPosition, Bracket previous, Delimiter previousDelimiter, boolean image) { + private Bracket(Text node, Position markerPosition, Position contentPosition, Bracket previous, Delimiter previousDelimiter, boolean image) { this.node = node; + this.markerPosition = markerPosition; 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 2cc793698..12eebc03f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -183,8 +183,7 @@ private Node parseInline() { ParsedInlineImpl parsedInlineImpl = (ParsedInlineImpl) parsedInline; Node node = parsedInlineImpl.getNode(); scanner.setPosition(parsedInlineImpl.getPosition()); - // TODO: Should we set source spans here? Or let the inline parsers set it? - if (includeSourceSpans) { + if (includeSourceSpans && node.getSourceSpans().isEmpty()) { node.setSourceSpans(scanner.textBetween(position, scanner.position()).getSourceSpans()); } return node; @@ -252,7 +251,7 @@ private Node parseOpenBracket() { Text node = text(scanner.textBetween(start, contentPosition)); // Add entry to stack for this opener - addBracket(Bracket.link(node, contentPosition, lastBracket, lastDelimiter)); + addBracket(Bracket.link(node, start, contentPosition, lastBracket, lastDelimiter)); return node; } @@ -265,10 +264,11 @@ private Node parseBang() { Position start = scanner.position(); scanner.next(); if (scanner.next('[')) { - Text node = text(scanner.textBetween(start, scanner.position())); + Position contentPosition = scanner.position(); + Text node = text(scanner.textBetween(start, contentPosition)); // Add entry to stack for this opener - addBracket(Bracket.image(node, scanner.position(), lastBracket, lastDelimiter)); + addBracket(Bracket.image(node, start, contentPosition, lastBracket, lastDelimiter)); return node; } else { return text(scanner.textBetween(start, scanner.position())); @@ -351,9 +351,10 @@ private Node parseCloseBracket() { } if (dest != null) { - // If we got here, open is a potential opener + // If we got here, we have a link or image Node linkOrImage = opener.image ? new Image(dest, title) : new Link(dest, title); + // 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(); @@ -361,7 +362,9 @@ private Node parseCloseBracket() { node = next; } - // TODO: source positions + if (includeSourceSpans) { + linkOrImage.setSourceSpans(scanner.textBetween(opener.markerPosition, scanner.position()).getSourceSpans()); + } // Process delimiters such as emphasis inside link/image processDelimiters(opener.previousDelimiter); @@ -507,7 +510,6 @@ private Node parseText() { } Text text = new Text(content); - // TODO: Test text.setSourceSpans(source.getSourceSpans()); return text; } @@ -621,6 +623,7 @@ private void processDelimiters(Delimiter stackBottom) { // Remove number of used delimiters from stack and inline nodes. opener.length -= useDelims; closer.length -= useDelims; + // TODO: Need to adjust source spans openerNode.setLiteral( openerNode.getLiteral().substring(0, openerNode.getLiteral().length() - useDelims)); @@ -739,18 +742,42 @@ private void mergeTextNodesInclusive(Node fromNode, Node toNode) { private void mergeIfNeeded(Text first, Text last, int textLength) { if (first != null && last != null && first != last) { StringBuilder sb = new StringBuilder(textLength); + List<SourceSpan> sourceSpans = Collections.emptyList(); + if (includeSourceSpans) { + sourceSpans = new ArrayList<>(first.getSourceSpans()); + } sb.append(first.getLiteral()); Node node = first.getNext(); Node stop = last.getNext(); while (node != stop) { sb.append(((Text) node).getLiteral()); + if (includeSourceSpans) { + mergeSourceSpans(sourceSpans, node.getSourceSpans()); + } + Node unlink = node; node = node.getNext(); unlink.unlink(); } - // TODO: Need to merge sourcespans too String literal = sb.toString(); first.setLiteral(literal); + first.setSourceSpans(sourceSpans); + } + } + + private void mergeSourceSpans(List<SourceSpan> sourceSpans, List<SourceSpan> other) { + if (sourceSpans.isEmpty()) { + sourceSpans.addAll(other); + } else if (!other.isEmpty()) { + 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())); + sourceSpans.addAll(other.subList(1, other.size())); + } else { + sourceSpans.addAll(other); + } } } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index 9fe1fac03..bd5d818ec 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -1,29 +1,20 @@ package org.commonmark.test; -import org.commonmark.node.BlockQuote; -import org.commonmark.node.FencedCodeBlock; -import org.commonmark.node.Heading; -import org.commonmark.node.HtmlBlock; -import org.commonmark.node.IndentedCodeBlock; -import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.node.ListBlock; -import org.commonmark.node.ListItem; -import org.commonmark.node.Node; -import org.commonmark.node.Paragraph; -import org.commonmark.node.SourceSpan; -import org.commonmark.node.ThematicBreak; +import org.commonmark.node.*; import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.junit.Test; +import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Deque; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; public class SourceSpansTest { private static final Parser PARSER = Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS).build(); + private static final Parser INLINES_PARSER = Parser.builder().includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES).build(); @Test public void paragraph() { @@ -194,20 +185,111 @@ public void visualCheck() { visualizeSourceSpans("> * ```\n> foo\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)); + + // 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](/url)", Text.class, SourceSpan.of(0, 1, 3)); + assertInlineSpans("*foo*", Text.class, SourceSpan.of(0, 1, 3)); + } + + @Test + public void inlineAutolink() { + assertInlineSpans("see <https://example.org>", Link.class, SourceSpan.of(0, 4, 21)); + } + + @Test + public void inlineBackslash() { + assertInlineSpans("\\!", Text.class, SourceSpan.of(0, 0, 2)); + } + + @Test + public void inlineBackticks() { + assertInlineSpans("see `code`", Code.class, SourceSpan.of(0, 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)); + } + + @Test + public void inlineEntity() { + assertInlineSpans("&", Text.class, SourceSpan.of(0, 0, 5)); + } + + @Test + public void inlineHtml() { + assertInlineSpans("hi <strong>there</strong>", HtmlInline.class, SourceSpan.of(0, 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("[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)); + } + + @Test + public void inlineEmphasis() { + assertInlineSpans("*hey*", Emphasis.class, SourceSpan.of(0, 0, 5)); + assertInlineSpans("*hey*", Text.class, SourceSpan.of(0, 1, 3)); + assertInlineSpans("**hey**", Emphasis.class, SourceSpan.of(0, 0, 7)); + assertInlineSpans("**hey**", Text.class, SourceSpan.of(0, 2, 3)); + } + + // TODO: +// @Test +// public void tabOffset() { +// assertInlineSpans(">\t*foo*"); +// } + private String visualizeSourceSpans(String source) { Node document = PARSER.parse(source); return SourceSpanRenderer.render(document, source); } + private static void assertSpans(String input, Class<? extends Node> nodeClass, SourceSpan... expectedSourceSpans) { - Node node = PARSER.parse(input); - while (node != null && !nodeClass.isInstance(node)) { - node = node.getFirstChild(); - } - if (node == null) { - fail("Expected to find " + nodeClass + " node"); - } else { - assertEquals(Arrays.asList(expectedSourceSpans), node.getSourceSpans()); + assertSpans(PARSER.parse(input), nodeClass, expectedSourceSpans); + } + + private static void assertInlineSpans(String input, Class<? extends Node> nodeClass, SourceSpan... expectedSourceSpans) { + assertSpans(INLINES_PARSER.parse(input), nodeClass, expectedSourceSpans); + } + + private static void assertSpans(Node rootNode, Class<? extends Node> nodeClass, SourceSpan... expectedSourceSpans) { + Node node = findNode(rootNode, nodeClass); + assertEquals(Arrays.asList(expectedSourceSpans), node.getSourceSpans()); + } + + private static Node findNode(Node rootNode, Class<? extends Node> nodeClass) { + Deque<Node> nodes = new ArrayDeque<>(); + nodes.add(rootNode); + while (!nodes.isEmpty()) { + Node node = nodes.removeFirst(); + if (nodeClass.isInstance(node)) { + return node; + } + if (node.getFirstChild() != null) { + nodes.addFirst(node.getFirstChild()); + } + if (node.getNext() != null) { + nodes.addLast(node.getNext()); + } } + throw new AssertionError("Expected to find " + nodeClass + " node"); } } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java index 4e416264f..e4820f09c 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecCoreTest.java @@ -1,10 +1,10 @@ package org.commonmark.test; -import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.node.AbstractVisitor; import org.commonmark.node.Node; import org.commonmark.node.Text; import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.SpecTestCase; import org.commonmark.testutil.example.Example; import org.junit.Test; From 66e15d7413623104f9a5518be3e1877e19d7616d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 09:20:01 +0000 Subject: [PATCH 448/815] Bump junit from 4.12 to 4.13.1 Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] <support@github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d81dc186a..7b80779f1 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,7 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.12</version> + <version>4.13.1</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> From e0348ad445e278374577cc3ffc4bef9509dac83d Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 28 Oct 2020 12:29:06 +1100 Subject: [PATCH 449/815] README: Fix build status badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b981e7089..b9718e9f2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Java library for parsing and rendering [Markdown] text according to the [![Maven Central status](https://img.shields.io/maven-central/v/com.atlassian.commonmark/commonmark.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.atlassian.commonmark%22) [![javadoc](https://www.javadoc.io/badge/com.atlassian.commonmark/commonmark.svg?color=blue)](https://www.javadoc.io/doc/com.atlassian.commonmark/commonmark) -[![Build status](https://travis-ci.org/atlassian/commonmark-java.svg?branch=master)](https://travis-ci.org/atlassian/commonmark-java) +[![ci](https://github.com/atlassian/commonmark-java/workflows/ci/badge.svg)](https://github.com/atlassian/commonmark-java/actions?query=workflow%3Aci) [![codecov](https://codecov.io/gh/atlassian/commonmark-java/branch/master/graph/badge.svg)](https://codecov.io/gh/atlassian/commonmark-java) [![SourceSpy Dashboard](https://sourcespy.com/shield.svg)](https://sourcespy.com/github/atlassiancommonmarkjava/) From 62c0e0ff3a21b81814a154ed45b0575a67abe711 Mon Sep 17 00:00:00 2001 From: Renovate Bot <bot@renovateapp.com> Date: Thu, 12 Nov 2020 22:34:04 +0000 Subject: [PATCH 450/815] Add renovate.json --- renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..f45d8f110 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +} From 2cebcadd10fa2f14481b22dce4611aa75ff660b6 Mon Sep 17 00:00:00 2001 From: javaor <69024684+javaor@users.noreply.github.com> Date: Wed, 18 Nov 2020 14:33:22 +0800 Subject: [PATCH 451/815] add dot match in metadata support below format: --- ms.author: <author> ms.date: yyyy/MM/dd --- --- .../ext/front/matter/internal/YamlFrontMatterBlockParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5612d9ffd..89c47548f 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 @@ -13,7 +13,7 @@ import java.util.regex.Pattern; public class YamlFrontMatterBlockParser extends AbstractBlockParser { - private static final Pattern REGEX_METADATA = Pattern.compile("^[ ]{0,3}([A-Za-z0-9_-]+):\\s*(.*)"); + private static final Pattern REGEX_METADATA = Pattern.compile("^[ ]{0,3}([A-Za-z0-9._-]+):\\s*(.*)"); private static final Pattern REGEX_METADATA_LIST = Pattern.compile("^[ ]+-\\s*(.*)"); private static final Pattern REGEX_METADATA_LITERAL = Pattern.compile("^\\s*(.*)"); private static final Pattern REGEX_BEGIN = Pattern.compile("^-{3}(\\s.*)?"); From fdc0f24cc2d91ab3535fe664ae737c7b307c95f0 Mon Sep 17 00:00:00 2001 From: javaor <69024684+javaor@users.noreply.github.com> Date: Wed, 18 Nov 2020 16:18:53 +0800 Subject: [PATCH 452/815] typo --- .../org/commonmark/renderer/text/TextContentRenderer.java | 4 ++-- 1 file changed, 2 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 d38f99972..aacfbb82a 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/text/TextContentRenderer.java @@ -100,9 +100,9 @@ public Builder nodeRendererFactory(TextContentNodeRendererFactory nodeRendererFa public Builder extensions(Iterable<? extends Extension> extensions) { for (Extension extension : extensions) { if (extension instanceof TextContentRenderer.TextContentRendererExtension) { - TextContentRenderer.TextContentRendererExtension htmlRendererExtension = + TextContentRenderer.TextContentRendererExtension textContentRendererExtension = (TextContentRenderer.TextContentRendererExtension) extension; - htmlRendererExtension.extend(this); + textContentRendererExtension.extend(this); } } return this; From 76fcc42d0019cc9aae579bda92a320db073cac27 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 27 Nov 2020 16:04:20 +1100 Subject: [PATCH 453/815] Source spans for delimiter processors Needed some changes to the DelimiterProcessor and DelimiterRun APIs, but didn't turn out too bad. --- .../StrikethroughDelimiterProcessor.java | 40 +++-- .../gfm/strikethrough/StrikethroughTest.java | 18 ++ .../ImageAttributesDelimiterProcessor.java | 100 +++++------ .../image/attributes/ImageAttributesTest.java | 27 ++- .../ins/internal/InsDelimiterProcessor.java | 40 +++-- .../java/org/commonmark/ext/ins/InsTest.java | 18 ++ .../org/commonmark/internal/Delimiter.java | 55 ++++-- .../commonmark/internal/InlineParserImpl.java | 160 +++++++----------- .../internal/StaggeredDelimiterProcessor.java | 10 +- .../inline/EmphasisDelimiterProcessor.java | 47 ++--- .../main/java/org/commonmark/node/Nodes.java | 64 +++++++ .../java/org/commonmark/node/SourceSpans.java | 51 ++++++ .../parser/delimiter/DelimiterProcessor.java | 27 +-- .../parser/delimiter/DelimiterRun.java | 32 +++- .../test/DelimiterProcessorTest.java | 43 ++--- .../org/commonmark/test/SourceSpansTest.java | 17 +- .../org/commonmark/test/SpecialInputTest.java | 5 + 17 files changed, 472 insertions(+), 282 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/node/Nodes.java create mode 100644 commonmark/src/main/java/org/commonmark/node/SourceSpans.java 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 dd881b419..7d54eedf2 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 @@ -2,6 +2,8 @@ import org.commonmark.ext.gfm.strikethrough.Strikethrough; import org.commonmark.node.Node; +import org.commonmark.node.Nodes; +import org.commonmark.node.SourceSpans; import org.commonmark.node.Text; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; @@ -24,27 +26,31 @@ public int getMinLength() { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { - if (opener.length() >= 2 && closer.length() >= 2) { + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + if (openingRun.length() >= 2 && closingRun.length() >= 2) { // Use exactly two delimiters even if we have more, and don't care about internal openers/closers. + + Text opener = openingRun.getOpener(); + + // Wrap nodes between delimiters in strikethrough. + Node strikethrough = new Strikethrough(); + + SourceSpans sourceSpans = new SourceSpans(); + sourceSpans.addAllFrom(openingRun.getOpeners(2)); + + for (Node node : Nodes.between(opener, closingRun.getCloser())) { + strikethrough.appendChild(node); + sourceSpans.addAll(node.getSourceSpans()); + } + + sourceSpans.addAllFrom(closingRun.getClosers(2)); + strikethrough.setSourceSpans(sourceSpans.getSourceSpans()); + + opener.insertAfter(strikethrough); + return 2; } else { return 0; } } - - @Override - public void process(Text opener, Text closer, int delimiterCount) { - // Wrap nodes between delimiters in strikethrough. - Node strikethrough = new Strikethrough(); - - Node tmp = opener.getNext(); - while (tmp != null && tmp != closer) { - Node next = tmp.getNext(); - strikethrough.appendChild(tmp); - tmp = next; - } - - opener.insertAfter(strikethrough); - } } diff --git a/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughTest.java index 225977854..e2e3b95c4 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 @@ -2,12 +2,16 @@ import org.commonmark.Extension; import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.SourceSpan; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; +import java.util.Arrays; import java.util.Collections; import java.util.Set; @@ -89,6 +93,20 @@ public void textContentRenderer() { assertEquals("/foo/", CONTENT_RENDERER.render(document)); } + @Test + public void sourceSpans() { + Parser parser = Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + + Node document = parser.parse("hey ~~there~~\n"); + Paragraph block = (Paragraph) document.getFirstChild(); + Node strikethrough = block.getLastChild(); + assertEquals(Arrays.asList(SourceSpan.of(0, 4, 9)), + strikethrough.getSourceSpans()); + } + @Override protected String render(String source) { return HTML_RENDERER.render(PARSER.parse(source)); 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 68e3f7847..a584948e3 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 @@ -3,6 +3,7 @@ import org.commonmark.ext.image.attributes.ImageAttributes; import org.commonmark.node.Image; import org.commonmark.node.Node; +import org.commonmark.node.Nodes; import org.commonmark.node.Text; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; @@ -31,70 +32,57 @@ public int getMinLength() { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { - return 1; - } + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + if (openingRun.length() != 1) { + return 0; + } - @Override - public void process(Text opener, Text closer, int delimiterCount) { // Check if the attributes can be applied - if the previous node is an Image, and if all the attributes are in // the set of SUPPORTED_ATTRIBUTES - if (opener.getPrevious() instanceof Image) { - boolean canApply = true; - List<Node> toUnlink = new ArrayList<>(); - - Map<String, String> attributesMap = new LinkedHashMap<>(); - Node tmp = opener.getNext(); - while (tmp != null && tmp != closer) { - Node next = tmp.getNext(); - // Only Text nodes can be used for attributes - if (tmp instanceof Text) { - String attributes = ((Text) tmp).getLiteral(); - for (String s : attributes.split("\\s+")) { - String[] attribute = s.split("="); - if (attribute.length > 1 && SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { - attributesMap.put(attribute[0], attribute[1]); - // The tmp node can be unlinked, as we have retrieved its value. - toUnlink.add(tmp); - } else { - // This attribute is not supported, so break here (no need to check any further ones). - canApply = false; - break; - } - } - } else { - // This node type is not supported, so break here (no need to check any further ones). - canApply = false; - break; - } - tmp = next; + Text opener = openingRun.getOpener(); + Node nodeToStyle = opener.getPrevious(); + if (!(nodeToStyle instanceof Image)) { + return 0; + } + + List<Node> toUnlink = new ArrayList<>(); + StringBuilder content = new StringBuilder(); + + for (Node node : Nodes.between(opener, closingRun.getCloser())) { + // Only Text nodes can be used for attributes + if (node instanceof Text) { + content.append(((Text) node).getLiteral()); + toUnlink.add(node); + } else { + // This node type is not supported, so stop here (no need to check any further ones). + return 0; } + } - // Only if all of the above checks pass can the attributes be applied. - if (canApply) { - // Unlink the tmp nodes - for (Node node : toUnlink) { - node.unlink(); - } - - if (attributesMap.size() > 0) { - ImageAttributes imageAttributes = new ImageAttributes(attributesMap); - - // The new node is added as a child of the image node to which the attributes apply. - Node nodeToStyle = opener.getPrevious(); - nodeToStyle.appendChild(imageAttributes); - } - return; + Map<String, String> attributesMap = new LinkedHashMap<>(); + String attributes = content.toString(); + for (String s : attributes.split("\\s+")) { + String[] attribute = s.split("="); + if (attribute.length > 1 && SUPPORTED_ATTRIBUTES.contains(attribute[0].toLowerCase())) { + attributesMap.put(attribute[0], attribute[1]); + } else { + // This attribute is not supported, so stop here (no need to check any further ones). + return 0; } } - // If we got here then the attributes cannot be applied, so fallback to leaving the text unchanged. - // Need to add back the opening and closing characters (which are removed elsewhere). - if (opener.getPrevious() == null) { - opener.getParent().prependChild(new Text("" + getOpeningCharacter())); - } else { - opener.getPrevious().insertAfter(new Text("" + getOpeningCharacter())); + // Unlink the tmp nodes + for (Node node : toUnlink) { + node.unlink(); + } + + if (attributesMap.size() > 0) { + ImageAttributes imageAttributes = new ImageAttributes(attributesMap); + + // The new node is added as a child of the image node to which the attributes apply. + nodeToStyle.appendChild(imageAttributes); } - closer.insertAfter(new Text("" + getClosingCharacter())); + + return 1; } } 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 206fd9f47..b7d8a84c3 100644 --- a/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java +++ b/commonmark-ext-image-attributes/src/test/java/org/commonmark/ext/image/attributes/ImageAttributesTest.java @@ -1,14 +1,21 @@ package org.commonmark.ext.image.attributes; import org.commonmark.Extension; +import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.SourceSpan; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; +import java.util.Arrays; import java.util.Collections; import java.util.Set; +import static org.junit.Assert.assertEquals; + public class ImageAttributesTest extends RenderingTestCase { private static final Set<Extension> EXTENSIONS = Collections.singleton(ImageAttributesExtension.create()); @@ -42,7 +49,7 @@ public void baseCase() { @Test public void doubleDelimiters() { assertRendering("![text](/url.png){{height=5}}", - "<p><img src=\"/url.png\" alt=\"text\" height=\"5\" /></p>\n"); + "<p><img src=\"/url.png\" alt=\"text\" />{{height=5}}</p>\n"); } @Test @@ -69,7 +76,8 @@ public void styleWithNoValueIsIgnored() { @Test public void repeatedStyleNameUsesFinalOne() { assertRendering("![text](/url.png){height=4 height=5 width=1 height=6}", - "<p><img src=\"/url.png\" alt=\"text\" height=\"6\" width=\"1\" /></p>\n"); } + "<p><img src=\"/url.png\" alt=\"text\" height=\"6\" width=\"1\" /></p>\n"); + } @Test public void styleValuesAreEscaped() { @@ -113,6 +121,21 @@ public void textNodesAreUnchanged() { assertRendering("{}", "<p>{}</p>\n"); } + @Test + public void sourceSpans() { + Parser parser = Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + + // This doesn't result in image attributes, so source spans should be for the single (merged) text node. + 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)), + text.getSourceSpans()); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); diff --git a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java index 9a4ad383c..b0bfb4c6e 100644 --- a/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java +++ b/commonmark-ext-ins/src/main/java/org/commonmark/ext/ins/internal/InsDelimiterProcessor.java @@ -2,6 +2,8 @@ import org.commonmark.ext.ins.Ins; import org.commonmark.node.Node; +import org.commonmark.node.Nodes; +import org.commonmark.node.SourceSpans; import org.commonmark.node.Text; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; @@ -24,27 +26,31 @@ public int getMinLength() { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { - if (opener.length() >= 2 && closer.length() >= 2) { + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + if (openingRun.length() >= 2 && closingRun.length() >= 2) { // Use exactly two delimiters even if we have more, and don't care about internal openers/closers. + + Text opener = openingRun.getOpener(); + + // Wrap nodes between delimiters in ins. + Node ins = new Ins(); + + SourceSpans sourceSpans = new SourceSpans(); + sourceSpans.addAllFrom(openingRun.getOpeners(2)); + + for (Node node : Nodes.between(opener, closingRun.getCloser())) { + ins.appendChild(node); + sourceSpans.addAll(node.getSourceSpans()); + } + + sourceSpans.addAllFrom(closingRun.getClosers(2)); + ins.setSourceSpans(sourceSpans.getSourceSpans()); + + opener.insertAfter(ins); + return 2; } else { return 0; } } - - @Override - public void process(Text opener, Text closer, int delimiterCount) { - // Wrap nodes between delimiters in ins. - Node ins = new Ins(); - - Node tmp = opener.getNext(); - while (tmp != null && tmp != closer) { - Node next = tmp.getNext(); - ins.appendChild(tmp); - tmp = next; - } - - opener.insertAfter(ins); - } } 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 05c8b41c5..c9591af5e 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 @@ -2,12 +2,16 @@ import org.commonmark.Extension; import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.SourceSpan; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; +import java.util.Arrays; import java.util.Collections; import java.util.Set; @@ -89,6 +93,20 @@ public void textContentRenderer() { assertEquals("foo", CONTENT_RENDERER.render(document)); } + @Test + public void sourceSpans() { + Parser parser = Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + + Node document = parser.parse("hey ++there++\n"); + Paragraph block = (Paragraph) document.getFirstChild(); + Node ins = block.getLastChild(); + assertEquals(Arrays.asList(SourceSpan.of(0, 4, 9)), + ins.getSourceSpans()); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); diff --git a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java index 5988e9508..9083ce3cb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/Delimiter.java +++ b/commonmark/src/main/java/org/commonmark/internal/Delimiter.java @@ -3,36 +3,33 @@ import org.commonmark.node.Text; import org.commonmark.parser.delimiter.DelimiterRun; +import java.util.List; + /** * Delimiter (emphasis, strong emphasis or custom emphasis). */ public class Delimiter implements DelimiterRun { - public final Text node; + public final List<Text> characters; public final char delimiterChar; + private final int originalLength; - /** - * Can open emphasis, see spec. - */ - public final boolean canOpen; + // Can open emphasis, see spec. + private final boolean canOpen; - /** - * Can close emphasis, see spec. - */ - public final boolean canClose; + // Can close emphasis, see spec. + private final boolean canClose; public Delimiter previous; public Delimiter next; - public int length = 1; - public int originalLength = 1; - - public Delimiter(Text node, char delimiterChar, boolean canOpen, boolean canClose, Delimiter previous) { - this.node = node; + public Delimiter(List<Text> characters, char delimiterChar, boolean canOpen, boolean canClose, Delimiter previous) { + this.characters = characters; this.delimiterChar = delimiterChar; this.canOpen = canOpen; this.canClose = canClose; this.previous = previous; + this.originalLength = characters.size(); } @Override @@ -47,11 +44,39 @@ public boolean canClose() { @Override public int length() { - return length; + return characters.size(); } @Override public int originalLength() { return originalLength; } + + @Override + public Text getOpener() { + return characters.get(characters.size() - 1); + } + + @Override + public Text getCloser() { + return characters.get(0); + } + + @Override + public Iterable<Text> getOpeners(int length) { + if (!(length >= 1 && length <= length())) { + throw new IllegalArgumentException("length must be between 1 and " + length() + ", was " + length); + } + + return characters.subList(characters.size() - length, characters.size()); + } + + @Override + public Iterable<Text> getClosers(int length) { + if (!(length >= 1 && length <= length())) { + throw new IllegalArgumentException("length must be between 1 and " + length() + ", was " + length); + } + + return characters.subList(0, length); + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 12eebc03f..550cdaa8a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -131,9 +131,11 @@ public void parse(SourceLines lines, Node block) { reset(lines); while (true) { - Node node = parseInline(); - if (node != null) { - block.appendChild(node); + List<? extends Node> nodes = parseInline(); + if (nodes != null) { + for (Node node : nodes) { + block.appendChild(node); + } } else { break; } @@ -151,12 +153,6 @@ void reset(SourceLines lines) { this.lastBracket = null; } - private Text text(String content, List<SourceSpan> sourceSpans) { - Text text = new Text(content); - text.setSourceSpans(sourceSpans); - return text; - } - private Text text(SourceLines sourceLines) { Text text = new Text(sourceLines.getContent()); text.setSourceSpans(sourceLines.getSourceSpans()); @@ -168,7 +164,7 @@ private Text text(SourceLines sourceLines) { * On success, return the new inline node. * On failure, return null. */ - private Node parseInline() { + private List<? extends Node> parseInline() { char c = scanner.peek(); if (c == Scanner.END) { return null; @@ -186,7 +182,7 @@ private Node parseInline() { if (includeSourceSpans && node.getSourceSpans().isEmpty()) { node.setSourceSpans(scanner.textBetween(position, scanner.position()).getSourceSpans()); } - return node; + return Collections.singletonList(node); } else { // Reset position scanner.setPosition(position); @@ -196,48 +192,46 @@ private Node parseInline() { switch (c) { case '[': - return parseOpenBracket(); + return Collections.singletonList(parseOpenBracket()); case '!': - return parseBang(); + return Collections.singletonList(parseBang()); case ']': - return parseCloseBracket(); + return Collections.singletonList(parseCloseBracket()); case '\n': - return parseLineBreak(); + return Collections.singletonList(parseLineBreak()); } boolean isDelimiter = delimiterCharacters.get(c); if (isDelimiter) { DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c); - Node delimiterNode = parseDelimiters(delimiterProcessor, c); - if (delimiterNode != null) { - return delimiterNode; + List<? extends Node> nodes = parseDelimiters(delimiterProcessor, c); + if (nodes != null) { + return nodes; } } // If we get here, even for a special/delimiter character, we will just treat it as text. - return parseText(); + return Collections.singletonList(parseText()); } /** * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters. */ - private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { + private List<? extends Node> parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar); if (res == null) { return null; } - Text node = res.text; + List<Text> characters = res.characters; // Add entry to stack for this opener - lastDelimiter = new Delimiter(node, delimiterChar, res.canOpen, res.canClose, lastDelimiter); - lastDelimiter.length = res.count; - lastDelimiter.originalLength = res.count; + lastDelimiter = new Delimiter(characters, delimiterChar, res.canOpen, res.canClose, lastDelimiter); if (lastDelimiter.previous != null) { lastDelimiter.previous.next = lastDelimiter; } - return node; + return characters; } /** @@ -524,13 +518,22 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char char charBefore = scanner.peekPrevious(); Position start = scanner.position(); + // Quick check to see if we have enough delimiters. int delimiterCount = scanner.matchMultiple(delimiterChar); - if (delimiterCount < delimiterProcessor.getMinLength()) { scanner.setPosition(start); return null; } + // We do have enough, extract a text node for each delimiter character. + List<Text> delimiters = new ArrayList<>(); + scanner.setPosition(start); + Position positionBefore = start; + while (scanner.next(delimiterChar)) { + delimiters.add(text(scanner.textBetween(positionBefore, scanner.position()))); + positionBefore = scanner.position(); + } + char charAfter = scanner.peek(); String before = charBefore == Scanner.END ? "\n" : String.valueOf(charBefore); String after = charAfter == Scanner.END ? "\n" : String.valueOf(charAfter); @@ -555,8 +558,7 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingCharacter(); } - SourceLines source = scanner.textBetween(start, scanner.position()); - return new DelimiterData(delimiterCount, canOpen, canClose, text(source.getContent(), source.getSourceSpans())); + return new DelimiterData(delimiters, canOpen, canClose); } private void processDelimiters(Delimiter stackBottom) { @@ -573,7 +575,7 @@ private void processDelimiters(Delimiter stackBottom) { char delimiterChar = closer.delimiterChar; DelimiterProcessor delimiterProcessor = delimiterProcessors.get(delimiterChar); - if (!closer.canClose || delimiterProcessor == null) { + if (!closer.canClose() || delimiterProcessor == null) { closer = closer.next; continue; } @@ -581,15 +583,15 @@ private void processDelimiters(Delimiter stackBottom) { char openingDelimiterChar = delimiterProcessor.getOpeningCharacter(); // Found delimiter closer. Now look back for first matching opener. - int useDelims = 0; + int usedDelims = 0; boolean openerFound = false; boolean potentialOpenerFound = false; Delimiter opener = closer.previous; while (opener != null && opener != stackBottom && opener != openersBottom.get(delimiterChar)) { - if (opener.canOpen && opener.delimiterChar == openingDelimiterChar) { + if (opener.canOpen() && opener.delimiterChar == openingDelimiterChar) { potentialOpenerFound = true; - useDelims = delimiterProcessor.getDelimiterUse(opener, closer); - if (useDelims > 0) { + usedDelims = delimiterProcessor.process(opener, closer); + if (usedDelims > 0) { openerFound = true; break; } @@ -607,7 +609,7 @@ private void processDelimiters(Delimiter stackBottom) { // we want to consider it next time because the number // of delimiters can change as we continue processing. openersBottom.put(delimiterChar, closer.previous); - if (!closer.canOpen) { + if (!closer.canOpen()) { // We can remove a closer that can't be an opener, // once we've seen there's no matching opener: removeDelimiterKeepNode(closer); @@ -617,34 +619,26 @@ private void processDelimiters(Delimiter stackBottom) { continue; } - Text openerNode = opener.node; - Text closerNode = closer.node; - - // Remove number of used delimiters from stack and inline nodes. - opener.length -= useDelims; - closer.length -= useDelims; - // TODO: Need to adjust source spans - openerNode.setLiteral( - openerNode.getLiteral().substring(0, - openerNode.getLiteral().length() - useDelims)); - closerNode.setLiteral( - closerNode.getLiteral().substring(0, - closerNode.getLiteral().length() - useDelims)); + // Remove number of used delimiters nodes. + for (int i = 0; i < usedDelims; i++) { + Text delimiter = opener.characters.remove(opener.characters.size() - 1); + delimiter.unlink(); + } + for (int i = 0; i < usedDelims; i++) { + Text delimiter = closer.characters.remove(0); + delimiter.unlink(); + } removeDelimitersBetween(opener, closer); - // The delimiter processor can re-parent the nodes between opener and closer, - // so make sure they're contiguous already. Exclusive because we want to keep opener/closer themselves. - mergeTextNodesBetweenExclusive(openerNode, closerNode); - delimiterProcessor.process(openerNode, closerNode, useDelims); // No delimiter characters left to process, so we can remove delimiter and the now empty node. - if (opener.length == 0) { - removeDelimiterAndNode(opener); + if (opener.length() == 0) { + removeDelimiterAndNodes(opener); } - if (closer.length == 0) { + if (closer.length() == 0) { Delimiter next = closer.next; - removeDelimiterAndNode(closer); + removeDelimiterAndNodes(closer); closer = next; } } @@ -667,9 +661,7 @@ private void removeDelimitersBetween(Delimiter opener, Delimiter closer) { /** * Remove the delimiter and the corresponding text node. For used delimiters, e.g. `*` in `*foo*`. */ - private void removeDelimiterAndNode(Delimiter delim) { - Text node = delim.node; - node.unlink(); + private void removeDelimiterAndNodes(Delimiter delim) { removeDelimiter(delim); } @@ -692,18 +684,9 @@ private void removeDelimiter(Delimiter delim) { } } - private void mergeTextNodesBetweenExclusive(Node fromNode, Node toNode) { - // No nodes between them - if (fromNode == toNode || fromNode.getNext() == toNode) { - return; - } - - mergeTextNodesInclusive(fromNode.getNext(), toNode.getPrevious()); - } - private void mergeChildTextNodes(Node node) { - // No children or just one child node, no need for merging - if (node.getFirstChild() == node.getLastChild()) { + // No children, no need for merging + if (node.getFirstChild() == null) { return; } @@ -729,6 +712,8 @@ private void mergeTextNodesInclusive(Node fromNode, Node toNode) { first = null; last = null; length = 0; + + mergeChildTextNodes(node); } if (node == toNode) { break; @@ -742,17 +727,18 @@ private void mergeTextNodesInclusive(Node fromNode, Node toNode) { private void mergeIfNeeded(Text first, Text last, int textLength) { if (first != null && last != null && first != last) { StringBuilder sb = new StringBuilder(textLength); - List<SourceSpan> sourceSpans = Collections.emptyList(); + sb.append(first.getLiteral()); + SourceSpans sourceSpans = null; if (includeSourceSpans) { - sourceSpans = new ArrayList<>(first.getSourceSpans()); + sourceSpans = new SourceSpans(); + sourceSpans.addAll(first.getSourceSpans()); } - sb.append(first.getLiteral()); Node node = first.getNext(); Node stop = last.getNext(); while (node != stop) { sb.append(((Text) node).getLiteral()); - if (includeSourceSpans) { - mergeSourceSpans(sourceSpans, node.getSourceSpans()); + if (sourceSpans != null) { + sourceSpans.addAll(node.getSourceSpans()); } Node unlink = node; @@ -761,38 +747,22 @@ private void mergeIfNeeded(Text first, Text last, int textLength) { } String literal = sb.toString(); first.setLiteral(literal); - first.setSourceSpans(sourceSpans); - } - } - - private void mergeSourceSpans(List<SourceSpan> sourceSpans, List<SourceSpan> other) { - if (sourceSpans.isEmpty()) { - sourceSpans.addAll(other); - } else if (!other.isEmpty()) { - 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())); - sourceSpans.addAll(other.subList(1, other.size())); - } else { - sourceSpans.addAll(other); + if (sourceSpans != null) { + first.setSourceSpans(sourceSpans.getSourceSpans()); } } } private static class DelimiterData { - final int count; + final List<Text> characters; final boolean canClose; final boolean canOpen; - final Text text; - DelimiterData(int count, boolean canOpen, boolean canClose, Text text) { - this.count = count; + DelimiterData(List<Text> characters, boolean canOpen, boolean canClose) { + this.characters = characters; this.canOpen = canOpen; this.canClose = canClose; - this.text = text; } } } diff --git a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java index c510edbe5..0fe8065bb 100644 --- a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java @@ -1,6 +1,5 @@ package org.commonmark.internal; -import org.commonmark.node.Text; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; @@ -71,12 +70,7 @@ private DelimiterProcessor findProcessor(int len) { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { - return findProcessor(opener.length()).getDelimiterUse(opener, closer); - } - - @Override - public void process(Text opener, Text closer, int delimiterUse) { - findProcessor(delimiterUse).process(opener, closer, delimiterUse); + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + return findProcessor(openingRun.length()).process(openingRun, closingRun); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java index 98b43938c..493e4299c 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EmphasisDelimiterProcessor.java @@ -1,9 +1,6 @@ package org.commonmark.internal.inline; -import org.commonmark.node.Emphasis; -import org.commonmark.node.Node; -import org.commonmark.node.StrongEmphasis; -import org.commonmark.node.Text; +import org.commonmark.node.*; import org.commonmark.parser.delimiter.DelimiterProcessor; import org.commonmark.parser.delimiter.DelimiterRun; @@ -31,35 +28,39 @@ public int getMinLength() { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { // "multiple of 3" rule for internal delimiter runs - if ((opener.canClose() || closer.canOpen()) && - closer.originalLength() % 3 != 0 && - (opener.originalLength() + closer.originalLength()) % 3 == 0) { + if ((openingRun.canClose() || closingRun.canOpen()) && + closingRun.originalLength() % 3 != 0 && + (openingRun.originalLength() + closingRun.originalLength()) % 3 == 0) { return 0; } + + int usedDelimiters; + Node emphasis; // calculate actual number of delimiters used from this closer - if (opener.length() >= 2 && closer.length() >= 2) { - return 2; + if (openingRun.length() >= 2 && closingRun.length() >= 2) { + usedDelimiters = 2; + emphasis = new StrongEmphasis(String.valueOf(delimiterChar) + delimiterChar); } else { - return 1; + usedDelimiters = 1; + emphasis = new Emphasis(String.valueOf(delimiterChar)); } - } - @Override - public void process(Text opener, Text closer, int delimiterUse) { - String singleDelimiter = String.valueOf(getOpeningCharacter()); - Node emphasis = delimiterUse == 1 - ? new Emphasis(singleDelimiter) - : new StrongEmphasis(singleDelimiter + singleDelimiter); + SourceSpans sourceSpans = SourceSpans.empty(); + sourceSpans.addAllFrom(openingRun.getOpeners(usedDelimiters)); - Node tmp = opener.getNext(); - while (tmp != null && tmp != closer) { - Node next = tmp.getNext(); - emphasis.appendChild(tmp); - tmp = next; + Text opener = openingRun.getOpener(); + for (Node node : Nodes.between(opener, closingRun.getCloser())) { + emphasis.appendChild(node); + sourceSpans.addAll(node.getSourceSpans()); } + sourceSpans.addAllFrom(closingRun.getClosers(usedDelimiters)); + + emphasis.setSourceSpans(sourceSpans.getSourceSpans()); opener.insertAfter(emphasis); + + return usedDelimiters; } } diff --git a/commonmark/src/main/java/org/commonmark/node/Nodes.java b/commonmark/src/main/java/org/commonmark/node/Nodes.java new file mode 100644 index 000000000..a74725f19 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/Nodes.java @@ -0,0 +1,64 @@ +package org.commonmark.node; + +import java.util.Iterator; + +/** + * Utility class for working with multiple {@link Node}s. + */ +public class Nodes { + + private Nodes() { + } + + /** + * The nodes between (not including) start and end. + */ + public static Iterable<Node> between(Node start, Node end) { + return new NodeIterable(start.getNext(), end); + } + + private static class NodeIterable implements Iterable<Node> { + + private final Node first; + private final Node end; + + private NodeIterable(Node first, Node end) { + this.first = first; + this.end = end; + } + + @Override + public Iterator<Node> iterator() { + return new NodeIterator(first, end); + } + } + + private static class NodeIterator implements Iterator<Node> { + + private Node node; + private final Node end; + + private NodeIterator(Node first, Node end) { + node = first; + this.end = end; + } + + @Override + public boolean hasNext() { + return node != null && node != end; + } + + @Override + public Node next() { + Node result = node; + node = node.getNext(); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + } +} + diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java new file mode 100644 index 000000000..faa70dba0 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java @@ -0,0 +1,51 @@ +package org.commonmark.node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A list of source spans that can be added to. Takes care of merging adjacent source spans. + */ +public class SourceSpans { + + private List<SourceSpan> sourceSpans; + + public static SourceSpans empty() { + return new SourceSpans(); + } + + public List<SourceSpan> getSourceSpans() { + return sourceSpans != null ? sourceSpans : Collections.<SourceSpan>emptyList(); + } + + public void addAllFrom(Iterable<? extends Node> nodes) { + for (Node node : nodes) { + addAll(node.getSourceSpans()); + } + } + + public void addAll(List<SourceSpan> other) { + if (other.isEmpty()) { + return; + } + + if (sourceSpans == null) { + sourceSpans = new ArrayList<>(); + } + + if (sourceSpans.isEmpty()) { + sourceSpans.addAll(other); + } else { + 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())); + sourceSpans.addAll(other.subList(1, other.size())); + } else { + sourceSpans.addAll(other); + } + } + } +} 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 0e8bc6fac..897943d66 100644 --- a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterProcessor.java @@ -27,27 +27,18 @@ public interface DelimiterProcessor { int getMinLength(); /** - * Determine how many (if any) of the delimiter characters should be used. + * Process the delimiter runs. * <p> - * This allows implementations to decide how many characters to use based on the properties of the delimiter runs. - * An implementation can also return 0 when it doesn't want to allow this particular combination of delimiter runs. - * - * @param opener the opening delimiter run - * @param closer the closing delimiter run - * @return how many delimiters should be used; must not be greater than length of either opener or closer - */ - int getDelimiterUse(DelimiterRun opener, DelimiterRun closer); - - /** - * Process the matched delimiters, e.g. by wrapping the nodes between opener and closer in a new node, or appending - * a new node after the opener. + * The processor can examine the runs and the nodes and decide if it wants to process or not. If not, it should not + * change any nodes and return 0. If yes, it should do the processing (wrapping nodes, etc) and then return how many + * delimiters were used. * <p> - * Note that removal of the delimiter from the delimiter nodes and unlinking them is done by the caller. + * Note that removal (unlinking) of the used delimiter {@link Text} nodes is done by the caller. * - * @param opener the text node that contained the opening delimiter - * @param closer the text node that contained the closing delimiter - * @param delimiterUse the number of delimiters that were used + * @param openingRun the opening delimiter run + * @param closingRun the closing delimiter run + * @return how many delimiters were used; must not be greater than length of either opener or closer */ - void process(Text opener, Text closer, int delimiterUse); + int process(DelimiterRun openingRun, DelimiterRun closingRun); } diff --git a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java index 29bdb8731..578eac96b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java +++ b/commonmark/src/main/java/org/commonmark/parser/delimiter/DelimiterRun.java @@ -1,7 +1,9 @@ package org.commonmark.parser.delimiter; +import org.commonmark.node.Text; + /** - * A delimiter run is one or more of the same delimiter character. + * A delimiter run is one or more of the same delimiter character, e.g. {@code ***}. */ public interface DelimiterRun { @@ -25,4 +27,32 @@ public interface DelimiterRun { * as {{@link #length()}} */ int originalLength(); + + /** + * @return the innermost opening delimiter, e.g. for {@code ***} this is the last {@code *} + */ + Text getOpener(); + + /** + * @return the innermost closing delimiter, e.g. for {@code ***} this is the first {@code *} + */ + Text getCloser(); + + /** + * Get the opening delimiter nodes for the specified length of delimiters. Length must be between 1 and + * {@link #length()}. + * <p> + * For example, for a delimiter run {@code ***}, calling this with 1 would return the last {@code *}. + * Calling it with 2 would return the second last {@code *} and the last {@code *}. + */ + Iterable<Text> getOpeners(int length); + + /** + * Get the closing delimiter nodes for the specified length of delimiters. Length must be between 1 and + * {@link #length()}. + * <p> + * For example, for a delimiter run {@code ***}, calling this with 1 would return the first {@code *}. + * Calling it with 2 would return the first {@code *} and the second {@code *}. + */ + Iterable<Text> getClosers(int length); } diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 948c484cd..173d711d3 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -99,13 +99,9 @@ public int getMinLength() { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { return delimiterUse; } - - @Override - public void process(Text opener, Text closer, int delimiterUse) { - } } private static class AsymmetricDelimiterProcessor implements DelimiterProcessor { @@ -126,20 +122,19 @@ public int getMinLength() { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { - return 1; - } - - @Override - public void process(Text opener, Text closer, int delimiterUse) { + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { UpperCaseNode content = new UpperCaseNode(); - Node tmp = opener.getNext(); - while (tmp != null && tmp != closer) { + Text start = openingRun.getOpener(); + Text end = closingRun.getCloser(); + Node tmp = start.getNext(); + while (tmp != null && tmp != end) { Node next = tmp.getNext(); content.appendChild(tmp); tmp = next; } - opener.insertAfter(content); + start.insertAfter(content); + + return 1; } } @@ -198,15 +193,11 @@ public int getMinLength() { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + openingRun.getOpener().insertAfter(new Text("(1)")); + closingRun.getCloser().insertBefore(new Text("(/1)")); return 1; } - - @Override - public void process(Text opener, Text closer, int delimiterUse) { - opener.insertAfter(new Text("(1)")); - closer.insertBefore(new Text("(/1)")); - } } private static class TwoDelimiterProcessor implements DelimiterProcessor { @@ -227,14 +218,10 @@ public int getMinLength() { } @Override - public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) { + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + openingRun.getOpener().insertAfter(new Text("(2)")); + closingRun.getCloser().insertBefore(new Text("(/2)")); return 2; } - - @Override - public void process(Text opener, Text closer, int delimiterUse) { - opener.insertAfter(new Text("(2)")); - closer.insertBefore(new Text("(/2)")); - } } } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index bd5d818ec..ba25206c2 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -246,8 +246,22 @@ public void links() { public void inlineEmphasis() { assertInlineSpans("*hey*", Emphasis.class, SourceSpan.of(0, 0, 5)); assertInlineSpans("*hey*", Text.class, SourceSpan.of(0, 1, 3)); - assertInlineSpans("**hey**", Emphasis.class, SourceSpan.of(0, 0, 7)); + assertInlineSpans("**hey**", StrongEmphasis.class, SourceSpan.of(0, 0, 7)); assertInlineSpans("**hey**", Text.class, SourceSpan.of(0, 2, 3)); + + // This is an interesting one. It renders like this: + // <p>*<em>hey</em></p> + // 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("***hey**", Text.class, SourceSpan.of(0, 0, 1)); + assertInlineSpans("***hey**", StrongEmphasis.class, SourceSpan.of(0, 1, 7)); + + Node document = INLINES_PARSER.parse("*hey**"); + Node lastText = document.getFirstChild().getLastChild(); + assertEquals(Arrays.asList(SourceSpan.of(0, 5, 1)), lastText.getSourceSpans()); } // TODO: @@ -261,7 +275,6 @@ private String visualizeSourceSpans(String source) { return SourceSpanRenderer.render(document, source); } - private static void assertSpans(String input, Class<? extends Node> nodeClass, SourceSpan... expectedSourceSpans) { assertSpans(PARSER.parse(input), nodeClass, expectedSourceSpans); } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 2b19db3db..2ed93c92d 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -174,4 +174,9 @@ public void trailingTabs() { // This matches what commonmark.js did at the time of writing. assertRendering("a\t\nb\n", "<p>a\t\nb</p>\n"); } + + @Test + public void emph() { + assertRendering("*foo bar*\n", "<p><em>foo bar</em></p>\n"); + } } From 35be54313d36799bfbb79a3a28c6a3b8e0a90e7d Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 27 Nov 2020 16:22:07 +1100 Subject: [PATCH 454/815] Test case for tab --- .../java/org/commonmark/internal/DocumentParser.java | 4 ++-- .../java/org/commonmark/test/SourceSpansTest.java | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 1211f2230..a3bec69a8 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -405,9 +405,9 @@ private void addLine() { content = line.subSequence(index, line.length()); } SourceSpan sourceSpan = null; -// SourceLine sourceLine = ; if (includeSourceSpans == IncludeSourceSpans.BLOCKS_AND_INLINES) { - // TODO: This is not correct for expanded tabs + // 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()); } getActiveBlockParser().addLine(SourceLine.of(content, sourceSpan)); diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index ba25206c2..d5ce6f6cd 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -264,11 +264,13 @@ public void inlineEmphasis() { assertEquals(Arrays.asList(SourceSpan.of(0, 5, 1)), lastText.getSourceSpans()); } - // TODO: -// @Test -// public void tabOffset() { -// assertInlineSpans(">\t*foo*"); -// } + @Test + public void tabExpansion() { + assertInlineSpans(">\tfoo", BlockQuote.class, SourceSpan.of(0, 0, 5)); + assertInlineSpans(">\tfoo", Text.class, SourceSpan.of(0, 2, 3)); + + assertInlineSpans("a\tb", Text.class, SourceSpan.of(0, 0, 3)); + } private String visualizeSourceSpans(String source) { Node document = PARSER.parse(source); From 6238c36713378345ca81d81e6f90338fc9172e4b Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 27 Nov 2020 16:38:33 +1100 Subject: [PATCH 455/815] Add tests for SourceLine --- .../org/commonmark/parser/SourceLine.java | 2 - .../org/commonmark/test/SourceLineTest.java | 42 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/test/SourceLineTest.java diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java index b2b1aff8d..f593d3279 100644 --- a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java @@ -31,8 +31,6 @@ public SourceSpan getSourceSpan() { } public SourceLine substring(int beginIndex, int endIndex) { - // TODO: Check indices - // TODO: Tests CharSequence newContent = content.subSequence(beginIndex, endIndex); SourceSpan newSourceSpan = null; if (sourceSpan != null) { diff --git a/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java b/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java new file mode 100644 index 000000000..aa330fbc9 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/SourceLineTest.java @@ -0,0 +1,42 @@ +package org.commonmark.test; + +import org.commonmark.node.SourceSpan; +import org.commonmark.parser.SourceLine; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SourceLineTest { + + @Test + public void testSubstring() { + SourceLine line = SourceLine.of("abcd", SourceSpan.of(3, 10, 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, 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(3, 4), "d", SourceSpan.of(3, 13, 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); + } + + @Test(expected = StringIndexOutOfBoundsException.class) + public void testSubstringEndOutOfBounds() { + SourceLine.of("abcd", SourceSpan.of(3, 10, 4)).substring(0, 5); + } + + private static void assertSourceLine(SourceLine sourceLine, String expectedContent, SourceSpan expectedSourceSpan) { + assertEquals(expectedContent, sourceLine.getContent()); + assertEquals(expectedSourceSpan, sourceLine.getSourceSpan()); + } +} From 10931591122ec4764d0d873b2d5c878a7741c46d Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 27 Nov 2020 16:38:48 +1100 Subject: [PATCH 456/815] Fix SourceSpan for getLine --- .../main/java/org/commonmark/internal/DocumentParser.java | 3 +-- .../src/test/java/org/commonmark/test/SourceSpansTest.java | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index a3bec69a8..57ad45734 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -142,8 +142,7 @@ public Document parse(Reader input) throws IOException { public SourceLine getLine() { SourceSpan sourceSpan = null; if (includeSourceSpans != IncludeSourceSpans.NONE) { - // TODO: is this wrong? Should be 0 shouldn't it? - sourceSpan = SourceSpan.of(lineIndex, index, line.length()); + sourceSpan = SourceSpan.of(lineIndex, 0, line.length()); } return SourceLine.of(line, sourceSpan); } diff --git a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java index d5ce6f6cd..a96d9c58b 100644 --- a/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SourceSpansTest.java @@ -199,6 +199,13 @@ public void inlineText() { assertInlineSpans("*foo*", Text.class, SourceSpan.of(0, 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)); + } + @Test public void inlineAutolink() { assertInlineSpans("see <https://example.org>", Link.class, SourceSpan.of(0, 4, 21)); From 43941d7e44398ab9c03c9c4df0d72f9c8403a5e6 Mon Sep 17 00:00:00 2001 From: javaor <69024684+javaor@users.noreply.github.com> Date: Tue, 8 Dec 2020 15:59:08 +0800 Subject: [PATCH 457/815] Add a test case for dot support in keys --- .../ext/front/matter/YamlFrontMatterTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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 505c70e6a..35c647c3c 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 @@ -310,6 +310,25 @@ public void nodesCanBeModified() { assertTrue(data.containsKey("see")); assertEquals(Collections.singletonList("you"), data.get("see")); } + + @Test + public void dotInKeys() { + final String input = "---" + + "\nms.author: author" + + "\n---" + + "\n"; + + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map<String, List<String>> data = visitor.getData(); + + assertEquals(1, data.size()); + assertEquals("ms.author", data.keySet().iterator().next()); + assertEquals(1, data.get("ms.author").size()); + assertEquals("author", data.get("key").get(0)); + } @Override protected String render(String source) { From c0c99205f4995adb157892843f1afb294e890de0 Mon Sep 17 00:00:00 2001 From: javaor <69024684+javaor@users.noreply.github.com> Date: Tue, 8 Dec 2020 17:06:56 +0800 Subject: [PATCH 458/815] Update YamlFrontMatterTest.java --- .../org/commonmark/ext/front/matter/YamlFrontMatterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 35c647c3c..251b1c15f 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 @@ -327,7 +327,7 @@ public void dotInKeys() { assertEquals(1, data.size()); assertEquals("ms.author", data.keySet().iterator().next()); assertEquals(1, data.get("ms.author").size()); - assertEquals("author", data.get("key").get(0)); + assertEquals("author", data.get("ms.author").get(0)); } @Override From 122c709f6e77b2047f6bebbfd788273b00bfe026 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 10 Dec 2020 13:20:24 +1100 Subject: [PATCH 459/815] Rename textBetween method now that it returns the source lines --- .../commonmark/internal/HeadingParser.java | 2 +- .../commonmark/internal/InlineParserImpl.java | 30 +++++++++---------- .../LinkReferenceDefinitionParser.java | 6 ++-- .../internal/inline/AutolinkInlineParser.java | 2 +- .../inline/BackticksInlineParser.java | 4 +-- .../internal/inline/EntityInlineParser.java | 3 +- .../internal/inline/HtmlInlineParser.java | 2 +- .../commonmark/internal/inline/Scanner.java | 5 ++-- .../org/commonmark/parser/SourceLines.java | 1 - .../internal/inline/ScannerTest.java | 16 +++++----- 10 files changed, 34 insertions(+), 37 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 9e8c54075..596dfe690 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -121,7 +121,7 @@ private static HeadingParser getAtxHeading(SourceLine line) { } } - SourceLines source = scanner.textBetween(start, end); + SourceLines source = scanner.getSource(start, end); String content = source.getContent(); if (content.isEmpty()) { return new HeadingParser(level, SourceLines.empty()); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 550cdaa8a..b81d67676 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -180,7 +180,7 @@ private List<? extends Node> parseInline() { Node node = parsedInlineImpl.getNode(); scanner.setPosition(parsedInlineImpl.getPosition()); if (includeSourceSpans && node.getSourceSpans().isEmpty()) { - node.setSourceSpans(scanner.textBetween(position, scanner.position()).getSourceSpans()); + node.setSourceSpans(scanner.getSource(position, scanner.position()).getSourceSpans()); } return Collections.singletonList(node); } else { @@ -242,7 +242,7 @@ private Node parseOpenBracket() { scanner.next(); Position contentPosition = scanner.position(); - Text node = text(scanner.textBetween(start, contentPosition)); + Text node = text(scanner.getSource(start, contentPosition)); // Add entry to stack for this opener addBracket(Bracket.link(node, start, contentPosition, lastBracket, lastDelimiter)); @@ -259,13 +259,13 @@ private Node parseBang() { scanner.next(); if (scanner.next('[')) { Position contentPosition = scanner.position(); - Text node = text(scanner.textBetween(start, contentPosition)); + Text node = text(scanner.getSource(start, contentPosition)); // Add entry to stack for this opener addBracket(Bracket.image(node, start, contentPosition, lastBracket, lastDelimiter)); return node; } else { - return text(scanner.textBetween(start, scanner.position())); + return text(scanner.getSource(start, scanner.position())); } } @@ -282,13 +282,13 @@ private Node parseCloseBracket() { Bracket opener = lastBracket; if (opener == null) { // No matching opener, just return a literal. - return text(scanner.textBetween(beforeClose, afterClose)); + return text(scanner.getSource(beforeClose, afterClose)); } if (!opener.allowed) { // Matching opener but it's not allowed, just return a literal. removeLastBracket(); - return text(scanner.textBetween(beforeClose, afterClose)); + return text(scanner.getSource(beforeClose, afterClose)); } // Check to see if we have a link/image @@ -331,7 +331,7 @@ private Node parseCloseBracket() { // 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.textBetween(opener.contentPosition, beforeClose).getContent(); + ref = scanner.getSource(opener.contentPosition, beforeClose).getContent(); } if (ref != null) { @@ -357,7 +357,7 @@ private Node parseCloseBracket() { } if (includeSourceSpans) { - linkOrImage.setSourceSpans(scanner.textBetween(opener.markerPosition, scanner.position()).getSourceSpans()); + linkOrImage.setSourceSpans(scanner.getSource(opener.markerPosition, scanner.position()).getSourceSpans()); } // Process delimiters such as emphasis inside link/image @@ -386,7 +386,7 @@ private Node parseCloseBracket() { removeLastBracket(); scanner.setPosition(afterClose); - return text(scanner.textBetween(beforeClose, afterClose)); + return text(scanner.getSource(beforeClose, afterClose)); } } @@ -414,10 +414,10 @@ private String parseLinkDestination(Scanner scanner) { String dest; if (delimiter == '<') { // chop off surrounding <..>: - String rawDestination = scanner.textBetween(start, scanner.position()).getContent(); + String rawDestination = scanner.getSource(start, scanner.position()).getContent(); dest = rawDestination.substring(1, rawDestination.length() - 1); } else { - dest = scanner.textBetween(start, scanner.position()).getContent(); + dest = scanner.getSource(start, scanner.position()).getContent(); } return Escaping.unescapeString(dest); @@ -433,7 +433,7 @@ private String parseLinkTitle(Scanner scanner) { } // chop off ', " or parens - String rawTitle = scanner.textBetween(start, scanner.position()).getContent(); + String rawTitle = scanner.getSource(start, scanner.position()).getContent(); String title = rawTitle.substring(1, rawTitle.length() - 1); return Escaping.unescapeString(title); } @@ -456,7 +456,7 @@ String parseLinkLabel(Scanner scanner) { return null; } - String content = scanner.textBetween(start, end).getContent(); + String content = scanner.getSource(start, end).getContent(); // spec: A link label can have at most 999 characters inside the square brackets. if (content.length() > 999) { return null; @@ -488,7 +488,7 @@ private Node parseText() { scanner.next(); } - SourceLines source = scanner.textBetween(start, scanner.position()); + SourceLines source = scanner.getSource(start, scanner.position()); String content = source.getContent(); char c = scanner.peek(); @@ -530,7 +530,7 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char scanner.setPosition(start); Position positionBefore = start; while (scanner.next(delimiterChar)) { - delimiters.add(text(scanner.textBetween(positionBefore, scanner.position()))); + delimiters.add(text(scanner.getSource(positionBefore, scanner.position()))); positionBefore = scanner.position(); } diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 6178d8b64..3d8e9ab70 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -121,7 +121,7 @@ private boolean label(Scanner scanner) { return false; } - label.append(scanner.textBetween(start, scanner.position()).getContent()); + label.append(scanner.getSource(start, scanner.position()).getContent()); if (!scanner.hasNext()) { // label might continue on next line @@ -160,7 +160,7 @@ private boolean destination(Scanner scanner) { return false; } - String rawDestination = scanner.textBetween(start, scanner.position()).getContent(); + String rawDestination = scanner.getSource(start, scanner.position()).getContent(); destination = rawDestination.startsWith("<") ? rawDestination.substring(1, rawDestination.length() - 1) : rawDestination; @@ -221,7 +221,7 @@ private boolean title(Scanner scanner) { return false; } - title.append(scanner.textBetween(start, scanner.position()).getContent()); + title.append(scanner.getSource(start, scanner.position()).getContent()); if (!scanner.hasNext()) { // Title ran until the end of line, so continue on next line (until we find the delimiter) 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 3f0ba871e..ecfd2d972 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/AutolinkInlineParser.java @@ -23,7 +23,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { scanner.next(); Position textStart = scanner.position(); if (scanner.find('>') > 0) { - SourceLines textSource = scanner.textBetween(textStart, scanner.position()); + SourceLines textSource = scanner.getSource(textStart, scanner.position()); String content = textSource.getContent(); scanner.next(); 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 07e35e6bc..ad079444a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/BackticksInlineParser.java @@ -23,7 +23,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { if (count == openingTicks) { Code node = new Code(); - String content = scanner.textBetween(afterOpening, beforeClosing).getContent(); + String content = scanner.getSource(afterOpening, beforeClosing).getContent(); content = content.replace('\n', ' '); // spec: If the resulting string both begins and ends with a space character, but does not consist @@ -41,7 +41,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { } // If we got here, we didn't find a matching closing backtick sequence. - SourceLines source = scanner.textBetween(start, afterOpening); + SourceLines source = scanner.getSource(start, afterOpening); Text text = new Text(source.getContent()); return ParsedInline.of(text, afterOpening); } 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 dce5438e9..c29b8694f 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/EntityInlineParser.java @@ -2,7 +2,6 @@ import org.commonmark.internal.util.AsciiMatcher; import org.commonmark.internal.util.Html5Entities; -import org.commonmark.node.Node; import org.commonmark.node.Text; /** @@ -48,7 +47,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { } private ParsedInline entity(Scanner scanner, Position start) { - String text = scanner.textBetween(start, scanner.position()).getContent(); + String text = scanner.getSource(start, scanner.position()).getContent(); return ParsedInline.of(new Text(Html5Entities.entityToString(text)), scanner.position()); } } 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 a60b0d7a8..ba992e2fd 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -69,7 +69,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { } private static ParsedInline htmlInline(Position start, Scanner scanner) { - String text = scanner.textBetween(start, scanner.position()).getContent(); + String text = scanner.getSource(start, scanner.position()).getContent(); HtmlInline node = new HtmlInline(); node.setLiteral(text); return ParsedInline.of(node, scanner.position()); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 5715e2e14..5533cb8ee 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -10,7 +10,7 @@ public class Scanner { /** - * Character representing the end of input (or outside of the text in case of the "previous" methods). + * Character representing the end of input source (or outside of the text in case of the "previous" methods). * <p> * Note that we can use NULL to represent this because CommonMark does not allow those in the input (we replace them * in the beginning of parsing). @@ -209,8 +209,7 @@ public void setPosition(Position position) { // For cases where the caller appends the result to a StringBuilder, we could offer another method to avoid some // unnecessary copying. - // TODO: Rename - public SourceLines textBetween(Position begin, Position end) { + public SourceLines getSource(Position begin, Position end) { if (begin.lineIndex == end.lineIndex) { // Shortcut for common case of text from a single line SourceLine line = lines.get(begin.lineIndex); diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLines.java b/commonmark/src/main/java/org/commonmark/parser/SourceLines.java index 23f2edb96..e1b761d22 100644 --- a/commonmark/src/main/java/org/commonmark/parser/SourceLines.java +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLines.java @@ -37,7 +37,6 @@ public List<SourceLine> getLines() { } public boolean isEmpty() { - // TODO: What if there's a single empty line? return lines.isEmpty(); } diff --git a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java index e7728c000..68968d59a 100644 --- a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java @@ -78,48 +78,48 @@ public void testTextBetween() { Position start = scanner.position(); scanner.next(); - assertSourceLines(scanner.textBetween(start, scanner.position()), + assertSourceLines(scanner.getSource(start, scanner.position()), "a", SourceSpan.of(10, 3, 1)); Position afterA = scanner.position(); scanner.next(); - assertSourceLines(scanner.textBetween(start, scanner.position()), + assertSourceLines(scanner.getSource(start, scanner.position()), "ab", SourceSpan.of(10, 3, 2)); Position afterB = scanner.position(); scanner.next(); - assertSourceLines(scanner.textBetween(start, scanner.position()), + assertSourceLines(scanner.getSource(start, scanner.position()), "ab\n", SourceSpan.of(10, 3, 2)); scanner.next(); - assertSourceLines(scanner.textBetween(start, scanner.position()), + assertSourceLines(scanner.getSource(start, scanner.position()), "ab\nc", SourceSpan.of(10, 3, 2), SourceSpan.of(11, 4, 1)); scanner.next(); - assertSourceLines(scanner.textBetween(start, scanner.position()), + assertSourceLines(scanner.getSource(start, scanner.position()), "ab\ncd", SourceSpan.of(10, 3, 2), SourceSpan.of(11, 4, 2)); scanner.next(); - assertSourceLines(scanner.textBetween(start, scanner.position()), + assertSourceLines(scanner.getSource(start, scanner.position()), "ab\ncde", SourceSpan.of(10, 3, 2), SourceSpan.of(11, 4, 3)); - assertSourceLines(scanner.textBetween(afterA, scanner.position()), + assertSourceLines(scanner.getSource(afterA, scanner.position()), "b\ncde", SourceSpan.of(10, 4, 1), SourceSpan.of(11, 4, 3)); - assertSourceLines(scanner.textBetween(afterB, scanner.position()), + assertSourceLines(scanner.getSource(afterB, scanner.position()), "\ncde", SourceSpan.of(11, 4, 3)); } From 94a4606f387183c0576d29458962c8d2c0610f11 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 10 Dec 2020 13:58:48 +1100 Subject: [PATCH 460/815] Remove Travis CI Looks like they stopped providing builds for OSS. Thanks for all the free builds. Main builds have been moved to GitHub actions already. Android build will need to be re-enabled somehow. --- .travis.yml | 24 ------------------- .../{.travis.sh => run.sh} | 0 2 files changed, 24 deletions(-) delete mode 100644 .travis.yml rename commonmark-android-test/{.travis.sh => run.sh} (100%) diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ebe918c1c..000000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: android - -matrix: - include: - - jdk: oraclejdk8 - env: TEST=java - - jdk: openjdk11 - env: TEST=java - - jdk: oraclejdk8 - env: TEST=android - dist: precise - android: - components: - - android-16 - - build-tools-21.1.1 - - extra-android-m2repository - - sys-img-armeabi-v7a-android-16 - - allow_failures: - - env: TEST=android - -script: - - 'if [ $TEST = java ]; then mvn test -Dsurefire.useFile=false; fi' - - 'if [ $TEST = android ]; then mvn install -DskipTests && cd commonmark-android-test && travis_retry ./.travis.sh; fi' diff --git a/commonmark-android-test/.travis.sh b/commonmark-android-test/run.sh similarity index 100% rename from commonmark-android-test/.travis.sh rename to commonmark-android-test/run.sh From 7b2396b0327107d5e188910c25aa1965f485f69d Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 10 Dec 2020 15:42:49 +1100 Subject: [PATCH 461/815] Add some @since tags to new APIs --- commonmark/src/main/java/org/commonmark/node/Node.java | 3 +++ commonmark/src/main/java/org/commonmark/node/Nodes.java | 2 ++ commonmark/src/main/java/org/commonmark/node/SourceSpan.java | 2 ++ commonmark/src/main/java/org/commonmark/node/SourceSpans.java | 2 ++ .../main/java/org/commonmark/parser/IncludeSourceSpans.java | 2 ++ commonmark/src/main/java/org/commonmark/parser/Parser.java | 1 + commonmark/src/main/java/org/commonmark/parser/SourceLine.java | 2 ++ .../src/main/java/org/commonmark/parser/SourceLines.java | 2 ++ .../src/main/java/org/commonmark/parser/block/BlockParser.java | 2 ++ 9 files changed, 18 insertions(+) diff --git a/commonmark/src/main/java/org/commonmark/node/Node.java b/commonmark/src/main/java/org/commonmark/node/Node.java index 8ef608c8c..5a2f036e4 100644 --- a/commonmark/src/main/java/org/commonmark/node/Node.java +++ b/commonmark/src/main/java/org/commonmark/node/Node.java @@ -117,6 +117,7 @@ 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 */ public List<SourceSpan> getSourceSpans() { return sourceSpans != null ? Collections.unmodifiableList(sourceSpans) : Collections.<SourceSpan>emptyList(); @@ -126,6 +127,7 @@ public List<SourceSpan> getSourceSpans() { * Replace the current source spans with the provided list. * * @param sourceSpans the new source spans to set + * @since 0.16.0 */ public void setSourceSpans(List<SourceSpan> sourceSpans) { if (sourceSpans.isEmpty()) { @@ -139,6 +141,7 @@ public void setSourceSpans(List<SourceSpan> sourceSpans) { * Add a source span to the end of the list. * * @param sourceSpan the source span to add + * @since 0.16.0 */ public void addSourceSpan(SourceSpan sourceSpan) { if (sourceSpans == null) { diff --git a/commonmark/src/main/java/org/commonmark/node/Nodes.java b/commonmark/src/main/java/org/commonmark/node/Nodes.java index a74725f19..22d5932af 100644 --- a/commonmark/src/main/java/org/commonmark/node/Nodes.java +++ b/commonmark/src/main/java/org/commonmark/node/Nodes.java @@ -4,6 +4,8 @@ /** * Utility class for working with multiple {@link Node}s. + * + * @since 0.16.0 */ public class Nodes { diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java index a643bd4dc..2410e9ff7 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java @@ -20,6 +20,8 @@ * Note that the column index and length are measured in Java characters (UTF-16 code units). If you're outputting them * to be consumed by another programming language, e.g. one that uses UTF-8 strings, you will need to translate them, * otherwise characters such as emojis will result in incorrect positions. + * + * @since 0.16.0 */ public class SourceSpan { diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java index faa70dba0..3ab29f536 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpans.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpans.java @@ -6,6 +6,8 @@ /** * A list of source spans that can be added to. Takes care of merging adjacent source spans. + * + * @since 0.16.0 */ public class SourceSpans { diff --git a/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java b/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java index 4ea8b5112..91d2b4e00 100644 --- a/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java +++ b/commonmark/src/main/java/org/commonmark/parser/IncludeSourceSpans.java @@ -3,6 +3,8 @@ /** * Whether to include {@link org.commonmark.node.SourceSpan} or not while parsing, * see {@link Parser.Builder#includeSourceSpans(IncludeSourceSpans)}. + * + * @since 0.16.0 */ public enum IncludeSourceSpans { /** diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index a4d5c8531..dcdecfd5a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -197,6 +197,7 @@ public Builder enabledBlockTypes(Set<Class<? extends Block>> enabledBlockTypes) * * @param includeSourceSpans which kind of source spans should be included * @return {@code this} + * @since 0.16.0 */ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { this.includeSourceSpans = includeSourceSpans; diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java index f593d3279..63caceb9e 100644 --- a/commonmark/src/main/java/org/commonmark/parser/SourceLine.java +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLine.java @@ -4,6 +4,8 @@ /** * A line or part of a line from the input source. + * + * @since 0.16.0 */ public class SourceLine { diff --git a/commonmark/src/main/java/org/commonmark/parser/SourceLines.java b/commonmark/src/main/java/org/commonmark/parser/SourceLines.java index e1b761d22..0b4290341 100644 --- a/commonmark/src/main/java/org/commonmark/parser/SourceLines.java +++ b/commonmark/src/main/java/org/commonmark/parser/SourceLines.java @@ -7,6 +7,8 @@ /** * A set of lines ({@link SourceLine}) from the input source. + * + * @since 0.16.0 */ public class SourceLines { 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 bdaaf0e47..aa956a48a 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/BlockParser.java @@ -40,6 +40,8 @@ public interface BlockParser { * Add a source span of the currently parsed block. The default implementation in {@link AbstractBlockParser} adds * it to the block. Unless you have some complicated parsing where you need to check source positions, you don't * need to override this. + * + * @since 0.16.0 */ void addSourceSpan(SourceSpan sourceSpan); From b48dc96d9798a07add7ad53ee0c1d6306ec371a2 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 10 Dec 2020 22:30:20 +1100 Subject: [PATCH 462/815] Some optimizations --- .../commonmark/internal/DocumentParser.java | 57 ++++++++++-------- .../internal/FencedCodeBlockParser.java | 3 +- .../commonmark/internal/HeadingParser.java | 8 ++- .../commonmark/internal/InlineParserImpl.java | 60 +++++++++---------- .../commonmark/parser/block/ParserState.java | 2 +- 5 files changed, 66 insertions(+), 64 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 57ad45734..179ad1140 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -36,8 +36,7 @@ public class DocumentParser implements ParserState { NODES_TO_CORE_FACTORIES = Collections.unmodifiableMap(map); } - - private CharSequence line; + private SourceLine line; /** * Line index (0-based) @@ -107,7 +106,7 @@ public Document parse(String input) { int lineBreak; while ((lineBreak = Parsing.findLineBreak(input, lineStart)) != -1) { String line = input.substring(lineStart, lineBreak); - incorporateLine(line); + parseLine(line); if (lineBreak + 1 < input.length() && input.charAt(lineBreak) == '\r' && input.charAt(lineBreak + 1) == '\n') { lineStart = lineBreak + 2; } else { @@ -116,7 +115,7 @@ public Document parse(String input) { } if (input.length() > 0 && (lineStart == 0 || lineStart < input.length())) { String line = input.substring(lineStart); - incorporateLine(line); + parseLine(line); } return finalizeAndProcess(); @@ -132,7 +131,7 @@ public Document parse(Reader input) throws IOException { String line; while ((line = bufferedReader.readLine()) != null) { - incorporateLine(line); + parseLine(line); } return finalizeAndProcess(); @@ -140,11 +139,7 @@ public Document parse(Reader input) throws IOException { @Override public SourceLine getLine() { - SourceSpan sourceSpan = null; - if (includeSourceSpans != IncludeSourceSpans.NONE) { - sourceSpan = SourceSpan.of(lineIndex, 0, line.length()); - } - return SourceLine.of(line, sourceSpan); + return line; } @Override @@ -181,12 +176,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 incorporateLine(CharSequence ln) { - lineIndex++; - line = Parsing.prepareLine(ln); - index = 0; - column = 0; - columnIsInTab = false; + private void parseLine(CharSequence ln) { + setLine(ln); // 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 @@ -231,7 +222,7 @@ private void incorporateLine(CharSequence ln) { findNextNonSpace(); // this is a little performance optimization: - if (isBlank() || (indent < Parsing.CODE_BLOCK_INDENT && Parsing.isLetter(line, nextNonSpace))) { + if (isBlank() || (indent < Parsing.CODE_BLOCK_INDENT && Parsing.isLetter(this.line.getContent(), nextNonSpace))) { setNewIndex(nextNonSpace); break; } @@ -310,14 +301,28 @@ private void incorporateLine(CharSequence ln) { } } + private void setLine(CharSequence ln) { + lineIndex++; + index = 0; + column = 0; + columnIsInTab = false; + + CharSequence lineContent = Parsing.prepareLine(ln); + SourceSpan sourceSpan = null; + if (includeSourceSpans != IncludeSourceSpans.NONE) { + sourceSpan = SourceSpan.of(lineIndex, 0, lineContent.length()); + } + this.line = SourceLine.of(lineContent, sourceSpan); + } + private void findNextNonSpace() { int i = index; int cols = column; blank = true; - int length = line.length(); + int length = line.getContent().length(); while (i < length) { - char c = line.charAt(i); + char c = line.getContent().charAt(i); switch (c) { case ' ': i++; @@ -343,7 +348,7 @@ private void setNewIndex(int newIndex) { index = nextNonSpace; column = nextNonSpaceColumn; } - int length = line.length(); + int length = line.getContent().length(); while (index < newIndex && index != length) { advance(); } @@ -357,7 +362,7 @@ private void setNewColumn(int newColumn) { index = nextNonSpace; column = nextNonSpaceColumn; } - int length = line.length(); + int length = line.getContent().length(); while (column < newColumn && index != length) { advance(); } @@ -372,7 +377,7 @@ private void setNewColumn(int newColumn) { } private void advance() { - char c = line.charAt(index); + char c = line.getContent().charAt(index); index++; if (c == '\t') { column += Parsing.columnsToNextTabStop(column); @@ -390,7 +395,7 @@ private void addLine() { if (columnIsInTab) { // Our column is in a partially consumed tab. Expand the remaining columns (to the next tab stop) to spaces. int afterTab = index + 1; - CharSequence rest = line.subSequence(afterTab, line.length()); + CharSequence rest = line.getContent().subSequence(afterTab, line.getContent().length()); int spaces = Parsing.columnsToNextTabStop(column); StringBuilder sb = new StringBuilder(spaces + rest.length()); for (int i = 0; i < spaces; i++) { @@ -399,9 +404,9 @@ private void addLine() { sb.append(rest); content = sb.toString(); } else if (index == 0) { - content = line; + content = line.getContent(); } else { - content = line.subSequence(index, line.length()); + content = line.getContent().subSequence(index, line.getContent().length()); } SourceSpan sourceSpan = null; if (includeSourceSpans == IncludeSourceSpans.BLOCKS_AND_INLINES) { @@ -419,7 +424,7 @@ private void addSourceSpans() { for (int i = 1; i < openBlockParsers.size(); i++) { OpenBlockParser openBlockParser = openBlockParsers.get(i); int blockIndex = openBlockParser.sourceIndex; - int length = line.length() - blockIndex; + int length = line.getContent().length() - blockIndex; if (length != 0) { openBlockParser.blockParser.addSourceSpan(SourceSpan.of(lineIndex, blockIndex, length)); } diff --git a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java index 536d8beff..2d7d2c0c9 100644 --- a/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/FencedCodeBlockParser.java @@ -31,8 +31,7 @@ public BlockContinue tryContinue(ParserState state) { int nextNonSpace = state.getNextNonSpaceIndex(); int newIndex = state.getIndex(); CharSequence line = state.getLine().getContent(); - boolean closing = state.getIndent() < Parsing.CODE_BLOCK_INDENT && isClosing(line, nextNonSpace); - if (closing) { + if (state.getIndent() < Parsing.CODE_BLOCK_INDENT && nextNonSpace < line.length() && line.charAt(nextNonSpace) == block.getFenceChar() && isClosing(line, nextNonSpace)) { // closing fence - we're at end of line, so we can finalize now return BlockContinue.finished(); } else { diff --git a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java index 596dfe690..81c60d0d1 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HeadingParser.java @@ -46,9 +46,11 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar SourceLine line = state.getLine(); int nextNonSpace = state.getNextNonSpaceIndex(); - HeadingParser atxHeading = getAtxHeading(line.substring(nextNonSpace, line.getContent().length())); - if (atxHeading != null) { - return BlockStart.of(atxHeading).atIndex(line.getContent().length()); + if (line.getContent().charAt(nextNonSpace) == '#') { + HeadingParser atxHeading = getAtxHeading(line.substring(nextNonSpace, line.getContent().length())); + if (atxHeading != null) { + return BlockStart.of(atxHeading).atIndex(line.getContent().length()); + } } int setextHeadingLevel = getSetextHeadingLevel(line.getContent(), nextNonSpace); diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index b81d67676..021fc9255 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -23,7 +23,6 @@ public class InlineParserImpl implements InlineParser, InlineParserState { private static final Pattern UNICODE_WHITESPACE_CHAR = Pattern.compile("^[\\p{Zs}\t\r\n\f]"); private final BitSet specialCharacters; - private final BitSet delimiterCharacters; private final Map<Character, DelimiterProcessor> delimiterProcessors; private final InlineParserContext context; private final Map<Character, List<InlineContentParser>> inlineParsers; @@ -53,21 +52,14 @@ public InlineParserImpl(InlineParserContext inlineParserContext) { this.inlineParsers.put('&', Collections.<InlineContentParser>singletonList(new EntityInlineParser())); this.inlineParsers.put('<', Arrays.asList(new AutolinkInlineParser(), new HtmlInlineParser())); - this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet()); - this.specialCharacters = calculateSpecialCharacters(delimiterCharacters, inlineParsers.keySet()); + this.specialCharacters = calculateSpecialCharacters(this.delimiterProcessors.keySet(), inlineParsers.keySet()); } - public static BitSet calculateDelimiterCharacters(Set<Character> characters) { + public static BitSet calculateSpecialCharacters(Set<Character> delimiterCharacters, Set<Character> characters) { BitSet bitSet = new BitSet(); - for (Character character : characters) { - bitSet.set(character); + for (Character c : delimiterCharacters) { + bitSet.set(c); } - return bitSet; - } - - public static BitSet calculateSpecialCharacters(BitSet delimiterCharacters, Set<Character> characters) { - BitSet bitSet = new BitSet(); - bitSet.or(delimiterCharacters); for (Character c : characters) { bitSet.set(c); } @@ -166,13 +158,28 @@ private Text text(SourceLines sourceLines) { */ private List<? extends Node> parseInline() { char c = scanner.peek(); - if (c == Scanner.END) { - return null; + + switch (c) { + case '[': + return Collections.singletonList(parseOpenBracket()); + case '!': + return Collections.singletonList(parseBang()); + case ']': + return Collections.singletonList(parseCloseBracket()); + case '\n': + return Collections.singletonList(parseLineBreak()); + case Scanner.END: + return null; + } + + // No inline parser, delimiter or other special handling. + if (!specialCharacters.get(c)) { + return Collections.singletonList(parseText()); } - Position position = scanner.position(); List<InlineContentParser> inlineParsers = this.inlineParsers.get(c); if (inlineParsers != null) { + Position position = scanner.position(); for (InlineContentParser inlineParser : inlineParsers) { ParsedInline parsedInline = inlineParser.tryParse(this); if (parsedInline instanceof ParsedInlineImpl) { @@ -190,20 +197,8 @@ private List<? extends Node> parseInline() { } } - switch (c) { - case '[': - return Collections.singletonList(parseOpenBracket()); - case '!': - return Collections.singletonList(parseBang()); - case ']': - return Collections.singletonList(parseCloseBracket()); - case '\n': - return Collections.singletonList(parseLineBreak()); - } - - boolean isDelimiter = delimiterCharacters.get(c); - if (isDelimiter) { - DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c); + DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c); + if (delimiterProcessor != null) { List<? extends Node> nodes = parseDelimiters(delimiterProcessor, c); if (nodes != null) { return nodes; @@ -481,8 +476,10 @@ private Node parseLineBreak() { private Node parseText() { Position start = scanner.position(); scanner.next(); - while (scanner.hasNext()) { - if (specialCharacters.get(scanner.peek())) { + char c; + while (true) { + c = scanner.peek(); + if (c == Scanner.END || specialCharacters.get(c)) { break; } scanner.next(); @@ -491,7 +488,6 @@ private Node parseText() { SourceLines source = scanner.getSource(start, scanner.position()); String content = source.getContent(); - char c = scanner.peek(); 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; diff --git a/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java b/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java index 7ec694238..b32bbaee5 100644 --- a/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java +++ b/commonmark/src/main/java/org/commonmark/parser/block/ParserState.java @@ -9,7 +9,7 @@ public interface ParserState { /** - * @return the current line being parsed (full line) + * @return the current source line being parsed (full line) */ SourceLine getLine(); From fb6f627a6ad4a3241e5e3d1c327a9cf44a0f859a Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 11 Dec 2020 11:02:51 +1100 Subject: [PATCH 463/815] Changelog for 0.16.0 --- CHANGELOG.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d035037dc..0bc478e49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. -## Unreleased +## [0.16.0] - 2020-12-11 ### Added -- Support for including source spans on block nodes: - - Answer for "Where in the source input (line/column position and length) does this block come from?" +- Support for including source spans on block and inline nodes (#1): + - Answer for "Where in the source input (line/column position and length) does this node come from?" - Useful for things like editors that want to keep the input and rendered output scrolled to the same lines, - or start editing on the block that was selected. - - Use `includeSourceSpans` on `Parser.Builder` to enable, read data with `Node.getSourceSpans` + or start editing on the node that was selected. + - Use `includeSourceSpans` on `Parser.Builder` to enable, + either with `IncludeSourceSpans.BLOCKS` or `IncludeSourceSpans.BLOCKS_AND_INLINES` + - Read data with `Node.getSourceSpans` + - Note that enabling this has a small performance impact on parsing (about 10%) +### Changed +- In order to support source spans (see above), a few of the extension + APIs changed. It should only affect users implementing their own + extensions. See the Javadoc to see what changed. +- YAML front matter extension: Support dots in key names ## [0.15.2] - 2020-07-20 ### Fixed @@ -299,6 +307,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.16.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.0 [0.15.2]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.1...commonmark-parent-0.15.2 [0.15.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.0...commonmark-parent-0.15.1 [0.15.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.14.0...commonmark-parent-0.15.0 From b413020235384ff3cc41ee421e03f848eeccb83b Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 11 Dec 2020 11:19:04 +1100 Subject: [PATCH 464/815] Fix Javadoc warnings/errors --- commonmark/src/main/java/org/commonmark/node/SourceSpan.java | 2 +- commonmark/src/main/java/org/commonmark/package-info.java | 1 - commonmark/src/main/java/org/commonmark/parser/Parser.java | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java index 2410e9ff7..f7dbabc27 100644 --- a/commonmark/src/main/java/org/commonmark/node/SourceSpan.java +++ b/commonmark/src/main/java/org/commonmark/node/SourceSpan.java @@ -9,7 +9,7 @@ * <p> * For example, this CommonMark source text: * <pre><code> - * > foo + * > foo * </code></pre> * The {@link BlockQuote} node would have this source span: line 0, column 0, length 5. * <p> diff --git a/commonmark/src/main/java/org/commonmark/package-info.java b/commonmark/src/main/java/org/commonmark/package-info.java index e3f0e0572..e784703e9 100644 --- a/commonmark/src/main/java/org/commonmark/package-info.java +++ b/commonmark/src/main/java/org/commonmark/package-info.java @@ -1,6 +1,5 @@ /** * Root package of commonmark-java - * <p> * <ul> * <li>{@link org.commonmark.parser} for parsing input text to AST nodes</li> * <li>{@link org.commonmark.node} for AST node types and visitors</li> diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index dcdecfd5a..ae1f700b8 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -260,7 +260,6 @@ public Builder postProcessor(PostProcessor postProcessor) { * link ([title](http://)) * image (![alt](http://)) * <p> - * <p> * Note that if this method is not called or the inline parser factory is set to null, then the default * implementation will be used. * From 049c5416b91b0ea205671e79ef0a241632dc8931 Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Fri, 11 Dec 2020 02:27:57 +0000 Subject: [PATCH 465/815] [maven-release-plugin] prepare release commonmark-parent-0.16.1 --- 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 | 5 ++--- commonmark/pom.xml | 2 +- pom.xml | 24 ++++++++++++------------ 12 files changed, 24 insertions(+), 25 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 6ace6dcdf..90a35bffc 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 830679c7e..c3e97a574 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index f79d47846..863ae472e 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 68280c177..282148392 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index a3bfdce37..4d9440743 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 0c4a8b863..a163e7444 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 85724e508..dbe139400 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 8e6455a16..e92aa647e 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 84e395140..b7dcaeabe 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index cad82f51d..b3cedf8e6 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -1,11 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 4bc665269..9ea2d2313 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 7b80779f1..b9a6dd6c7 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -89,52 +89,52 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.15.3-SNAPSHOT</version> + <version>0.16.1</version> </dependency> <!-- Common test dependencies --> @@ -208,7 +208,7 @@ <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.16.1</tag> </scm> </project> From 790dba08fb1698d6d79e4eac389a5e8708511b65 Mon Sep 17 00:00:00 2001 From: bambooagent <build-agent@atlassian.com> Date: Fri, 11 Dec 2020 02:27:59 +0000 Subject: [PATCH 466/815] [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 90a35bffc..d6a19df40 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index c3e97a574..80d01118f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 863ae472e..babf54640 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 282148392..07b840e63 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 4d9440743..166ea02b1 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index a163e7444..223837493 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index dbe139400..78829c1e0 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e92aa647e..06c8ce933 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>com.atlassian.commonmark</groupId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index b7dcaeabe..aa43a6619 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index b3cedf8e6..ff1459728 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 9ea2d2313..ff5a74381 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index b9a6dd6c7..4907cedc5 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -89,52 +89,52 @@ <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.16.1</version> + <version>0.16.2-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -208,7 +208,7 @@ <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> <url>https://github.com/atlassian/commonmark-java</url> - <tag>commonmark-parent-0.16.1</tag> + <tag>HEAD</tag> </scm> </project> From 001add91d3318d7e5754d28037c97c02ba6aacb7 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 11 Dec 2020 13:26:43 +1100 Subject: [PATCH 467/815] Change version (had to burn 0.16.0, thanks maven) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc478e49..0000368ba 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. -## [0.16.0] - 2020-12-11 +## [0.16.1] - 2020-12-11 ### Added - Support for including source spans on block and inline nodes (#1): - Answer for "Where in the source input (line/column position and length) does this node come from?" @@ -307,7 +307,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[0.16.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.0 +[0.16.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.1 [0.15.2]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.1...commonmark-parent-0.15.2 [0.15.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.0...commonmark-parent-0.15.1 [0.15.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.14.0...commonmark-parent-0.15.0 From b0952daecbabbbf27fc6c2eee82112421df72346 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 11 Dec 2020 13:48:14 +1100 Subject: [PATCH 468/815] Build javadoc as well, otherwise it can fail in the middle of a release --- .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 e1c22438d..3d4893158 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: java-version: ${{ matrix.java }} - name: Build - run: mvn -B package + run: mvn -B package javadoc:javadoc coverage: runs-on: ubuntu-latest From f99163e6a2e660e0c4fc3b008391e44ef8b3422a Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 11 Dec 2020 14:24:56 +1100 Subject: [PATCH 469/815] Bump versions --- README.md | 4 ++-- commonmark-android-test/README.md | 4 ++-- commonmark-android-test/run.sh | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b9718e9f2..d5fa98f4b 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.15.2</version> + <version>0.16.1</version> </dependency> ``` @@ -233,7 +233,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.15.2</version> + <version>0.16.1</version> </dependency> ``` diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index a4c498fc4..2812e3e62 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -28,12 +28,12 @@ sdk.dir=/path_to_android_sdk path.report=../report # Version number of commonmark and extensions in maven central. -version.maven=0.15.2 +version.maven=0.16.1 # Version number of autolink in maven central (not bundled with extension jar). version.maven_autolink=0.10.0 # Version number of commonmark and extensions in project. -version.snapshot=0.14.1-SNAPSHOT +version.snapshot=0.16.2-SNAPSHOT # Version number of autolink for snapshots (not bundled in extension jar). version.snapshot_autolink=0.10.0 ``` diff --git a/commonmark-android-test/run.sh b/commonmark-android-test/run.sh index 44426d7fe..840614fdb 100755 --- a/commonmark-android-test/run.sh +++ b/commonmark-android-test/run.sh @@ -8,7 +8,7 @@ autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpressi touch test.properties echo "path.report=../report" >> test.properties -echo "version.maven=0.15.2" >> test.properties +echo "version.maven=0.16.1" >> test.properties echo "version.maven_autolink=0.10.0" >> test.properties echo "version.snapshot=$version" >> test.properties echo "version.snapshot_autolink=$autolink_version" >> test.properties From 193ce13907a6cc3197b5d65c20bbe53aa4ec9364 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Wed, 16 Dec 2020 01:16:34 +0300 Subject: [PATCH 470/815] Use lint for Android compatibility --- commonmark-android-test/README.md | 68 +----- commonmark-android-test/app/build.gradle | 73 ++----- commonmark-android-test/app/lint.xml | 25 +++ .../android/test/AndroidSupportTest.java | 200 +++++++++--------- commonmark-android-test/build.gradle | 6 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 58695 bytes .../gradle/wrapper/gradle-wrapper.properties | 3 +- commonmark-android-test/gradlew | 109 ++++++---- commonmark-android-test/gradlew.bat | 30 ++- commonmark-android-test/run.sh | 20 -- 10 files changed, 240 insertions(+), 294 deletions(-) create mode 100644 commonmark-android-test/app/lint.xml delete mode 100755 commonmark-android-test/run.sh diff --git a/commonmark-android-test/README.md b/commonmark-android-test/README.md index 2812e3e62..458b7206b 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -1,19 +1,18 @@ commonmark-android-test ======================= -This module ensures that commonmark-java is supported on Android +This module ensures that commonmark-java is supported on Android by running `lint` checks on library sources. Requirements: * Java 7 or above -* Android SDK 15 -* Running emulator or connected android device +* Android SDK 30 Configuration ----- 1. Download Android SDK -2. Be sure that SDK Platform 15 and emulator for this platform (system image) are installed. It's recommended to use x86 +2. Be sure that SDK Platform 30 is installed. It's recommended to use x86 3. Export to PATH: `path_to_android_sdk/platform-tools` and `path_to_android_sdk/tools` 4. Create 2 properties files in commonmark-android-test @@ -22,76 +21,21 @@ Configuration sdk.dir=/path_to_android_sdk ``` -/test.properties -```properties -# Absolute or relative (./ == /app) path to test reports. -path.report=../report - -# Version number of commonmark and extensions in maven central. -version.maven=0.16.1 -# Version number of autolink in maven central (not bundled with extension jar). -version.maven_autolink=0.10.0 - -# Version number of commonmark and extensions in project. -version.snapshot=0.16.2-SNAPSHOT -# Version number of autolink for snapshots (not bundled in extension jar). -version.snapshot_autolink=0.10.0 -``` - -If you're going to test on device with Android 15 then you can skip downloading emulator. - Usage ----- -#### Run test with MAVEN version +#### Run lint checked on Mac/Linux: ```shell -./gradlew :app:connectedMavenDebugAndroidTest +./gradlew :app:lint ``` on Windows: ```bat -.\gradlew :app:connectedMavenDebugAndroidTest -``` - -#### Run test with SNAPSHOT version - -Before running tests you need to run `mvn clean install` in the root of -this repository. - -on Mac/Linux: -```shell -./gradlew :app:connectedSnapshotDebugAndroidTest +.\gradlew :app:lint ``` -on Windows: -```bat -.\gradlew :app:connectedSnapshotDebugAndroidTest -``` - - -#### Testing in CI - -on Mac/Linux: -```shell -echo no | android create avd --force -n test -t "android-15" -emulator -avd test & -adb wait-for-device -./gradlew :app:clean :app:connectedSnapshotDebugAndroidTest -adb emu kill -``` - -on Windows: -```bat -echo no | android create avd --force -n test -t "android-15" -start emulator -avd test -adb wait-for-device -gradlew :app:clean :app:connectedSnapshotDebugAndroidTest & adb emu kill -``` - -There could be problems with command `adb wait-for-device` which could be resolved by adding additional pause before running test. - Links ----- [Gradle Documentations](https://docs.gradle.org/current/userguide/userguide.html) diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 3ca56fbe9..86a7dd1e3 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -1,31 +1,15 @@ apply plugin: 'com.android.application' -def testProperties -def testPropertiesFile = file('../test.properties') -if (testPropertiesFile.canRead()) { - testProperties = new Properties() - testPropertiesFile.withInputStream { - stream -> testProperties.load(stream) - } -} - android { - compileSdkVersion 16 - buildToolsVersion "21.1.1" + compileSdkVersion 30 + buildToolsVersion "30.0.2" defaultConfig { applicationId "com.atlassian.commonmark.android.test" minSdkVersion 16 - targetSdkVersion 16 + targetSdkVersion 30 versionCode 1 versionName "1.0" - - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - - productFlavors { - maven - snapshot } compileOptions { @@ -40,45 +24,22 @@ android { exclude 'META-INF/NOTICE.txt' } - testOptions { - resultsDir = testProperties['path.report'] - } -} - -repositories { - flatDir { - dirs '../../commonmark/target', - '../../commonmark-ext-autolink/target', - '../../commonmark-ext-gfm-strikethrough/target', - '../../commonmark-ext-gfm-tables/target', - '../../commonmark-ext-heading-anchor/target', - '../../commonmark-ext-ins/target', - '../../commonmark-ext-yaml-front-matter/target', - '../../commonmark-test-util/target' + // 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" } + } } } dependencies { - androidTestCompile 'com.android.support.test:runner:0.4.1' - androidTestCompile 'com.android.support.test:rules:0.4.1' - - androidTestCompile ':commonmark-test-util-' + testProperties['version.snapshot'] - - androidTestMavenCompile 'org.nibor.autolink:autolink:' + testProperties['version.maven_autolink'] - androidTestMavenCompile 'com.atlassian.commonmark:commonmark:' + testProperties['version.maven'] - androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-autolink:' + testProperties['version.maven'] - androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:' + testProperties['version.maven'] - androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-gfm-tables:' + testProperties['version.maven'] - androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-heading-anchor:' + testProperties['version.maven'] - androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-ins:' + testProperties['version.maven'] - androidTestMavenCompile 'com.atlassian.commonmark:commonmark-ext-yaml-front-matter:' + testProperties['version.maven'] - - androidTestSnapshotCompile 'org.nibor.autolink:autolink:' + testProperties['version.snapshot_autolink'] - androidTestSnapshotCompile ':commonmark-' + testProperties['version.snapshot'] - androidTestSnapshotCompile ':commonmark-ext-autolink-' + testProperties['version.snapshot'] - androidTestSnapshotCompile ':commonmark-ext-gfm-strikethrough-' + testProperties['version.snapshot'] - androidTestSnapshotCompile ':commonmark-ext-gfm-tables-' + testProperties['version.snapshot'] - androidTestSnapshotCompile ':commonmark-ext-heading-anchor-' + testProperties['version.snapshot'] - androidTestSnapshotCompile ':commonmark-ext-ins-' + testProperties['version.snapshot'] - androidTestSnapshotCompile ':commonmark-ext-yaml-front-matter-' + testProperties['version.snapshot'] + implementation('org.nibor.autolink:autolink:0.10.0') } diff --git a/commonmark-android-test/app/lint.xml b/commonmark-android-test/app/lint.xml new file mode 100644 index 000000000..3507f11d5 --- /dev/null +++ b/commonmark-android-test/app/lint.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<lint> + <!--Method considered overridden by Dalvik--> + <issue id="DalvikOverride" severity="fatal"/> + + <!--Package not included in Android--> + <issue id="InvalidPackage" severity="fatal"/> + + <!--Using Private APIs--> + <issue id="PrivateApi" severity="fatal"/> + + <!--Potential AAPT crash--> + <issue id="AaptCrash" severity="fatal"/> + + <!--Restricted API--> + <issue id="RestrictedApi" severity="fatal"/> + + <!--Calling new methods on older versions--> + <issue id="NewApi" severity="fatal"/> + + <issue id="AllowBackup" severity="ignore"/> + <issue id="FullBackupContent" severity="ignore"/> + <issue id="MissingApplicationIcon" severity="ignore"/> + <issue id="GradleDependency" severity="ignore"/> +</lint> diff --git a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java index 26fd2c360..1a27dd720 100644 --- a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java +++ b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java @@ -1,100 +1,100 @@ -package com.atlassian.commonmark.android.test; - -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.heading.anchor.HeadingAnchorExtension; -import org.commonmark.ext.ins.InsExtension; -import org.commonmark.node.Node; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.testutil.TestResources; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; - -import java.util.Collections; - -import static org.junit.Assert.assertNotNull; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class AndroidSupportTest { - - private String spec; - - @Before - public void setUp() throws Exception { - spec = TestResources.readAsString(TestResources.getSpec()); - } - - @Test - public void parseTest() throws Exception { - Parser parser = new Parser.Builder().build(); - - Node document = parser.parse(spec); - - assertNotNull(document); - } - - @Test - public void autolinkExtensionTest() throws Exception { - parseWithExtensionsTest(AutolinkExtension.create()); - } - - @Test - public void strikethroughExtensionTest() throws Exception { - parseWithExtensionsTest(StrikethroughExtension.create()); - } - - @Test - public void tablesExtensionTest() throws Exception { - parseWithExtensionsTest(TablesExtension.create()); - } - - @Test - public void headingAnchorExtensionTest() throws Exception { - parseWithExtensionsTest(HeadingAnchorExtension.create()); - } - - @Test - public void insExtensionTest() throws Exception { - parseWithExtensionsTest(InsExtension.create()); - } - - @Test - public void yamlFrontMatterExtensionTest() throws Exception { - parseWithExtensionsTest(YamlFrontMatterExtension.create()); - } - - @Test - public void htmlRendererTest() throws Exception { - Parser parser = Parser.builder().build(); - HtmlRenderer renderer = HtmlRenderer.builder().build(); - - String renderedString = renderer.render(parser.parse(spec)); - - assertNotNull(renderedString); - } - - private void parseWithExtensionsTest(Extension extension) throws Exception { - Parser parser = Parser.builder() - .extensions(Collections.singletonList(extension)) - .build(); - - Node document = parser.parse(spec); - assertNotNull(document); - - HtmlRenderer renderer = HtmlRenderer.builder() - .extensions(Collections.singletonList(extension)) - .build(); - - String renderedString = renderer.render(document); - assertNotNull(renderedString); - } -} +//package com.atlassian.commonmark.android.test; +// +//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.heading.anchor.HeadingAnchorExtension; +//import org.commonmark.ext.ins.InsExtension; +//import org.commonmark.node.Node; +//import org.commonmark.parser.Parser; +//import org.commonmark.renderer.html.HtmlRenderer; +//import org.commonmark.testutil.TestResources; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +// +//import android.support.test.runner.AndroidJUnit4; +//import android.test.suitebuilder.annotation.SmallTest; +// +//import java.util.Collections; +// +//import static org.junit.Assert.assertNotNull; +// +//@RunWith(AndroidJUnit4.class) +//@SmallTest +//public class AndroidSupportTest { +// +// private String spec; +// +// @Before +// public void setUp() throws Exception { +// spec = TestResources.readAsString(TestResources.getSpec()); +// } +// +// @Test +// public void parseTest() throws Exception { +// Parser parser = new Parser.Builder().build(); +// +// Node document = parser.parse(spec); +// +// assertNotNull(document); +// } +// +// @Test +// public void autolinkExtensionTest() throws Exception { +// parseWithExtensionsTest(AutolinkExtension.create()); +// } +// +// @Test +// public void strikethroughExtensionTest() throws Exception { +// parseWithExtensionsTest(StrikethroughExtension.create()); +// } +// +// @Test +// public void tablesExtensionTest() throws Exception { +// parseWithExtensionsTest(TablesExtension.create()); +// } +// +// @Test +// public void headingAnchorExtensionTest() throws Exception { +// parseWithExtensionsTest(HeadingAnchorExtension.create()); +// } +// +// @Test +// public void insExtensionTest() throws Exception { +// parseWithExtensionsTest(InsExtension.create()); +// } +// +// @Test +// public void yamlFrontMatterExtensionTest() throws Exception { +// parseWithExtensionsTest(YamlFrontMatterExtension.create()); +// } +// +// @Test +// public void htmlRendererTest() throws Exception { +// Parser parser = Parser.builder().build(); +// HtmlRenderer renderer = HtmlRenderer.builder().build(); +// +// String renderedString = renderer.render(parser.parse(spec)); +// +// assertNotNull(renderedString); +// } +// +// private void parseWithExtensionsTest(Extension extension) throws Exception { +// Parser parser = Parser.builder() +// .extensions(Collections.singletonList(extension)) +// .build(); +// +// Node document = parser.parse(spec); +// assertNotNull(document); +// +// HtmlRenderer renderer = HtmlRenderer.builder() +// .extensions(Collections.singletonList(extension)) +// .build(); +// +// String renderedString = renderer.render(document); +// assertNotNull(renderedString); +// } +//} diff --git a/commonmark-android-test/build.gradle b/commonmark-android-test/build.gradle index 027fee4d9..4de776573 100644 --- a/commonmark-android-test/build.gradle +++ b/commonmark-android-test/build.gradle @@ -1,18 +1,22 @@ buildscript { repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:4.0.2' } } allprojects { repositories { jcenter() + google() } } task clean(type: Delete) { delete rootProject.buildDir } + + diff --git a/commonmark-android-test/gradle/wrapper/gradle-wrapper.jar b/commonmark-android-test/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch literal 58695 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 literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pw<qsXkJ z#UKj`c8#DW2he(+i|9&^{QOxN1tjFeAl4C$wk<6$!Hu;tK;hS+!ekG5&mZT4iNNbq ziXSvf1S2Gd0gfLUJqYVci|hMuS_X7Y`=kA}2Zz5Z7Ww~~qq3*7i>a-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~<f_R)I<|+W)L)=C<FS+*s?z=kN7{Q21?( zY>6VHL4lzmy;l<MYEx8~KG5K1pT0U^i|oh&-U7R6D8Y9REPu%~l)e8$xB#sMbWmvv zdgy#xQFxTSYfpKoCRFwc-{JK1lOs(5m)=NY2VX=A=W?u$y68?sWcxO6T^SdVqIOJ` z0<%h8kicZ>Sdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@<!(+el1|4j{yt1~Vs7wpw5x-FtDD(A;*hOxY&zkq)^SRM z$4w{(lTeXd2wV5XP{QhK%SGG1x}!yMrKE>Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp<C4N8t_wMpvEBE+9!@1(Gy>0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{I<nV^EgiGUNsLgEDs zkVZoVfS|G^$t~wk@w~l|_P;}a-gefH^GX*>Au*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z<b`p}fZ<O_Dm=_!2o?vmD;ZE+PCPca6)ju-xpg^$g`Ft}nN&PoK4`V6F!klpX?$+> zGh0A%o~bd$iYvP!egRY{hObDIvy_vXAOkeTgl5o!33m!l4VLm@<-FwT0+k|yl~vUh z@RFcL4=b(QQQmwQ;>FS_e96dyIU`jmR%&&Amxcb8^&?wvpK{_V_IbmqHh);$hBa~S z;^ph!k~noKv{`Ix7Hi&;Hq%y3wpqUsYO%HhI3Oe~HPmjnSTEasoU;Q_UfYbzd?Vv@ zD6ztDG|W|%xq)xqSx%bU1<hc*Wnq_LM0m(V<5VyJAiq#_@ZfKikrD4o%n&WO4q2ej zIfyxDg(==DqbX`j=AvfHCX(KgVHyVe4WdRsQSTv_vR<)f4eJ?vBVt|h*)r9hD0!qP zADW&jInS-o>f>fF#;p9g=Hnjph>Pp$ZHaHS@-DkHw#H&vb1gARf4A*zm3Z75QQ6l( z=-MPMjish$J$0I49EEg^Ykw8IqSY`XkCP&TC?!7zmO`ILgJ9R{<i@p=4;z_i+sUG) z0H)plxmrLFLJK@TX@g=ScvqG2p7(U*Qm9_23pWT5bI8fwu`7Z=jgk^X%~85x$${F{ z1e@9O3|NYMRL<+eQ@jDzgV`koqd!0ed!NJT0|TqwXI0vhyJ*Rx)W2jQ1dHJAQpah2 zD?l1K_i<FzR$c(nvUd?#!ZnJ1(sXmlXjQ!S6vd@pAGv|b=-w0bWA<-F>56s-ZY$f> zU9GwXt`(^0LGOD9@WoNFK0owGKDC1)QACY_r#@IuE2<`tep4B#I^(PRQ_-Fw(5nws zpkX=rVeVXzR;+%UzoNa;jjx<&<VYAS1p4G0Nz6N1)Ty^N(!C@$Hdx_Lajhe)Wta9` zC^Xt(bd*jqqL^Wgm6LZlQYCis?7O<F8tCG9vmC{=JVts=z_(%waDz*rELTuiTWE!V z#^5HTs}5ycsg)}Q+MkSRiA~SNR7?Yz9|<*IWeIh!5*YBnkr~@jmy$nc3@;cAw3t~~ zrO2txz)VL~#h-@P+icqy)Lh-{+Fyx%rGW&886dL>@ABmU5X926KsQsz40o*{@47S2 z)p9z@lt=9?A2~!G*QqJWYT5z^CTeckRwhSWiC3h8PQ0M9R}_#QC+lz>`?kgy2DZio zz&2Oz<i!GDlpRuj0;341$n>o=yTXVf-?&E;_t`qY<To&+GNQx^TbA!#Vo1eA2Fa5| z)EJG?^-6#e_uJ2n3b*&ZIiu((@V|j}k}0gKDz~`cOd$1y*~6HI7eLCUcCNSnjGFZj z9U2KH3r4RoIx5~ko|m5j9q+sY8dqa%aN!JDGDW-!x0pgnYfjv#$rmC(j6gWzS|Ma? z=YzVI^Orbj6!9?3zA+iVzuggd9f1`6(9ic#5{rEqsW)U&<QWvbC9!^>{Oy>?+7+I= zWl!tZM_YCLmGXY1nKbIHc;*Mag{Nzx-<P4@aJOV*2fLzG!@ZWhwOPCxitK@~wHm-D z1#!#3v^uQVn`zvEhkgS|%+BD3r0=A*BS75q`-e~Huc)1f+*vVD$&Z0fea~m?>#yA{ zTATrWj;Nn;NWm6_1#0zy9SQiQV=38f(`DRgD|RxwggL(!^`}lcDTuL4RtLB2F5)lt z=mNMJN|1gcui=?#{NfL{r^nQY+_|N|6Gp5L^vRgt5&tZjSRIk{_*y<3^NrX6PTkze zD|*8!08ZVN)-72TA4Wo3B=+Rg1s<WiEcjrtmrFP?-xL`yZw%T20ll1iC&(Z%Bcm6_ z`-1;huVP#q&|&a5ZbSI%S!n;WS5f~*SSxIAXJ%>c>SX9*X>a!rR~ntLVYeWF5MrLl zA&1L8oli@9ERY|geFokJq^O$2hEpV<D5M>pIW8G>PPH0;=|7|#AQChL2Hz)4XtpAk zNrN2@Ju^8y&42HCvGddK3)r8FM?oM!3oeQ??bjoYjl$2^3|T7~s}_^835Q(&b>~3} z2kybqM_%CIKk1KSOuXDo@Y=OG2o!SL{Eb4H0-QCc+BwE8x6{rq9j$6EQUYK5a7JL! z`#NqLkDC^u0$R1Wh@%&;yj?39HRipTeiy6#+?5OF%pWyN{0+dVIf*7@T&}{v%_aC8 zCCD1xJ+^*uRsDT%lLxEUuiFqSnBZu`0yI<gg`ef@G-s?|xXp3&RHqB9NzDVz5&E`< zn|5Bf$qFi6W#8J=R=C#@*NEn*RlfXsRY-Dp`<X>FSv*ajhO^DNoi35o1**16bg1JB z{jl8@msjlA<e`^h=Z8oA=OKW51p)^CZPw4ld;k`T3Wbt#0U111K=bzp3&)WWGDJ3W zC$=E!w=~$d0hCe`rWhqxnkVh;@K9>n3`qW{1^SIklxN^q#w|#gqFgkAZ4xtaoJN*u z{YUf|`W)RJfq)@6F&LfUxoMQz%@3SuEJHU;-YXb7a$%W=2RWu5;j44cMjC0oYy|1! zed@H>VQ!7=f~DVYkWT0nfQfAp*<@FZh{^;wmhr|K(D)i?fq9<M3WP)O7zF<!PXt`n zW=w*IU$gzHgkeL!Y#;^g!Uak2OrTjGF$uJQowOknjs)=lf@;GAns|qZR(LZ&lw<_% zK?F?j4YZ)`)Zp=+aiwKz**}z|1eVjpzM=osRDTkdRQ>r2FEIatP=^0(s{f8GBn<8T zVz_@sKhbLE&d91L-?o`13zv6PNeK}O5dv>f{-`!ms#4U+JtPV=fgQ5;iNPl9Hf&9( zsJSm<p93&4gDZYcC2+<7H?f{~@$%kIC|f;(sfSJPne&``?&F-b-tX6YdZ5tT#t^rt zG7n*4Trrlc-(?Z(tS_YZQQ)Y?cMNpy$W&yiGUXo9gJe;lj%qcGlGBn-dcR^$3Fs_5 zu$kvCjEJ}Ui^X()cP4a^({s}oUt2RUP)q6z;smMW*oy#5qLxHZ1AswFOgnx7&IWN8 z06xM`SqgQglTAY+biCAA&cAIUvPu}a?TSiZN0N?-7LLiLssgVVHzY-nVi;4A;=x8n zO@ffaX-#$GbDV7Obd*ixvC)R*RQjKS9H@%{Paz{U7%S0Xmf~WQNnvg%F`O$hqQ9eI zE(1$YopB9|P=m%@#H(edX~W7&80!AGzMdT380nbkEVNKm_EM#o(B-h0YJ@{h$xsW% zXRvL0C~=%mcW2_pBSd!XAdg|dkjG0yPtZOyae9jm>5iXIqN7|;I5M08MjUJ{J2@M3 zYN9ft?xIjx&{$K_>S%;Wfwf9N>#|ArVF^shFb9vS)v9Gm00m_%^wcLxe;gIx$7^xR zz$-JDB|>2tnGG@Rrt@R>O40AreXSU|kB3Bm)NILHl<Qem81qAZwnTf&VXu)~D><r) z18g#In{Sk6?}F44@Mv+_n7C>rcQ&jak^+~b`)2;otjI(n8A_X~kvp4N$+4|{8IIIv zw*(i}tt+)Kife9&xo-TyoPffGYe;D0a%!Uk(Nd^m?SvaF-gdAz4~-DTm3|Qzf%Pfd zC&tA;D2b4F@d23KV)Csxg6fyOD2>pLy#n+rU&KaQU*txfUj&D3aryVj!Lnz*;xHvl zzo}=X>kl0mBeSRXoZ^SeF94hlCU*cg+b}8p#>JZvWj8gh#66A0ODJ`AX>rubFqbBw z-WR3Z5`33S;7D5J8nq%Z^JqvZj^l)wZUX#7^q&*R+XVPln{wtnJ~;_WQzO{BIFV55 zLRuAKXu+A|7*2L*<_P${>0V<tRuzAkdWzz+5h7jo8oP>dVjlC|n^@lRi}r?wnzQQm z3&h~C3!4C`w<92{?Dpea@5nLP2RJrxvCCBh%Tjobl2FupWZfayq_U$Q@L%$uEB6#X zrm_1TZA8FEtkd`tg)a_jaqnv3BC_O*AUq-*RNLOT)$>2D!r>FZdH&$x5G_FiAPaw4 zgK*7>(qd6R?+M3s@h>Z|H%7eGPxJWn_U$w`fb(Mp+_IK2Kj37YT#Xe5e6KS-_~mW} z`NXEovDJh7n!#q4b+=ne<7uB7Y2(TAR<3@PS&o3P$h#cZ-xF$~JiH6_gsv9v(#ehK zhSB_#AI%lF#+!MB5DMUN+Zhf}=t~{B|Fn{rGM?dOaSvX!D{oGXfS*%~g`W84JJAy4 zMdS?9Bb$vx?`91$J`pD-MGCTHNxU+SxLg&QY+*b_pk0R=A`F}jw$pN*BNM8`6Y=cm zgRh#vab$N$0=XjH6vMyTHQg*+1~gwOO9yhnzZx#e!1H#|Mr<`jJGetsM;$TnciSPJ z5I-R0)$)0r8ABy-2<wkPatn?)b`7wG?VPZW69wN;gF6Hcw@+SI3wBBUtVM;^nDxr; zOw@}HZR0+=4Eg>y&`2$33xx#%1mp+@1Vr|q_e=#t7YjjWXH#3F|Fu<G#+-tE2K7 zOJkYxNa74@UT_K4CyJ%mR9Yfa$l=z}lB(6)tZ1Ksp2bv$^OUn3Oed@=Q0M}imYTwX zQoO^_H7SKzf_#kPgKcs%r<l0PY{=(svpsKlZ?_+BrT_ds-sT3H-qlBnDQJT*ucs0m zbPX4qaZuhbp%FSZ{Y7p~l#@)5KT1s&TSZ}Wg$qSwcXKCew=8NS!4W1e{Q|?!9eG^v zQWZ8M>4BFUyAK9MzfYReHCd=l)YJEgPKq-^z3C%4lq%{&8c{2CGQ3jo!iD|wSEhZ# zjJoH87Rt{4*M_1GdBnBU3trC*hn@KCFABd=Zu`hK;@!TW`hp~;4Aac@24m|GI)Ula z4y%}ClnEu;AL4XVQ6^*!()W#P>BYC@K5mw7c4X|Hk^(mS9ZtfMsVLoPIiwI?w_X0- z#vyiV5q9(xq~fS`_FiUZw->8Awktga<tS2TUBwv(V(GLz>>2SrWyvZ|h@LVFtnY#T z%OX30{<CR{1dX4_88vcTN2W_z$|=%RBjz{I5#_g=`Y>yiSov4!43kFd(8)cPRMyrN z={af_ONd;m=`^wc7lL|b7V!;zmCI}&8qz=?-6t=uOV;X>G{8pAwf9UJ`Hm=ubIbgR zs6bw3pFeQHL`1P1m5fP~fL*s?rX_|8%tB`Phrij^Nkj{o0oCo*g|ELexQU+2<b0b^ zH6Si*&}T1;-IIYpVfU-sbg3b!AY(3~!W5e052of>gt66=7}w5A+Qr}mHXC%)(ODT# zK#XTuzqOmMsO~*wgoYjDcy)P7G`5x7mYVB?DOXV^D3nN89P#?cp?A~c%c$#;+|10O z8z(C>mwk#A*LDlpv2~JXY_y_OLZ*Mt)>@gqKf-Ym+cZ{8d%+!1xNm3_xMygTp-!A5 zUTpYFd=!lz&4IFq)Ni7kxLYWhd0o2)ngenV-QP@VCu;147_Lo9f~=+=Nw$6=xyZzp zn7zAe41Sac>O60(dgwPd5a^umFVSH;<7vN<lRA{u-cX8bx?_y2UOPIBB44tjb`IcO zp6}D0iM}Wf<X;-2WV;9t=wB-R;oPOu2a3g;jpSOFhzk7@VMA`wh-)A@F!Dfu=sqs} zh3d@>>o;}YlMYhBZFZ}-sz`P^3oAI>SCZy&zUtwKSewH;CYysPQN7H>&m215&e2J? zY}<s6FT=4{CBlz(b$RJTR}(J~<Q5p;B(@DqA^RlVZDzx`pO{;1k_P+NqV@g@*Y^!b z9U}UTEbwfq?TYFSxZwif?AY?14KWTB-2?`^zPb+j`a1PC#>>5N-LhaDeRF~C0cB>M z7@y&xh9q??*EIKnh*;1)n-WuSl6HkrI?OUiS^lx$Sr2C-jUm6zhd{nd(>#O8k9*kF zPom7-%w1NjFpj7WP=^!>Vx^6SG^r`r+M&s7V(uh~!T7aE;_ubqNSy)<5(Vi)-^Mp9 zEH@8Vs-+FEeJK%M0z3FzqjkXz$n~BzrtjQv`LagAMo>=?dO8-(af?k@UpL5J#;18~ zHCnWuB(m6G6a2gDq2s`^^5km@A3Rqg<a|J50~@3o(TXhi78&sK_*K=g#4V!%w9WX1 zYs8Dti=m0CkoIjQ_@!zEcwxjMKg29Zyu~nvF&-}n4!GMSQf#Q}f0>-oHZ68v5NqVc zHX_Iw!OOMhz<DqCkmE7ZfX9>S=gfR7k;K1gkEwuFs|MYTeNhc0js>Wo#^=wX4T<`p zR2$8p6%A9ZTac;OvA4u#Oe3(OUep%&QgqpR8-&{0gjRE()!Ikc?ClygFmGa(7Z^9X zWzmV0$<8Uh)#qaH1`2YCV4Zu6@~*c*bhtHXw~1I6q4I>{92Eq+ZS@_nSQU43b<g9G zTKyjN3#5Qd%<Udspb$!V=(rzilam)(#Fs}JSO&nUI(jg<k$kYl(6yA9p$mgFy3e(* zC3Q3=H8|5%6t#ASoV7&?IEY8p2Gba)IVm>Zyidk@hd$j-_iL=^^2CwPcaXnBP;s;b zA4C!k+~rg4U)}=bZ2q*)c4BZ#a&o!uJo*6hK3JRBhOOUQ6fQI;dU#3v>_#yi62&Sp z-%9JJxwIfQ`@<lw;bqz7KXGLVZhZoATNK$7+zaq2l8<MFt|mtHt_xfk+;T^dvn|o; zq?TCp3~w(TQ;2Ced?j+^z4{&N?QM<7<uDvgkT9(3GoQ@v8}d>w(_qH0J0z~(lbh`P zHoyp2?Oppx^WXwD<~20v!lYm~n53G1w<bq30_n<2t=n9tt1+;ELL4Qmpi@2S;>*Ej z9^B*j@lrd>XGW43ff)F;5k|HnGGRu=wmZG9c~#%vDWQHlOIA9(;&TBr#yza{(?k0> zcGF&nOI}JhuPl`kLViBEd)~p2nY9QL<pZYd>dX42u9C~EUWsl-@CE;05y@^V1^wM$ z&zemD1oZd$Z))kEw9)_Mf+X#nT?}n({(+aXHK2S@j$MDsdrw-iLb?#r{?Vud?I5+I zVQ8U?LXsQ}8-)JBGaoawyOsTTK_f8~gFFJ&lhDLs8@Rw$ey-wr&eqSEU^~1jtHmz6 z!D2g4Y<b?~IDfEG@|?}~@NK5%EfL8m#@}<DdF^rh-TmzQzJ%ZHlN`o&KN5&i%pd<p zd)`MogP&IP-kj;}o?vMX8{j)4{4uo>h?3VE*W8=*r&G`?u?M~AdO;uTRPfE(@=Gkg z7gh=EGu!6VJJ?S_>|5ZwY?dGFBp3B9m4J1=7u=HcGjsCW+y6`W?OWxfH?S#X8&Zk& zvz6tWcnaS1@~3FTH}q_*$)AjYA_j;yl0H0{I(CW7Rq|;5Q2>Ngd(tmJDp+~qHe_8y zPU_fiCrn!SJ3x&>o6;WDnjUVEt`2fhc9+uLI>99(l$(>Tzwpbh>O775OA5i`jaBdp zXnCwUgomyF3K$0tXzgQhSAc!6nhyRh_$fP}Rd$|*Y7?ah(JrN=I7+)+Hp4BLJJ2P~ zFD!)H^uR2*m7<GPV#LHgsC6vhayHW))RX)=VMbT5C%W^{xi!j-vMZEwaizER{bn~` z*%zcR6b|kIhmXa<;AK(bIsa28`ImW9qn5agw2WKSdP;oS=2TPFicFCWts(xLktJ_m zD>GQZpLUVS#R3^?2wCd}<N5gl#_ofxV$#HpCC1rQ&C~<QDZZ_2sngr~G&@FH3O56i zpYU+i^QFIyPm>(gcFcz!u5KN9ld<NJ){7KQD8%x=J0Jw6dq*TNp7ct#f48DN|88K# zHN1PuP|UV`&5NH6UASf3;wmy!yfBZkt|!hg9d$RGx6Wl#lBtYzLYw$)guuvQcbZZC zj7gb!+EQSI=jz>NJdh@%onf06z9m~T0n;dqg6<uAT+7VbZL2g(0eDJbOi8&OREsL# zB+Z?@j2VQ0s4FHSqk1~JRK^IY#?axJDuN?H+C^M2;K4B1fQ{Df4T8j=B2vyZG&LP7 zui$FHVNSS%MFs}(Qi>@?>G@S|rPO*Kj>{su+R|7bH>osA&uD4eqxtr**k($ii`uO? z7-&VkiL4Rp3S&e+T}2Z#;NtWHZco(v8O3QMvN0g7l8GV|U2>x-DbamkZo5)bjaSFR zr~Y9(EvF9{o*@|nBPj+e5o$_K`%TH1hD=|its}|qS^o6EQu_gOuDUH=Dtzik;P7G$ zq%_T<>9O}bGIB?;IQ*H`BJ5NWF6+XLv@G7aZwcy(&BoepG~u`aIcG>y+;J7+L=wTZ zB=%n@O}=+mjBO%1lMo6C0@1*+mhBqqY((%QMUBhyeC~r*5WVqzisOXFncr*5Lr0q6 zyPU&NOV}Vt2jl>&yig4I6j93?D>Ft=keRh=Y;3*^Z-I26nkZ#Jj5OJ89_?@#9lNjp z#gfAO6i937)~I|98P%xAWxwmk(F&@lTMx63*FZ~2<ClX?bRjNezMp1%S|D?RB{q!< zEggd%ffIqfo(4Bfx1#P{GLZ``)ZIGI_-D9_&deXQqYt61WxAuBfU`SCW=`I?(Sx0w z1xkBAcgJ#_E>b{NHU+}EV8+kMAB0bM*Zn#&7ubt98!PT^ZcMOfwMgkYz6+;?CKbvV zQ}Z@s_3JcMPhF&y1?}9uZFIBiPR3g7lf=+XEr9Bl%zRfGcaKb*ZQq5b35ZkR@=JEw zP#iqgh2^#@VA-h)>r`7R-$1_ddGr&oWWV$r<x}DZ#bg++!uinCKq;~vC^vT3#yH_E zs$t6fd0!OeHt{39E9Ex*H(#oq;dj+T-OVaBef>x;pkG0Yohp9p@In_p)hKvMo@qIv zcN2t{23&^Nj=Y&gX;<A7`MA@yDtof9<+6Ka<)s5q3vdT|2G(LkRiq{LQ}ZLX%0?}` zoBYW`p?I(xW*)#})K&792W~c8U7Vfv9k`-&Bj0_im@<_<{^BAxovM7Jg)-zd)FL77 z0GdgMk8H`jJ3_uFnKTf)R#VG`K)vMH*+we-BX5<M(Gv`O^;7g=tA0k}c*d@<H$0Nw z^QAao)GY(CaU6bJ_V{C7?pAyCSI}G(s#2AnRMy}T6})4`;6f^2%?O#x{d>*vJ;kjM zHE2`jtjVRRn;=WqVAY&m$z=IoKa{>DgJ;To@OPqNbh=#jiS$WE+O4TZIOv?niWs47 zQfRBG&W<EaZbDRqg?K!_2U-S@${R>GmU~>2O{}h17wXGEnigSIhCkg%N~|e?hG8a- zG!Wv&NMu5z!*80>;c^G9h3n#e<GnZGP4AxF02!2}l|O+3D#j^WP~|FpQfA6|hRdwS zIa{A9d~$Xw??68C#;=i47E!7>>SBt5JpCm0o-03o2u=@v^n+#6Q^r#96J5Q=Dd=>s z(n0{v%yj)=j_Je2`DoyT#yykulwTB+@ejCB{dA7VUnG<H2k=F!^!asn0Syeqjv*PH zM}MxDpnKeKGLA)cG!HXoIT#2&OIlx@1ore?mc%Gn*=YT^g6v7i7s@j|U%Cp9MUIFh zv(GiuR(0m!jP##O8E;~swen$y%KT`ZC(h}-be#o4T{v@E9Bv!Az4`?QEb@(xkMt!p z^)!YNe~4A^=QJTge?gJYXOA2vaF5>>4`oE?GFV4sx$5;%9&}yxfz<-wWk|IlA|g&! zN_Emw#w*2GT=f95(%Y1#Viop;Yro3SqUrW~2`Fl?Ten{jAt==a>hx$0$zXN`^7>V_ zG*o7iqeZV)<S6=vQwnvz+BX*pjK+#g2&d}e&7sf7T*j9VUey;68OC&FKLaT}6`D|) zUlYnV8^wicy=hE;&$nEiVbo0ppQ)+_VS>txtHUU2#SDTyU#@paP;_yxp!SAG##cB= zr@LoQg4f~Uy5QM++W`WlbNrDa*U;54`3$T;^YVNSHX4?%z|`B~i7W+kl0wBB`8|(l zAyI6dXL&-Sei0=f#P^m`z=JJ`=W;PPX18HF;5AaB%Zlze`#pz;t#7B<M-GIl$j*TQ z?KP!fG0}GOmyE=70u40;@tK<Qp$qe4=K?NH`4m2H-#2rag*boGdFs0!Vi)dSdhqV5 zpcj&&SEf-eS5r9wLXd7GNV&F&-R`p+Vd^6T?aO7Gatc($Mf3pia>zq0;k8IyvdK=R zBW+4GhjOv+oNq^~#!5(+pDz)Ku{<X$tTXZRJn{h&rj}s7rZIu)WmWA*fY%J|+BmN@ zLaTh;a1fE!z&)>u60bVjyym8Or8L;iqR|qTcxEKTRm^Y%QjFYU=ab+^a|!{!hYc+= z%Qc02=prKpzD+jiiOwzyb(dELO|-iyWzizeLugO!<1(j|3cbR!8Ty1$C|l@cWoi?v zLe<5+(Z-eH++=fX**O-I8^ceYZgiA!!dH+7zfoP-Q+@$>;ab&~cLFg!uOUX7h0r== z`@*QP9tnV1cu1!9pHc43C!{3?-GUBJEzI(&#~vY9MEUcRNR*61)mo!RG>_Yb^rNN7 zR9^bI45V?3Lq`^^BMD!GONuO4NH#v9OP3@s%6*Ha3#S<?){D`p=@UhPJW%rI1>*;f z6JEi)qW#Iq#5BtIXT9Gby|H?NJG}DN#Li82kZ_Rt1=T0Z@U6OAdyf}4OD|Sk^2%-1 zzgvqZ@b6~kL!^sZLO$r{s!3fQ5b<Yp-sP<2UYg<hksU52A%8|-o$8&DMB5F%z!9jR ztj6+2s|e!%TJ^)|3HFQf!#O^;K-MkF_4jHOIE^(Xskxk{TF+Kw0kSI4lT_n%|06m1 zQ3Mi|JMgp1NjLPiwXYcE-X8eiF?H12GpB->HW}8r$uTVS*iw1u8^9{YlPp_^Xm5IN zF|@)ZO<U-^xPAodX<lAEfqplo^sd~r=XsV^Dz|Foe3YWO9^tz&J2n5Lxy~s3{>ReX zB*#tE<lWYJkyX62^W(k=0>bWEX~@f)ST|s$oUKS@drycE1tYtdJ9b*<T~8vt_-YSR z>(uFTxNZ{n3BI*kF7wXgT6+@PI@vwH7iQS{1T!Nauk>fm8<V3`g4G-r!zX4MwU{d8 zCl*OywFH<hgEG9ECBvg8#yjEA$w63hu4od^uah*vC?09}`C|sZoNaM>gOLe`->Pi~ z8)3=UL_$OLl2n7QZlHt846nkYFu4V};3LpYA%5VaF#a2#d2g0&ZO~3WA%1XlerVpg zCAlM;(9<WF5Igo}TVG_p%Z}ctzS(|1i05vs<17aRnekfF+jv51k-~GwKk4WD?jLG2 zy`BxEk9KKblX-;suAOs^8#D{&_iicr?lG7~5B#?iW5B6(0hrrVcZ2ktlFcH1RAV=< zG({)|S2m7;`M6s*apu<<%a!sAZ)OnM)z&RQI?jh{d)2pmqI@&Xu^uD}b&mpaVXzF4 zKi&bewZN}S0zM!PJ1{7%``*x#sZR4L@b`dEz$euZow$t;95*jVV?3XHDsH^H*;F$! zo!2VjD>OqH@`(>Tha{*@R%twB!}1ng4V=^+R`Q{#fkRk)C|suozf-uCXrkIH2SC^C z6wlxR`yS;-U#uu#`OnD<PIu|V4$j^9VtCxo7&*YI{M0NH`ctXUcqQ`+ADzr~@|Zs} z08FnNCRz>%U<41%C4mp>LYLPIbgVO~WsT1if)Y)T*8nUB`2*(B;U_ha1NWv2`GqrZ z3MWWpT3tZ!*N@d*!j3=@K4>X*gX4A^@QPAz24?7u90AXaLiFq=Z$|5p$Ok2|YCX_Z zFgNPiY2r_Bg2BQE!0z=_N*G?%0cNITmAru*!Mws=F+F&Qw!&1?DBN{vSy%IvGRV@1 zS<NSq0fsn2?)Z`#`+aZJ6Doywvf4szK9w(2fgdL{D8f|4s{bQBClQ;$NTl2!eeqto z-XDk)h}X5;V=UDB5kQ}0zNEkQ{Vw9Is##j|s@|NzmaY+W64hr>->PARgL^XS!-aZj zi@`~LhWfD!H-L0kNv=Jil9zR0>jZLqu)cLq?$yXVyk%EteKcWbe^qh#spHJPa#?92 za(N<tKb?I1@FhMN5PDD2p_4kPz9dc?WB+15g^z?98@ALJmzeyDZ~Tr8u#q$&m6+TS zS282#ySe`Uw`48xO{}4TzYJdT--OZs2iYuSZ|~yl@|SUzHMF!-b}@8v`QP-NruK#! z0`EYBsS2sTCQ_n67SOO+{X)__IEAdx8muIPH153UDmpQdv`Pw;i*#z!C#>(Kw0<V~ zpn6Zq^(VUTS+51RdP6_<>se^$<YYFd`Je6h?~miwRG{s?SSH{$@&l|Ak}i5fEir5} zlSw1F-#ln9brgfY+M-Bl?)Q4|;oA=L@u`uJkibn?3{?j*qFikxUixB5-0#ESGkrWR zMnakR21bzhZiSB~Xff5}65`NyGiPC@a!_`m#FxRsl+7}rEF!GIwWrNn)0!*nPD!AK zrjjaZD^q?yi{@KgTX6n}Z?iNt!jzUhr+HdbdVWen?KIDDn=YA3sKA8MZniOthQ9gp zq9kDN!Q4}MRmrH!%2Xd@Ybj>7nQUQZBet;C_Dj5(2_?TdrXFYwmebq}YGQbN5Ex7M zGSCX~Ey;5AqAzEDNr%p^!cuG?&wIeY&Bm5guVg>8F=!nT%7QZTGR(uGM&IZuMw0V_ zhPiIFWm?H?aw*<a+;kuAS!&F>(v6#uVT@NEzi2h5I$cZ-n0~m$tmwdMTjG*of^Y%1 zW?Y%o*-_iMqEJhXo^!Qo?tGFUn1Mb|urN4_;a)9bila2}5rBS#hZ5wV+t1xbyF1TW zj+~cdjbcMgY$zTOq6;ODaxzNA@PZIXX(-=cT8DBd;9ihfqqtbDr9#gXGtK24BPxjZ z9+Xp>W1(s)->-}VX~BoQv$I|-CBdO`gULrvNL>;@*HvTdh@wyNf}~IB5mFnTitX2i z;>W>tlQyc2)T4Mq+f!(i3#KuK-I8Kj3Wm(UYx?KWWt8DEPR_Jdb9CE~Fjc7Rkh#gh zowNv()KRO@##-C+ig0l!^*ol!Bj%d32_N*~d!|&>{t!k3lc?6VrdlCCb1?qyoR42m zv;4KdwCgvMT*{?tJKa(T?cl|b;k4P>c&O@~g71K5@}ys$)?}WSxD;<5%4wEz7h=+q ztLumn6>leWdDk#*@{=v9p)MsvuJMyf_VEs;pJ<y{_VV!a4tk9(A1i3&q7?0(TsCti zt2t4v6;A5Rg5tt*l<z&%*51aE;pV#@VhKgWVg%@eN^?9HNw&!B<Du6!<Y0V;9ueZH zX)^on_(Lw+=^rc1?6C?qUeLQ$J^JR@u-z8RctzBHF3&3A8x|+s<MTGToeiLu8IXWh z7A*T&I<ZE9M)zIF0cb6jyil=R;tLV_=m=|nzAiatUb&#L2>h?i3z7_W@Q|3<Iv-$p zr4L-Ad1f8|Rv`U-45c4m#4Iu4BXPfse$;{u`z!SYGuqE$w@EC^O3G7bAcHkUy$13H zkOUN&$30bJ)8jPZ$h9K>p$a}P@MQ-NpMtDUBgH!h4Ia#L&POr4Qw0Tqdw^}gCmQAB z8Dgkzn?V!_@04(cx0~-pqJOpeP1_}@Ml3pCb45EJoghLows9ET13J8kt0;m$6-jO( z4F|p+JFD1NT%4bpn4?&)d+~<360$z5on`eS6{H`S>t`VS$>(D`#mC*XK6zULj1Da# zpV$gw$2Ui{07NiYJQQNK;rOepRxA>soNK~B2;>z;{Ovx`k}(dlOHHuNHfeR}7tmIp zcM}q4*Fq8vSNJYi@4-;}`@bC?nrUy`3jR%HXhs79qWI5;hyTpH5%n-NcKu&j(aGwT z1~{geeq?Jd>>HL+?2`0K8dB2pvTS=LO~tb~vx_<=iN8^rW!y@~lBTAaxHmvVQJSeJ z!cb9ffMdP1lgI=>QJN{XpM4{reRrdIt|v|0-8!p}M*Qw^uV1@Ho-YsNd0!a(os$F* zT0tGHA#0%u0j*%S>kL*73@~7|iP;;!JbWSTA@`#VHv_l_%Z7CgX@>d<wo!f?;Z%_x zWtw_K!q_M8*$()&!aPHcZ3ugW`s`40Uni{PBW%;|JdfUNBW#oJJWt#NAn;M_z>hg_ zgn0|U)SY~U-E5<aLBxcqGBaf+uYD5o5TNm63}wyoP>{QiT@(uPp#1jaz!(_3^Cbz2 z4ZgWWz=PdGCiGznk{^4TBfx_;ZjAHQ>dB4YI}zfEnTbf60lR%=@VWt0yc=fd38Ig* z)Q38#e9^+tA7K}IDG5Z~>JE?J+n%0_-|i2{E*$jb4h?|_^$HRHjVkiyX6@Y+)0C2a zA+eegpT1dUpqQFIwx;!ayQcWQBQTj1n5&h<%Lggt@&tE19Rm~Rijtqw6nmYip_xg0 zO_IYpU304embcWP+**H|Z5~%R*mqq+y{KbTVqugkb)JFSgjVljsR{-c>u+{?moCCl zTL)?85;LXk0HIDC3v*|bB-r_z%zvL6Dp__L*A~Z*o?$rm>cYux&)W=6#+Cb}TF&Kd zdCgz3(ZrNA>-V>$C{a^Y^2F!l_<AwP8>%3lFe$s(IOfLBLEJ4Mcd!y&Ah9r)7q?oc z5L(+S8{AhZ)@3bw0*8(}Xw{94Vmz6FrK&VF<D4FN)aeEY8AkUU<oHVRp64UimBNVG zPmOVQuvnjJPR(~l<0v~2(F=*u3ingep6h^NZe3yEUL_kGO?D;I{O<HXqQi)T-q()m zEw-i<FXFT+PLClyx677F%&0E?%>rJN;xB96QmqYEibFz|yHgUluA-=+yS}I-+#_Pk zN67-#8W(R^e7f!;i0tXbJgMmJZH%yEwn*-}5ew13D<_FYWnt?{Mv1+MI~u;FN~?~m z{hUnlD1|RkN}c1HQ6l@^WYbHAXPJ^m0te1woe;LDJ}XEJqh1tPf=sD0%b+Ou<!IGZ zKsgDSYE#K6n~RRbM~@WstVuh9)moF#U9n>R1aCoP>I>GBn4C24Zu$D)qg=gq;D??5 zUSj%;-Hvk_ffj-+SI{ZCp`gZcNu=L@_N}kCcs?TyMr-<T#dJlx5oxO`inNn|CKXO& zzLQK?yzl+O7g>37<G^Q(z2KFPh1%rFCK@?V*l8it<Y`;OnZeXPndYXlGvZ@!UVv^2 zD<>fhy$?a<7lt1`fZw<%$8@B6(Wgo!#!z9z{ab|x`+&;kP!(gfdY}A-GP&4Cbh-S< z1(kmgnMyB2z3ipEj5;4<{(=&<7a>A_Jl`ujUKYV@%k(oD=cD7W@8~5O=R*zdjM_y; zXwme~0wo0aDa~9rDnjF=B}Bbj|DHRQjN|?@(F^=bVFdr!#mwr|c0843k>%~5J|7|v zSY=T)iPU6rEAwrM(xTZwPio%D4y9Z4kL0bMLKvu4yd)0ZJA3<;>a2q~rEfcREn}~1 zCJ~3c?Afvx?3^@+!lnf(kB6YwfsJ*u^y7kZA?VmM%nBmaMspWu?WXq4)jQsq`9EbT zlF2zJ)wXuAF*2u|yd5hNrG>~|i}R&ZyeetTQ!?Hz6xGZZb3W6|vR>Hq=}*m=V=Lsp zUOMxh;ZfP4za~C{Ppn^%rhitvpnu^G{Z#o-r?TdEgSbtK_+~_iD4<f6Y7V3YF<U&v zVj0Pa&?~%lU;5MFr8Fr!=O2;I9E%%yuJRL!<OzA)w}+*QepCEc&lYp+cSX9rht2yE zEfbwX1;;Usd(vWqv=rdELe;@BSnsTtd2_#ZQwE#*(Y``Ii@KLBwJC`YC8(n3!YO&* zwA8x1tb$KHyiF%4>9xM;$}X*mJF02|WBL{SDqK9}p4N!G$3m=x#@T+4QcapM{4j|Q zwO!(hldpuSW#by!zHEP@tzIC<ftg$q9K7f^TQ~x75hpz_*ll75X?KcXKjB7J>|KdD z%BJzQ7Ho1(HemWm`Z8m_D#*`PZ-(R%sZmPrS$aHS#WP<b_K3Q@c>jH3EDitxN|DY+ zYC|3S?PQ3NNYau$Qk8f>{w}~xCX;;CE=7;Kp4^xXR8#&^L+y-jep7oO^wnQ840tg1 zuN17QKsfdqZPlB8OzwF+)q#IsmenEmIbRAJHJ$JjxzawKpk8^sBm3iy=*kB%LppNb zhSdk`^n?01FKQ;=iU+McN7Mk0^`KE>mMe1CQ2a_R26_}^$bogFm=2vqJake7x)KN( zYz;gRPL+r4*KD>1U+DU+1jh{mT8#P#(z9^(aDljpeN{mRmx{AZX&hXKXN<h<rn9VB zjn*jhsA^>uxj3x*RrpjvOaZ#`1EqK!$+8=0yv8}=;>f=E?5tGbRUd4%?Q<cc8-A>L zy$kq6mZeF%k6E1&8nwAYMd!-lRkhQTob$7s`*XqcHs;l~mHV}fx&0I&i!CHaPVSM{ zHdRh7<Yd8Xytn18mgkX!i|^GCCC-dE(3D0L^sGNL=5C||)^eg5NdM00mh8zeX^<y# z&2aJIby3iS19Ho*oeMWdwmhia0Mp5;sOAuGVww{|=SaVtqgeul>a>hP)t@YTrWm9y zl-ENWSVzlKVvTdWK>)enmGCEw(WYS=FtY{srdE{Z(3~4svwd)ct;`6Y{^qiW+9E@A ztzd?lj5F#k`=E1U-n*1JJc0{x{0q!_tkD<_S6bGsW)^RxGu%Rj^Mvw|R0WP1SqvAI zs(MiAd@Y5x!UKu376&|quQNxir;{Iz(+}3k-GNb29HaQh?K30u=6sXpIc?j0hF{VY zM$Do*>pN)eRljAOgpx7fMfSrnZ7>fi@@>Jh;qxj1#-Vj}JC3E^GCbC(r55_AG>6cq z4ru34FtVuBt)bkX4>ZFWjToyu)VA>IE6hXc+^(3ruUaKRqHnx3z)(GXetm;^0D95s zQ&drwfjhM4*|q=;i5Io0eDf?I{p}qo@7i7abHX5qLu~VDwYf4bmV~-^M_U?DL(+cG z{AyE^a|*73Ft)o5k-p)+GLXj#q01VlJ9#ZJkf|+c%6qfRgV<R5+UTr_+90OGWmS7m z;q?P(_fconepip4k)6HU7092Q_m6ykGJx-)+pdigO6KNeh-_<~Y_WlFmr-Tj&9}5u zeCf$j*o~m{lqdu!Lth&rcY}x29nI*8Y?GNg;}@3>p&6NsU3~F?!uh}HJm73xq>v$h zYoW3wJE6n9P|;{8U<^%UE2wjR4x^G_Nc$J(i)!>;g4`CCh2z^Dth#ah#<`#axDR?F z4>~hnN2%B2ZUuU6j>m1Qjj~5jQSdA&Q#7hOky#=Ue)}7LPJ!8nbZO_0Sw{G>>M7&E zb1dy|0Zi$(ubk`4^XkVI%4WIpe?Bh!D~IjvZs14yHw=aQ8-`N-=P*?Kzi&eRGZ_6Z zT>eis`!Dy3eT3=vt#Lbc+;}i5XJf7zM3QneL{t?w=U<1rk7+z2Cu^|~=~54tAeSYF zsXHsU;nM0dpK>+71yo(NFLV-^Lf7%U?Q$*q{^j04Gl71ya2)^j`nmJ$cmI9eFMjp+ z#)jKmi4lZc<;l>!={@jTm%?!5jS;6;c*Ml55~r6Y?22B^K3bPhKQ(ICc&z%w<4W1= zjTTtz_}IA$%kCqU)h#$!Yq>>2mVG}qYL}!avmCWYV}x4!YE<j!oVMvb8>eq)pgTp| zR;+sk<i0M|cJ%Y;oVV#c-&)sBEc}b3*f4IJ@-*cG9V-4%sLCN@VSzt<WgB)QfUjT| z4|T(_Hlnd@CN_4Q8UOy^0Ki8WnwymQJTV19QuzM)l4SB=$&dFJla}9EuK6s-W}19+ z^7Ga1LwiW{qkq06@O+r^_Yg%7Ro9MxS+g#FodbIEjW9|-UrH`|k49Qezi|Una<+Zv zZu@_U*I-3I!93w%;-BO4HhlLf)Cw5faDBGnNuHgSkEYp@&MS9<EvwJtVq)?#%yWA= z+>Huc7YXRLrcbYXt>?@pa{l^2pL>RrZ!22zMmi1ZR?nkaWF*`@XFK4jGh&Em3vn(l z3~^Q9&tM^eV=f^lccCUc9v02z%^n5VV6s$~k0uq5B#Ipd6`M1Kptg^v<2jiNdlAWQ z_MmtNEaeYIHaiuaFQdG&df7miiB5lZkSbg&kxY*Eh|KTW`Tk~Vw<M3M(m_<9S!nV` zDzY8T8DAH(DiRR@y}HfHmJs{1GG->KC~+-GoYE+pvwc{+nIEizq<cM<7GICd!t<Fp zS6#)ZM(CDnT2c@bn8^dH5Mn7Y6K5_vDT9Bk`HO*$G89poj3{}tb4o=@ex&%I<2WXM zWPJ7GElfmI51ZQ4RefG!&p5oi$?lkDz2PU6$1_cdMpr{>6!xP>7ZQ(S2%48l$Y98L zvs7s<&0ArXqOb*GdLH0>Yq-f!{I~e~Z@FUIPm?jzqFZvz9VeZLYNGO}>Vh<=!Er7W zS!X6RF^et7)IM1pq57z*^hP5w7HKSDd8jHX!*gkKr<Tsi!u2^FB20W%BQYZ5)KQ$L zBZ_m~(&z=z_bHU6yX*Y#O;EKF98J52TD>Gc-GssrNu5H%7-cNE{h$!aEQK3g*qy;= z)}pxO8;}nLVYm_24@iEs8)R7i;Th0n4->&$8m6(LKCRd(yn7KY%QHu_f=*#e`H^U( z{u!`9JaRD?Z?23fEXrjx>A@+a!y-_oaDB)o@2s{2%A97-ctFfrN0cXQ@6aGH`X~Nr z144?qk;MzDU-cgQOLfT3-ZR#hKmYtKG*iGf4ZJ`|`9!^SkBDUUSJCba)>mM!)k~(z z<MlkpcVBeX*vh=-GFiZS12N#Md{6&{y>djUqB`)~!UObMHB1b$UItM$<0kwlqHH;c z=)+~bkOcIT7vI0Iy(wD)vsg9|oi##%Rgrq`Ek;pN)}lbpz`iv{F4K*{ZZ?Zjixxxr zY|SPl2NsXH+5pimj+MvbZ_+HrfvdC13|9Zs)Y=nW$z<0mh<GitQ487GFo3dN5ShBT zZ8<EFYJ-MdeLR{9xFDVnhwT@fWSs<3mb9?YXz@YP{GsY6>l}%irBSm5T3ZrN#2AhY z_ZrTmS(L`U#y}VZ@~QL9wUS6AnU*7LWS02Xyz`b>%rTml#Wb0yr>@c(Ym*40g;P{V zjV1XSHdU>oY!&Jh7MzhzUV8(9E+yl5UJYga>=0Ldjwtc`5!1>LxaB-kVW;IlSPs+0 zUBx=m8OKVp<`frNvMK>WMO(iKY%P<so97xfVbg0v>uvqD+PK*vP6f?_o!O)MCW5Ic zv(%f5PLHyOJ<Tb=9-j6HkSOIE+QsoSmE5(Z*9l#bIVj4Av!-%TxAJ*LIZ)k}<E=M5 z8&8~zID(`+E!)HSu<G8+vminFC;ax|(elj2;7YfVa<M+CbkOPemAG#3eNCaipA(J# zKp0Ny_GnN>2h@Yn_to@54Yq;fdoy40&sbe3A$4uUXHsHP_~K}h#)p&Ty<c(C-{cmP zd0JxI&cB!QGA1VG>Ox(~JE?y(IBAQKl}~VQjVC-c6oZwmESL;`Xth?2)-b6ImNcJi z;w|`Q*k?`L(+Dp}t(FocvzWB(%~9$EAB6_J6CrA}hMj-Vy*6iA$FdV}!lvk%6}M)4 zTf<)EbXr9^hveAav1yA?>O0aNEpv0&rju{(Gt|dP=AP%)uQm~OE7@+wEhILrRLt&E zoEsF^nz>4yK1|EOU*kM+9317S;+bb7?TJM2UUpc!%sDp<o^NvIr13qoE+gG-rp{X( zM}ATIEoEmu3*AKHeVl}A%qNKwe4Ge&&LS>}7!<`i=W!ot8*C&fpj>mk#<a@1Lml5{ zdPDeAHH~8@6spP8p>qt~GCeqcy)?W6sl>eUnR%yCBR&Ow-rc|q;lhnI+f-%`6Xf)% zIYZru;27%vA{Qi2=J`PQC<28;tFx(V^sgXf>)8WNxxQ<D8vs)|&bOiN>wT14M9I6- z+V0@tiCiDkv`7r-06sJS8@s|Lf>mV+8h}SPT4ZGPSMaFK7_SMXH$3KN7b2V?iV-jA zh1!Z>2tv^HVbHnNUAf-wQW#zMV(h8=3x2Swd|-%AczEIWLcm~EAu7rc3s%56b;7ME zj}$pe#fc^314Mb9i)xH^_#({)tTD4hsoz<Jv$gLvCcZoVeg{3PW{d}YEx45fdopMQ zzh4|NvFea3>!7XcHUh9*G|}?k=D?9LBkTm2?fgaIG(%%$DL#}a-_990rQBU+M;jrf zCcvgM`+oyZmsUqc?lly9axZfO)02l$TMS#I+jHYY`Uk!gtDv|@GBQ||uaG^n*QR3Q z@tV?D;R;KmkxSDQh<2DkDC1?m?jTvf2i^T;+}aYhzL?ymNZmdns2e)}2V>tDCRw{= zTV3q3ZQDkdZQHi3?y{@8Y@1!SZQHi(y7|qSx$~Vl=iX<2<dgX$f8@>`@y3eSYpsBV zI`Q-6;)B=p(ZbX55C*pu1C&yqS|@Pytis3$VDux0kxKK}2tO&GC;cH~759o?W2V)2 z)`;U(nCHBE!<PCa+VaA+eoVg%s&pn6K+Fw6D~Bj(Sq7J}+z_hBY><ghN8H2rs&w3` zvJRVn>-maQz%z#zoRNpJR+GmJ!3N^<X9k%4f!=ilpGFPH6h^v$(M}b6nqK4#$CIdW zNzv+9n>@cA>0EGg?OtgM_h|j1X=!4N%!`g~%hdI3%yz&wq4rYChPIGnSg{H%i>96! z-(@qsCOfnz7ozXoUXzfzDmr>gg$5Z1DK$z#;wn9nnfJhy6T5-oi9fT^_CY%VrL?l} zGvnrMZP_P|XC$*}{V}b^|Hc38YaZQESOWqA1|tiXKtIxxiQ%Zthz?_wfx@<8I{XUW z+LH%eO9RxR_)8gia6-1>ZjZB2(=`?uuX|MkX08<in7IG<(1M==n@8kE5Me589?jvT z(VMo|{oN<TKZ8xV4K`3TfYNXV=KrnM{|906zl<<4RZrah5^Z&u&K8^y<XWR?lqpMq z*!=RBg#KPn5F{_D#;;s7LX4X-88}i<*nHKzXX~Jcxa5xjm9ck&)A<q<^NIQiecF*) zAceKtlM&yv?sj$cae2F$&iC2*iv5eVS>2Dz*=ep%hMwK$TVTyr2*|gDy&QOWu<aq= zES%k$K3|^Rk9-==@u)!=KK2?QF2pCH+kJ5`B#dhzvy1$1H~~Fl0f`I-HG*-)L(~Su zDv%Hsw!!ZhrZ<p$qVF6deM7+R8E$9&$kq?Wzc_dg`sR3f_WJ68dk7~=SSwOYapvQ> zorR#*(SDS{S|DzOU$<-I#JTKxj#@0(__e&GRz4NuZZLUS8}$w+$QBgWMMaKge*2-) zrm62RUyB?YSUCWTiP_j-thgG>#(ZEN+~bMuqT~i3;Ri`l${s0OCvCM>sqtIX?Cy`8 zm)MRz-s^YOw>9`aR#J^tJz6$S-et%elmR2iuSqMd(gr6<O2pC7_LR87#;Sdf%zzI* zg)&032aiWaeCX%2H}S0O=243UHWOaOv9O9Zse8>a#gA_+=N(I6%Cc+-mg$?_1>PlK zbgD2`hLZ?z<RAA}($7nPJ~Y%yBCghN4Iv(BPi6%p!!B0Z9H(i_4jmymVmat@5@x$f z&P@#)-u%pG8st`SK7mwnnq|wi%`9aw$xiu;^g=5`J}Ve&NHoxRQ9J7@-8xy`^2}C^ zRcPOa$y||OOZF&k)*b$K-5!p9ohNx@R~fESqBhel16r=avP&wM(&`tkv`kWMA7_G& zT9vw-v>4S~uhJf=rraLBL?H#c$cXyqt{u^?#2vX<rF~#xjm8V&3d$WjF72Yk9lqGd zU2g#5`8V5mak#w|hV^_1GZo-D9~Ey;F|T*gu9J7}u9SD-u9f#*UlY~jE;s&U>2sFb z^EU-9jmp{IZ~^ii@+7ogf!n_QawvItcLiC}w^$~vgEi(mX79UwDdBg`IlF42E5lWE zbSibqoIx*0>WWMT{Z_NadHkSg8{YW4*mZ@6!>VP>ey}2PuGwo%>W7FwVv7R!OD32n zW6ArEJX8g_aIxkbBl^YeTy5mhl1kFGI#n>%3hI>b(^`1uh}2+>kKJh0NUC|1&(l)D zh3Barl&yHRG+Le2#~u>KoY-#GSF>v)>xsEp%zgpq4;V6upzm3>V&yk^AD}uIF{vIn zRN-^d4(Sk6ioqc<Qp=>K@EObsAi#Z-u&Hh#kZdv1rjm4u=$2QF<6$mgJ4BE0yefFI zT7HWn?f668n!;x>!CrbdA~lDfjX?)315k1fMR~lG)|X_o()w|NX&iYUTKxI2TLl|r z{&TWcBxP>*;|XSZ1GkL&lSg?XL9rR4Ub&4&03kf};+6$F)%2rsI%9W_i_P|P%Z^b@ zDHH2LV*jB@Izq0~E4F^j04+C|SFiV8{!bth%<Tod{MPTY6WHO$dzNX4w2;pzb+Oem zi}`RS_fkC+T@Og2_EV(M-iPFENHyx@oIv_MuPoIud-{e0V-Uf%7)@4g-N3gQm`~vx z?xgzfHP-AqiSg$9SflWP^m#NOTddFU+M+?+(8lr_g^KY#6=U~7!isX>bz(KfCg42^ zGz5P7xor$)I4VX}Cf6|DqZ$-hG7(}91tg#AknfMLFozF1-R~KS3&5I0GNb`P1+hIB z?OPmW8md3RB6v#N{4S5jm@$WTT{Sg{rVEs*)vA^CQLx?XrMKM@*gcB3mk@j#l0(~2 z9I=(Xh8)bcR(@8=&9sl1C?1}w(z+FA2`Z^NXw1t(!rpYH3(gf7&m=mm3+-sls8vRq z#E(Os4ZNSDdxRo&`NiRpo)Ai|7^GziBL6s@;1DZqlN@P_rfv4Ce1={V2BI~@(;N`A zMqjHDayBZ);7{j>)-eo~ZwBHz0eMGRu`43F`@I0g!%s~ANs>Vum~RicKT1sUXn<V_ zg@YtD9Y@=Wecy}B#uLZ`0Y6*jpV`vuxU~A+4P62y?i|jRG)tkWzXVjI@x6TqM$m}# zbS|Bk;hWG!R)sJ3qv;#m`B;@pUIZ{OsCYTt%2wAuR~4+#U+=ZwfPhp%fPgsu{Rrcq zL~bO~c18x)!WICYfrX*Vf6Lhu6}6<W1yDXGO*a;t%N?v5EEn88Lf2L+chf}>L=gOG zDR`d=#>s?m+Af1fiaxYxSx{c5@u%@gvoHf#s6g>u57#@#a2~fNvb%uTYPf<C#}ju9 zHihjbYPO9=#1gmJRf;*(Y<k^4B)ySqM)!mgM>BoT_$~a^w96(}#d;-wELAoaiZCbM zxY4fKl<v{zHLssJ@4LiFPH<esk1E$lEtkXM<D=k?FHU{G^xaGd3~L?TcX;B8eN|@o zjj(tZEIt*eN_K^=W0b&P;RRu>S6-l1!b1!yra|`LOQoJB))=Cx<t5x}2d!26+-1C^ zR9eZmu9JG?UQV{RPjYn{+%DDoEfyXeJtq?V8!!z7MxQwlem<}2cde<XVcUvCW#h_c zCr46juv}&4@(MVkKBg~Byf0?bz(EkPTt-l3s=>UAYqFcTDThhA?d}6FD$gYlk**!# zD=!KW>>tg1EtmSejwz{usaTPgyQm~o+NDg`MvNo)*2eWX*qAQ)4_I?Pl__?+UL>zU zvoT(dQ)pe9z1y}qa^fi-NawtuXXM>*o6Al~8~$6e>l*vX)3pB_2NFKR#2f&zqbDp7 z5aGX%gMYRH3R1Q3LS91k6-#2tzadzwbwGd{Z~z+fBD5iJ6bz4o1Rj#7cBL|x8k%jO z{cW0%iYUcCODdCIB(++gAsK(^OkY5tbWY;)>IeTp{{d~Y#hpaDa-5r#&Ha?+G{tn~ zb(#A1=WG1~q1*ReXb4CcR7gFcFK*I6Lr8bXLt9>9IybMR&%ZK15Pg4p_(v5Sya_70 ziuUYG@EBKKbKYLWbDZ)|jXpJJZ&bB|>%8bcJ7<K?KbboaTR($??M~exw`OmcIx7v7 z={kv@I*3UBm_JtqtFd~^0Y(My{C)7O?M1s{T)vS@D($(u<W!r=KwzSywSmkU3_5Df zP_MIU+Hxpz+^!NFy1LC7I-QOM=d{pcV`ef_t|-ECQ(2y-u2h-4>>l2>hXuf-h5Bm+ zHZ55e9(Sg>G@8a`P@3e2(YWbpKayoLQ}ar?bOh2hs89=v+ifONL~;q(d^X$7qfw=; zENCt`J*+G;dV_85dL3Tm5qz2K4m$dvUXh>H*6A@*)DSZ2og!!0GMoCPTbc<nj>d!h z@fRl3f;{F%##~e|?vw6>4VLOJXrgF2O{)k7={TiDIE=(Dq*Qy@oTM*zDr{&ElSiYM zp<=R4r36J69aTWU+R9Hfd$H5gWmJ?V){KU3!FGyE(^@i!wFjeZHzi@5dLM387u=ld zDuI1Y9aR$wW>s#I{2!yLDaVkbP0&*0Rw%6<cx1-RS5PNoNfinsujvxd(ZL|$GBWig zRa-cyjbCj@hjimsWL8a15(2Yx)Y{G|kE;%x%FYu<@p92kV>bi(LtieJQ4(1V!z!ec zxPd)Ro0iU%RP#L|_l?KE=8&DRHK>jyVOYvhGeH+Dg_E%lgA(HtS6e$v%D7I;JSA2x zJyAuin-tvpN9g7>R_VAk2y;z??3BAp?u`h-AVDA;hP#m+Ie`7qbROGh%_UTW#<d34 zz&Cs3P;q?uMJ`^xLS#kTigwMwHwV0^d~LVUp9}r;c9y_pcT2*zFrI0v>R8yfGp<`u zT0}L)#f%<U%8Y+)jqE&8ed=$=JjeSnb4Y#P_lkT5EqSi<>(XEE)^iXVkO8^cvjflS zqgCxM310)JQde*o>fUl#>ZVeKsgO|j#uKGi)nF_ur&_f+8#C0&TfHnfsLOL|l(2qn zzdv^wdTi|o>$q(G;+tkTKrC4rE)BY?U`NHrct*gVx&Fq2&`!3htkZEOfODxftr4Te zoseFuag=IL1Nmq45nu|G#!^@0vYG5IueVyabw#q#aMxI9byjs99WGL*y)AKSaV(zx z_`(}GNM*1y<}4H9wYYSFJyg9J)H?v((!TfFaWx(sU<t2;aBGeC3Et<4d@8=a1+y1+ zC{viFxsY`fMWFdmm8XA{gpkf9>*fU823wPgN}sS|an>&UvI;9B(IW(V)zPBm!iHD} z#^w74Lpmu7Q-GzlVS%*T-z*?q9;ZE1rs0ART4jnba~>D}G#opcQ=0H)af6HcoRn+b z<2rB{evcd1C9+1D2J<8wZ*NxIgjZtv5GLmCgt?t)h#_#ke{c+R6mv6))J@*}Y25ef z&~LoA&qL-#o=tcfhjH{wqDJ;~-TG^?2bCf~s0k4Rr!xwz%Aef_LeAklxE=Yzv|3jf zgD0G~)e9wr@<NAwXzn@Tr_JeoALXYsIL!5suMVtRWLsesHCk)H^$%gST+;+pb2o(2 zp@>)BCjlY84wz?$NS8KC9I$wf(T&+79JjF#n?BTI)Oub%4wiOcqw+R`R_q<`dcuoF z%~hKeL&tDFFYqCY)LkC&5y(k7TTrD>35rIAx}tH4k!g9bwYVJ>Vdir4F$T*wC@$08 z9Vo*Q0>*RcvK##h>MGUhA9xix+?c1wc6xJhn)^9;@B<A{c%wXcknJHL8?TSUQilQ^ zT35Z%hWih0GcUZZgPv6G%jjVNuj1NPD|n^)OAlY0@vSi`)!cZk5VUw@Id1!S;G$IS z`&_SaqDl=^3l4kv>E6i*Rl8VQdstnLOP1mq$2;!bfASHmiW7|=fA{k$rs^-8n{D6_ z!O0=_K}HvcZJLSOC6z-L^pl3Gg>8-rU#Sp1VHMqgXPE@<y_*(2S91GdCv4<dCoaZ? zA?4TKM)S&Oq$p$!!5EeBG8Z?C<sUM7r)h{_98iuE!Rj!{@7Y!kZfGX90bSl|Q9|d( zxcPg$!rc0(rdS0}iJHFbR3;sEmCo#kVYCM&ov7oV-K{L=<CqzGagYVnblf6_U&P1Q zSLpH1tLN#w0fD5VX@bqBZp(8ahlk9j+Vf;gmN>9x&IHe;K3;!^SQLDP1Gk&szPtk| z!gP;D7|#y~yVQ?sOFiT*V(Z-}5w1H6Q_<rHoVq2SN~wRz_&Lb+eg25kD%S_<hy!|d z479UbBeZkS!YA}P`u8#z{w9z_yvIJnA$tgrix7XC=mr8W+{e#<F1sp_;_?cBaw`XT z{bg_Y4+SLce<~np|HIGH9$+#+Xa9d4AC)F2<R~X3RKz7@#>U5JM#iW16yZiFRP1Re z6d4#47#NzEm};1qRP9}1;S?AECZC5?6r)p;GIW%UGW3$tBN7WTlOy|7R1?%A<1!8Z zWcm5P6(|@=;*K&3_$9aiP>2C|H*~SEHl}qnF*32RcmCVYu#s!C?PGvhf1vgQ({MEQ z0-#j>--RMe{&5&$0wkE87$5Ic5_O3gm&0wuE-r3wCp?G1zA70H{;-u#8CM~=RwB~( zn~C`<6feUh$bdO1%&N3!qbu6nGRd5`MM1E_qrbKh-8UYp5Bn)+3H>W^BhAn;{BMii zQ6h=TvFrK)^wKK>Ii6gKj}shWFYof%+9iCj?ME4sR7F+EI)n8FL{{PKEFvB65==*@ ztYjjVTJCuAFf<rY<ZEMlK$u$5-g|rkMy)?}zrg<cj{J4~fJ^?r-mLI|&wp8-{--Y0 z$lBtc{7L`g@4CFzKLU6_{RUts#(({~fZ<<+Tq9>8I~yB-pN_PJtqH&j$`#<<`CruB zL=_u3WB~-<Bwam@u?Y3tJi%|VkE@FOA_WbQgjf<(NEYJn?j2c2v5%5fY6*Nhc0Kf@ zGyVA6F-((}nGN@{6!@*o-0aL#%yo5ZdR<*#pw-c}Fqq{ED8`ZcDuWb@&5e@{u$??R zV{;T)A~l!;PQl_M_0q-T2$E*e7zG=uI%X*)DM_7M7K_epWs6wGsb%#n4GsmXWLsXM zBQM5{n74^ZHYhM!mF)Naiww}*4UI<SX?1IsY%1lG@fVF1N*k`uZNm&coJ%VsTi)30 z63S(&5hkN(3);{^TsoYpatne&4UxTgE!PzT(%CmU7wStmP;ts@1czZcK$fFnmQ60{ z1`faF@nb=Dgf<0A3VGLZjnb0{n@9h2n!kn_HCDKyXu;%%6d9Dt4i)X*2_22dAHbxa z2srvHbi^3iUv%cZJHSx)8Kd(wg0x*i!0jVB91}Q)OsRD*{RUFUZe`rRbZj*_$$qM6 z%-6(nVT*<9hbz`OdMZ3^{PJ3{Yc<61R-(jj<Z3MHVtdi1*Jd3m$03jA7{pLqiqgWN zuf`QaEabQ(LKVZptm-fx-rT-qkcLwos%8=P@J-?UgS&Rj;xcvu%L&V&GZ|Av2=2YY zuK2muzm-$n>;t3q)iNn0eU(mF<W>Tih<4nOAb>1#WtBpLi(I)^zeYIHtkMGXCMx+I zxn4BT0V=+JPzPeY=!gAL9H~Iu%!rH<j+~n)ld$6<geb2z!M*B;3n68THTn)6(b11z z4m(Nwk^Vyz86bu0^6;Tn%;Ji#7$WO^G#_C0*A{VAYI#R4+3k40>0-S@IcG%~=tB#6 z3?WE7GAfJ{>GE{?Cn3T!QE}GK9b*EdSJ02&x@t|}JrL{^wrM@w^&})o;&q816M5`} zv)GB;AU7`haa1_vGQ}a$!m-zkV(+<n8pPDe>M>q!vI0Swo18{;<>GYZw7-V-`G#FZ z;+`vsBihuCk1RFz1IPbPX8$W|nDk6yiU8Si40!zy{^nmv_P1=2H*j<^as01|W>BQS zU)H`NU*-*((5?rqp;kgu@+hDpJ;?p8CA1d65)bxtJikJal(bvzdGGk}O*hXz+<}J? zLcR+L2OeA7Hg4Ngrc@8htV!xzT1}8!;I6q4U&S$O9SdTrot<`XEF=(`1{T&NmQ>K7 zMhGtK9(g1p@`t)<)=eZjN8=Kn#0pC2gzXjX<PjHqiA$Vvf_R%9gKYsvdV^zlWxqzf z!#>cadjHMc_pfV(@^3541)LC1fY~k2zn&2PdaW`RPEHoKW^(p_b=LxpW&kF?v&nzb z1`@60=JZj9zNXk(E6<E`2y|0HP0)>D5D}(@k4Oi@$e2^M%grhlEuRwVGjDDay$Qpj z`_X-Y_!4e-Y*GVgF==F0ow5MlTTAsnKR;h#b0TF>AyJe`6r|%==oiwd6xDy5ky6qQ z)}Rd0f)8xoNo)1jj59p;ChIv4Eo7z*{m2yXq6)lJrnziw9jn%Ez|A-2Xg4@1)ET2u zIX8`u5M4m=+-6?`S;?VDFJkEMf+=q?0D7?rRv)mH=gptBFJGuQo21rlIyP>%ymGWk z=PsJ>>q~i>EN~{zO0TklBIe(8i>xkd=+U@;C{SdQ`E03*KXmWm4v#DEJi_-F+3lrR z;0al0yXA&axWr)U%1VZ@(83WozZbaogIoGYpl!5vz@Tz5?u36m;N=*f0UY$ssXR!q zWj~U)qW9Q9Fg9UW?|XPnelikeqa9R^Gk77PgEyEqW$1j=P@<dE4w(u(3iVMi-8SY4 z3}C{Rr_5Fdb7Rr%)CRjq4+&Hx?mUJXe6rA*s&n~(V48wA1sGg{_CaE0GHmqWDt`D~ zX|%%BrEov=miSU@%ILVmtO!gF&qB~Zwn*+dVv$gW4nxk2U|7hBM@%!T=tG2}g!3V8 zkh2ON65LUEoMIT*<uZYoomew53~|P7OCgONbH$V*wBiXS3f4p|H^IsIX`jOD;1u>L z*ndO!fwPeq_7J_H1Sx>#L$EO_;MfYj{lKuD8ZrUtgQLUUEhvaXA$)-<61v`C=qUhI zioV&KR#l50fn!-2VT`aMv|LycLOFPT{rRSRGTBMc)A`Cl%K&4KIgMf}G%Qpb2@cB* zw8obt-BI3q8Lab!O<#zeaz{P-lI2l`2@qrjD+Qy)^VKks5&SeT(I)i?&Kf59{F`Rw zuh7Q>SQNwqLO%cu2<XTECiMRi^Gbl(lC2paG3*Thj*0)}Xq~An04S6wpQhX<1~$1w z$|BShs8E$Oh2P_lh%5{Rt@}ZR@|tp`N8?PA&uzngg2`_3EJvV=voL0TNQlm`uQfnb zmflQz*loTYcGz`!%+Vj=^M0e)jrIFBj=?aWXQV3D12mMODUEwaCDT}9Bp?=+Yq#K7 zp&vz<l)hrRjq0Yx1fxPL#%qOhE`AlXTtmsE?PArtiI!FPsPG%YD{1QYCJzkF(h3yM zSgG22fY44@_X7qJb~JHEIIXuHl5OFsr%T;sn2uH&>lzcJ7eR*3!g<TJ_lZfH8Wr<# zgT0tcvD1Q&1BtX2hf7@$jhm<`+i@heDmB$3?k`tQKjG^qZF&M6uLr9-`_h@i$<7L} zgUL|Kf{v`NW#Z9N7|M>}U)9=EQ}js-q{d%h!wl6X3%H0Z2^8f&^H;yqti4z6TNWc& zDUU8YV(ZHA*34HHaj#C43PF<m2DoY_InyxnT#nQbdAIC5&)!sabH!!uF>Zq7a><f0 zgtG{pGT8+md}S0t4`>=PMmj4+?C4&l=Y-W1D#1VYvJ1~K%$&g-o*-heAgLXXIGRhU zufonwl1R<@Kc8dPKkb`i<Tc2Ft*8|Y%hdxlha;A>5P9VFT_NOiRA=#tM0WX2Zut)_ zLjAl<azDfvq~H_KwlbAX@QUQH^9B7mGKnyS-1=w5);r`=sJqkPw~ttGrcw9@zpN33 zC6*)>JS1&nnrL8x8!o$G+*z|kmgv4DMjvfnvH)7s$X=-nQC3(eU!ioQwIkaXrl+58 z@v)uj$7>i`^#+Xu%21!F#AuX|6lD-uelN9ggShOX&ZIN+G#y5<zsBg0Jzu|^0arbz zHZiy8&9{9<=}nzl@rOCs!y6zYzW)*J$?3ymq5#w^1&H{60|ozf&;t&27aM?KJHT_= z<UdaNf1K`#u@jOYj3_~Jugv)kjhZ$C{)TR=WdUcB0>T0q+RL*(T(EP)(nP744-ML= z+Rs3|2`L4I;b=WHwvKX_AD56GU+z92_Q9D*P|HjPYa$yW0o|NO{>4B1Uvq!T;g_N- zAbNf%J0QBo1cL@iahigvWJ9~A4-glDJEK?>9*+GI6)I~UIWi>7ybj#%Po}yT6d6Li z^AGh(W{NJwz#a~Qs!IvGKjqYir%cY1+8(5lFgGvl(nhFHc7H2^A(P}yeOa_;%+bh` zcql{#E$kdu?yhRNS$iE@F8!9E5NISAlyeuOhRD)&xMf0gz^J927u5a<R_91y>K|P- z>B%*9vSHy?L_q)OD>4+P;^tz4T>d(rqGI7Qp@@@EQ-v9<l3HhbAKAXS;d<mAzAm?% zWxLu`9iXuSd^l-vs5}td&}`9WmHp3f-ddBjF%W>w-;n;7N05{)V4c7}&Y^!`kH3}Q z4RtMV6gAARY~y$hG7uSbU|4hRMn97Dv0$Le@1jDIq&DKy{D$FOjqw{NruxivljBGw zP4iM(4Nrz^^~;{QBD7TVrb6PB=B$<-e9!0QeE8lcZLdDeb?Gv$ePllO2jgy&FSbW* zSDjDUV^=`S(Oo0;k(Idvzh}aXkfO)F6AqB?wWqYJw-<Q4@a(N2WiV(%Vq!M!1j2wJ z!(_tjDZ}FRqGOyyFaZ=BcaPLacu6&odeD1kOs>1wOn5!{-ghaHb^v|B^92LmQ9QZj zHA&X)fd%B$^+TQaM@FPXM$$DdW|Vl)4bM-#?Slb^qUX1`$Yh6Lhc4>9J$I4ba->f3 z9CeGO>T!W3w(){M{OJ+?9!MK68KovK#k9TSX#R?++W4A+N>W8nnk**6AB)e;rev=$ zN_+(?(YEX;vsZ{EkEGw%J#iJYgR8A}p+iW;c@V>Z1&K->wI>!x-+!0*pn|{f=XA7J zfjw88LeeJgs4YI?&dHkBL|PRX`ULOIZlnniTUgo-k`2O2RXx4FC76;K^|ZC6WOAEw zz~V0bZ29xe=!#Xk?*b{sjw+^8l0Koy+e7HjWXgmPa4sITz+$VP!YlJ$eyfi<KyWTm zFcof9N;tvhj*k<(tO<;L#aIbBw`3X1?v|{U!$P^D+Bj~@or<|6qo%K?RMVEGhD~Ka zJ4LTJE~}5brdzk+`%xg32ga4AN^7K}=mu{->3^6gGx6jZLpbUzX;!Z6K}aoc!1CRi zB6Lhwt%-GMcUW;Yiy6Y7hX(2oksbsi;Z6k*=;y;1!taBcCNBXkhuVPTi+1N*z*}bf z`R=&hH*Ck5oWz>FR~>MO$3dbDSJ!y|wrff-H$y(5KadrA_PR|rR>jS=*9&J*ykWLr z-1Z^<F@{b4F$OXI9nXROeH=v7we7Mk%PT*bsvs`65Ve~Mk98j+>Q<W<ER>OxE=!6I z%Bozo)mW7#2Hd$-`hzg=F@6*cNz^$#BbGlIf${ZV1ADc}sNl=B72g`41|F7JtZ^BT z+y}nqn3Ug`2scS_{MjykPW2~*k$i6PhvvxJCW;n!SK5B8Rpm41fCEdy=ea-4F`rN5 zF>ClKp#4?}pI7eR#6U|}t`DA!GQJB7nT$HVV*{qPjIRU1Ou3W;I^pCt54o|ZHvWaH zooFx9L%#yv)!P;^er5LCU$5@qXMhJ-*T5Ah8|}byGNU5oMp3V)yR;hWJKojJEregX z<1UPt%&~=5OuP(|B{ty);vLdoe7o^?`tkQa7zoXKAW6D@lc<Y9BEsxR99CK?eu(Be zGA=o2I=p|tu*T4PbHe2diyG42d8!n4Hg)n2a4xjc6z78^o#R>+FTzucotaOfJ!(Bm zHE8f8j@6||lH`y2<&hP}Q1wr(=6ze0D6NRL{7QaE1=nTAzqjIeD}Be&@#_d*dyurz z&L7xo-D9!dS`i>^GaIPArR@r=N#-ppIh!UBcb!N*?nLUO+*%C>_dCF1IH)q>5oT(t zjQo{AoDB;mWL;3&;vTt?;bvJSj>^Gq4Jrh}S}D>G)+b!>oRDWI?c_d77$kF5ms{Gx zak*>~*5AvaB-Xl)IgdZ<t6nEV?}Cne<P-n&MECe~i3&sd4ZANKmk95&dg5wNK})LG zCB8Pla-X7EnslQM^yf-}PB(m;Y8753eJ&Ix-9#IThb=9p5d$-pGni+k$)lElNRT8S z2KNbs1uGE`I0DL!MEBb)!?uqF-gPGn_4IgS%r?~h{NnAe9R=0vb^&$>^Cupv6HxQ0 zM(KPaDpPsPOd)e)aFw}|=tfzg@J1P8oJx2ZBY=g4>_G(Hkgld(u&~jN((eJ}5@b1} zI(P7<P_r5po)9z#EH~syS!Kqb>j443AZj*I@%q!$JQ2?DZV47U!|Tt6_;tlb`mSP3 z74DE4#|1FMDqwYbT4P6#wSI%s?*wDc>)MR$4z9ZtJg04+CTUds>1JSDwI}=vpRoRR zLqx(Tvf34CvkTMOPkoH~$CG~fSZb;(2S4Q6Vpe9G83V={hwQ>acu+MCX)@0i>Vd`% z4I8Ye+7&Kcbh(*bN1etKmrpN)v|=eI+$oD=zzii6nP&w|kn2Y-f!(v<<k5@rT?>aE zKmOz#{6PZB(8zD={il`RO6D}v(@mN_66KXUAEefgg|;VmBfP?UrfB$&zaRw7oanna zkNmVGz4Vhd!vZSnp1(&_5^t;eSv6O771BloJAHi=Pnn+aa6y(e2iiE97uZ{evzQ^8 z*lN@ZYx<-hLXP^IuYLGf<01O*>nDp0fo;;Iyt`JADrxt7-jEF(vv_btyp6CT8=@5t zm`I0lW+2+_xj2CRL|40kcYysuyYeiGihGe&a)yilqP}5h+^)m8$=mzrUe`$(?BIY> zfF7-V10Gu0CkWF)wz04&hhI>es0NS7d`cnT`4y8K!wUAKv<L<7Nid8Jrumy4N;IHd z0paO3I2Y8JehmMfgyduZhif4rEPfg6bIuG&hw$^v0?8-vhVwO#EmG$GgX(#!Qo@^$ z<m+!1?0<C_U%$N(<^XtW4bWV(|L;4D|JhsoHH4d!NAbN}oKfJyuv5AQ6CP*V90d}f zAmSLHq0?s)+8ts_)0k~2xCrIz!|z4!`W4%My`pD0zopO9WOJw=zZ>$H<u&P)<#gp? z(e?3pi^~6P4M{O2ae7SeM+!0{y+F5rpa@NvJB^`+VBA%_JCR{x-;!_%(V_^C$+0Fo zP2ZY+=+UhgjNlCKH0JAQaN!lp>09fa>KeNQvwUNDT1zn}_*RHykC$CD%*h7vRCQ&Z z4&N-!L>(@8i?K$l5)13n0%VPPV`iG7Q$2{1T3JypLSvN%1kX73goBIOEmg=Uf$9e? zm}g>JFu}EQKH>|K!)m9teoCmTc`y2Ll}msZYyy0Pkqjeid66>DP_?C{KCw94lHvLW z-+X!2YSm70s833lH0o+|A%Xwsw`@8lE3ia0n_Dve;LC7@I+i~@%$lD|3fNf&R6ob6 z@iGfx^OC4s`$|vO!0jTWwVpX;X^EqJF{i324I>N=f@u+rTN+xJGGR0LsCQc;iFD=F zbZJrgOpS;04o^wP7HF5QBaJ$KJgS2V4u02ViWD=6+7rcu`uc&MOoyf%ZBU|gQZkUg z<}ax>*Fo?d*77Ia)+{(`X45{a8>Bi$u-0BWSteyp#GJnTs?&k&<0NeHA$Qb3;SAJK zl}<dpK~|?-2MdqSy$cG$9<5m0w^hpeS#3f_m*8b`Btwh>H*~eyD-0qHI3SEcn`_7d zq@YRsFdBig+k490BZSQwW)j}~GvM7x>2ymO4zakaHZ!q6C2{fz^NvvD8+e%7?BQBH z-}%B{oROo2+|6g%#+XmyyIJrK_(uEbg%MHlBn3^!&hWi+9c0iqM69enep#5FvV_^r z?Yr(k*5FbG{==#CGI1zU0Wk{V?UGhB<Ctd|b<#lQNMxZ5VviiRyuMI#pIiZ2^|JDK zO3L9dCXa}f;^Y$f(=pZwzVcA246lD95B`-WJHN6#wg7oz3xL9W|C>Bfv9<dD0O1Z5 zDLG^Xl+Vlhxug@ILjD1CqP%*c+=P&Df^gw{NCD&our?`fOtE5SV=k3X|F3}{5*{RU zTN!|*CN{h6y&J|<rmSR8TqE6jhSOEc(c5Lq7H6jK^9P{S<*}s>HP9A-AmcJmL^f4S zY3E2$WQa<os1kLE5}@;1pUY1%3gz3i)o@!9{w3g}zt-AjedDM>&n#WRQ5DOqty_Pu z-NWQGCR^Hnu^Vo2rm`-M>zzf|uM<GnavRyT%^9&KYCGX1vzC+ZzJoBv;GM8?M7el_ zOj4`ATGTY0Qyjz0MsEJ+d7MkgeT7odVS~t|L2-3{W5|1Mb|4}HxM{J)D%=MF#!F5H z$znU~W8GDE6~2jQuI4MQE(l2$c<!zZW}|ItOsYzO@}yDhw1^oS#npP$zC*=lO$*6U zu88^KQNrO|Xq6jw-X@)K(8^m_c10_RPV^ic1Vi^D1e|SZ@=YeGkvwPwnwfN*rB9q% z-*Y8R$pQNHhf+PC3yqmzyMhS)YhbF{OjKYv9*GLYu_9w+4+Wa?dcPq`6UO1E2Ny<B znj!WtPptmhi%@C4^+Q3%$RaJ9LHRmD{p0<C-3^<mgR}Lr>CUd1X0{wISJL2Pp=AO5 zF@(50!g|SYw3n<_VP0T~`WUjtY**6Npphr5bD%i3#*p7h8$#;XTLJAt5J-x~O1~`z z`2<a~bE>C~P4%XSI(JbrEmVMEwqdsa^aqXWg;A6KBn^jDxTl!}Q!^WhprL$kb(Iqq zUS`i$tIPs#hdE-zAaMGoxcG?Z;RO2L0Y|gcjV_)FFo|e)MtTl`msLTwq>po$`H6_U zhdWK97~M>idl9GE_WgobQkK_P85H_0jN?s3O)+m&68B`_;FnbZ3W*Qm++gh<gg&=e zndjA>Ss7<TnPD^1F;1U(juVW9o>|T4b7m~VVV%j0gl`Iw!?+-9#Lsb!j3O%fSTVuK z37V>qM81D+Atl};23`TqEAfEkQDpz$-1$e__>X2jN>xh@Sq)<Ygb_LJo1t>I6sj@< ziJ^66GSmW9c%F7eu6&_t$UaLXF4KweZecS1ZiHPWy-$e_7`jVk74OS*!z=l#(CQ^K zW-ke|g^&0o=hn+4uh-8lUh0><E4`l}*L0l`Mtr6!;{!8(D|F|5-srYFMx1S#{CLoi z-u@yl4F$V%pqJZK;U+B~so`D@_w#Z-bY@Qn=YAO{w^pRVf-oYOD2(2OLZqnWR4`y= zDh@E^+A7oBck(|Ajx3`M4sUxg)ut2KB~3O;R|I=v4>!VIXXnQXwKr>`94+2~<;+`k z$|}QZ>#pm2g}8k*;)`@EnM~ZQtci%_$ink9t6`HP{gn}P1==;WDAld3JX?k%^GcTU za>m|CH|UsyFhyJBwG5=`6562hkVRMQ=_ron-Vlm$4bG^GFz|Jh5mM{J1<Tk+4XYX( ziO#`V$T`))Hnit$gsM99=Wj7wF~qPn*YlclqD|t>`!!hAr~8<bbVd`cJIuqidsrOf zU2boSJkW~@abR_!UeoB!SbqQ;aILZszb&@Z!rjE#A2zjDGylM{@3Xh6m}m%7>F^w> z^YhQ=c|bFn_6~9X$v(30v$5IX;#Nl-XXRPgs{g_~RS*znH^6Vhe}8>T?aMA|qfnWO zQpf(wr^PfygfM+m2u!9}F|frrZPBQ!dh(varsYo!tCV)WA(Wn^_t=WR_G7cQU`AGx zrK^B6<}9+$w;$vra<KXC!@9QY7p=NFCVMP6-P}~2^ZJJ3eFVYE6&4hIiV8E=s8tr# z0#qZaEmZE!!D0z*QCvpKn7H8h!avhr%{lz@vGDf7f)P2r!v7rHVgu9Ko#EnO@3(an z?_kW{_uoD`nVxH1<HCRHk*AAK{oz3Y8>)QWMKf_Tnqg93AMVZ6Qd=q6rdB{;ZhsoT zWy9QhnpEnc@Dauz4<m19Rdfw8y;`Kav3Hx(A`<JMc*}%4lpGR%kQ5PjiVk{h>!8gq zqDanAX#$^vf-4~ZqUJtSe?SO+Hmb?)l2#}v(8}2+P{ZZuhlib0$3G0|a5?JR<k*cm zCmOOnj3(-HNh(i177pAS7^kL4T{>>QgUUP$HTE5hb`h>imq#7P+Y*-UVLm@9km|V# zoigziFt$bxgQMwqKKhd!c--&ciywIED>faY3zHLrA{V#IA)!mq!FXxf?1coGK~N(b zjwu*@2B1^(bzFVBJO`4EJ$=it!a0kbgUvPL;Er(0io{W4G7Bkqh)=g)uS|l0YfD}f zaCJwY7vR-D=P9M68`cmtmQ^!F-$lt@0S|9G7cHgT13A0xMv)HmH#Z<4{~iYo_VOD{ z5!kU+>mUOvHouw+-y?*cNlUlDwD#;6ZvAIc$YcwG&qKZFh>EtM(Eda+w)E$HcfZyB zG*$<*ae_ApE%gxWx%O^~XMnRSNLv<hmJFP&{*xkGgV^OrK?1kLgGbB96vK2H_jZxB z{uSH8-Mbv9D(hh0ecU+E0xnSpq9S%5YU?hQTM<WFbb895&H6T9H6~AYhFscp!GJw{ z{CMJQRqzreX33n*BIQ<*tXMmj%uX7F>!y`g99F(J_m)spJAc95P|_joOIoru%atbw z9PYgkcE*8x#)-W{>96KDl&74iW<<I5n&4tNuY!4LW89B#)kHmkF)^##PQ>#wrK)1s zxzU{`rW5af+dT6Z@_1dG<}CtDMT`EGVEXSL_5D9)Z;6UJe-TW7)M?bY%E;8G?Yc!$ zic;F5=#dba^P~7f#qvC}Nd#XEo2r_UlgfR_`B2^W0QjXU?RAi$>f&{G_Lu8Fp0qDp z?vAdm%z#3kcZmaJ@afooB=A@>8_N~O9Yzu=ZCEikM>UgU+{%>pPvmSNzGk@*jnc5~ z(Z#H4OL^gw>)gqZ!9X|3i4LAdp9vo)?F9QCR3##{BHoZ73Uk^Ha={2rc*TBijfKH- z=$cZQdc<5%*$kV<JTwx?Wn(uqpL99S9cOqdlB;ZG*LbZg!Ka?Zx({N4L0>o|{+bL3 zEoU&tq*YPR)^y-SISeQNQ)YZ9v>Hm4O=J)lf(y=Yu1ao&zj#5GVGxyj%V%vl9}dw< zO;@NRd4qe@Et}E@Q;SChBR2QPKll1{*5*jT*<$$5TywvC77vt=1=0xZ46>_17Yz<K za?9xqEdfacW8D|@q#P8^coE6j8@zW82Rl*skXUzNg8|`yh_|Ez<QOHIO{ab;wb2c- zpc&2NC#^0HqUf$geRxB?^^XHPc`tc7Av0*Fsu&&vziVbfs5<(8uABi0!43C-&I}FV z-&W55n6DcDZ7%+wV{kPqHvqGABY$aluV=9L`#xwsLVyXOJzhO&qmkkF`Cm4Q(he{( zvprb*xwWF0QQu|L&9)}$Bwh)8_64+>biBoDffH(1_qFP7v2SVhZmA_7JDB50t#C39 z8V<9(E?bVWI<7d6MzcS^w!XmZ**{AO!~DZNU<pxH8Gxh)IAIm+Zb48*U>)pgr=yY1 zT@!AapE;<gsI*}-Da?=g8*q;d%H8vD(W^0r;;DQ~9M)o3th<${vKD^}5=NW#KU?EW zh23}<+!)o|kzYJhS~Gs6+Fv`Nc7^UGW4&INySD#A51Npo(p8<dNY)vnQ+cG9ZHrc+ zl4o|9<gkNL)hXxTut*3&t(VaXirZ1eRqB~JTtKwbWRVOKnCiI>yg&hmj*g{I3vd## zx+d%^O?d%%?Dba|l~X6ZOW|>FPsrjPjn-h4swysH!RNJUWofC?K(^0uHrBPrH5#W> zMn8^@USzjUucqo%+5&))Dnnw`5l1mp>roaA99Nkk4keZl2wAF7oa(!x?@8uGWzc5Q zM}g`}zf-D@B6lVFYWmmJ8a+_%z8g$C7Ww~PD9&jk<nl<d;?|xUi{p(U084GCmBMN4 zGFuz{A+U((wpj+c-1uBvX}MHY&K*h!He!l}HpWWz2)-P%&c)+E+b7UeQ`E6LxLc(& zc6B+<KTUcZqh_;&LwyjloJ7F&d>i08NY!b!fK288R;E?e3Z+Pk{is%HxQU`xu9+y5 zq?DWJD7kKp(B2J$t5Ij8-)?g!T9_n<&0L8F5-D0dp>9!Qnl#E{eDtkNo#lw6rMJG$ z9Gz_Z&a_6ie?;F1Y^6I$Mg9_sml@-z6t!YLr=ml<6{^U~UIbZUUa_zy>fBtR3Rpig zc1kLSJj!rEJILzL^uE1mQ}hjMCkA|ZlWVC9T-#=~ip%McP%6QscEGlYLuUxDUC=aX zCK@}@!_@~@<J?+9-)*r5I{=D}hCY%rx`sl0_Jw~le^&*!YtV{Hcj*S)H-E3`In)2X z@^|b*1L53_m$vc^8UC-t_tNKpXue%JsK(b1D~gVeI4Sln9Kogfm*rF^X&ziLd*z|* z8?>z;70I+Hp5#Tq4h#d4r!$Np1KhXkAGlY$ap7IZ9DY})&(xoTyle8^<LCYg6(aW6 zy%{G)o970SPj-Lqi=`UVC)3TsSh}>dBXbQUhPE6ehWHrfMh&0=d<)E2+pxvWo=@`^ zIk@;-$}a4zJmK;rnaC)^a1_a_ie7OE*|hYEq1<6EG>r}!XI9+(j>oe!fVBG%7d}?U z#ja?<qnwPve_3NqgW-6u^A{BN$AWAUcc%hfdf~fcjO8U~dDHuIhK?hwRL&O0F9C#Q z-1+<O?F7fmi1n1;O~sr-c755<5#4$DRB3kMKAEI%*{v`j6-MQM@6iP6WYqbe-L-L; ztt=ir<wfn`c?aCGe=AJJQ?|^S)D4L1@S8qo4g5nO$SBF^3Pv@+G0;do?W!x*=0CFT zatuYcOA+J(#Va~A59>T@`XO(;q~fe2CfFm-g8FbVD;O7y9c;J)k0>#q7z-%oMy4l+ zW>V~Y?s`NoXkBeHlXg&u*8B7)B%alfYcCriYwFQWeZ6Qre!4timF`d$=Y<a(i0bbn zKKQ{1eJrnxk%*tB_~e&wjFNh9B`YBi?xJv8vE_t*m`2O+;wM0OBw`ShXE;A5a=EOw ziuz5v={b#v!z9w34UO*k#3zI6`U`aXicVHS5m*U7q0_hGy}+oZI^{&qUgX})LWH$$ zFM;*!BOQ2rK`7%^m@zRQzT<~~ZzW0e3fx!1+_)vH*WPU`Z}uTeUM31rt~Wr#(`kK? zxTNyj((17b{_2O}H##&<;pY6@7)vBPoWNi<&A@aDT$fR#Cc-mO#<cDOrOg{JHW<+t z7S=So=BNY<aJbCqWh9z8J|e8ewbfTq$Nf{sB@+g_VD`Ykx~tk+%l*gjEHWqIc#crJ zYOX{g;0<%K1cv6srUYVUKngF98;qQBFaDKF4Dl2wb7SUr+mD9z4TeOMN!zwi^Znb| ziK)KiGi}l^Rbw@Hqcu@&ALE?t@BK3xTo(rXyZj0QC!VUX^L?1zeJM7hRJoigLqF4y z^w9Bt2I=<pilTfE=wR;O<|23c>N~_fPM5Kc8P;B-WIDrg^-j=|{Szq6(TC)oa!V7y zLmMFN1&0lM`+TC$7}on;!51{<RT@_E@K=fWPGZZOy3nPlPY7Q4(DMf`4E{jCC+%r3 zps5XExcdx;&M<;I#HN$~15vUU2_wI}?-N7%1?-jXo{IDm8{a|JN9;;Fv_>d^&M`UW ztI$U4S&}_R?G;2sI)g4)uS-t}sbnRoXVwM!&vi3GfYsU?fSI5Hn2GCOJ5IpPZ%Y#+ z=l@;;{<jkPKiGwtaZ+-<{3t<FV4%X@eDZyH=iQ)K$S!-t3X#-#Bp6RZ+)0c!Auu42 zUy^EMbno8)OL|bs7?*|jXf97OIJfRyi|nrG{{TJKXz`<AdsrXVB#KdnT;RBVUqMEM z@(~pWl<GifrPhq<btJ+rJaC;VTa6|5NlhuP@h&4t;k)JW<17q>XiY_r#^RJSr?s1) z4b@ve?p5(@YTD-<%79-%w)Iv@!Nf+6F4F1`&t~S{b4!B3fl-!~58a~Uj~d4-xRt`k zsmGHs$D~Wr&+DWK$cy07NH@_z(Ku8gdSN989efXqp<EP1Av}JofrdXN7or{^(Q&IP zm&2WJP0w_xq@J1=aG|yuieS}MHm|W%J8&cO*DV<E7=I31!S?q2gp0E02*`AU(5%_w zP-ZZ@<io<0zc(nFOi#5*h@8Cm5s)=51O=v4hwOw4U4&NrEk6R;8*+?MJ7(uz^V9*H z)<<Yg?^qHzElXr9?AQ`GEmedXvgE~$y!7FJOsW}SO3UQ|6F5)+7e@47@9E!e;eS%z zgbbWa02@$DY@IBe0c)xMJt?M8Ne8gz9e|3xts^2tR(H)%sWm&#lTIW25EA<X6G|Xu zwjeMTEs{1&-7MazKOuCzOT*v?&3;BH?B(A~*7nrvGcX3WxLsy=U9LNsb-lknL-NDM zC{5oG1fe#NROct<MaMxUyC}Qx%-4+lwiXuymkCy{aqdU3YXRx0UW|=)=frymbQ|_! z6RE{*nz|sqlxT?YzuAtu$Gog>reBSw$I%17RdxoE<5C^N&9sk!s2b9*#}#v@O@Hgm z2|U7Gs*@hu1JO$H(Mk)%buh~*>paY&Z|_AK<qFt@cXN))G(1YlcAPtmHj#~eco8p^ z;k=X}&?zo@O$8Z`ek!f=!-2z8e*=Ci->f-?cz6jlT-v<MB#)k)myih=`Ssh@;;x}J zO=L&!7fiX11t$*%H7VB5wo{JZjW1%IgWgXvd6F(0HB0F91z5JWRqIufOuu_cLA**Z zmmw%qTf|NDak07Z7<oRn7_*JC+4`$%N{E9m4lEff#9|JoZM34#AXTyroO(bML3A>6 zF>l9?C6EBRpV2&c1~{1$VeSA|G7T(VqyzZr&G>vm87oBq2S%H0D+RbZm}Z`t5Hf$C zFn7<mF7Xu8aH7&p+*&39KYKi-7OJO{aHNxXz_(kUPiXO$h1pPLlg!bekftiGcm@|n zyU(ScX{zKfs^svS;XTOg!KfKY**%|-W9{|s3#Gj(QB-;}YLd871P?8UA`w0!A7QzO zWc7gLe$g*X1OM+=vmP*Ogr9g&xg9ao2KZPbx&pzMzUeef)Ls9B(UPZ{LX-@^?(2ZD z*Z+U=^^chSPm-dhm%j2s>X*;R_<CwObRhq40Y8dd5<<dXNQ8l6-%BVUErnsW_sE=( z!O55%E<r;dR?1ZB7kCT6)?R}f%i9(t4I`(jRGO@oHWn&3E^YD89&5@Qt(w|48ry2S zoB*bqc<9JaF&R#mfSH)?(VOoPH^|n<_Q??t4d71}zL8pZFSq=ZVZAbUXAgY3rpEPs z*E|~UI$I6~XuCv1s$RdQ4N69~cKH@=e_A|e27l4@UEM(7&fSRN9z5g2ewF7V;V0|_ z=12=mDl3(R0jywl;U)QpaAsH_PgBTqbB<5jq*;UWQu2(lrC~=|Se2^MPArRvD40tM z$6+K3$?BZv_k1Lilvq+L^AWEgNXK;#OAeJA_E;9iRbUP&JMUDK3P(=3R7Wt=Xr>D^ z#Ug0tYczRP$s!6w<27;5Mw0QT3uNO5xY($|*-DoR1cq8H9l}_^O(=g5jLnbU5*SLx zGpjfy(NPyjL`^Oln_$u<sR&ps8aH4yQp{NS*Q%a0(@~r)xRsM(JD)2?t_AvTl475@ zn)K3fs>I6(aEh(iS4G=$%0;n39C(iw79RlXG>W&8;R1h;oVaODw2nw^v{~`j(1K8$ z5pHKrj2wJhMfw0Sos}kyOS48Dw_~=ka$0ZPb!9=_FhfOx9NpMxd80!a-$dKOmOGDW zi$G74Sd(-u8c!%35lL|GkyxZdlYUCML{V-Ovq{g}SXea9t`pYM^ioot&1_(85oVZ6 z<s?t5SIP|7m@nUo6dTqLSV$Mw1iMSK9^=Y!=}N7?-PaxM9#3DE^=sKgl9p=DV}~&@ zYdmLsN{tFAHLs}Iz)`3D?jriOYih|L;lqCLr?EMkk$H3Jm`S~ZH1^!;Tq-R#WnmdP zf~|;k11U<Y>UhCw#HkfCg7mRT3|>99{swr3FlA@_$RnE?714^o;vps4j4}u=PfUAd zMmV3j;Rogci^f!ms$Z;gqiy7>soQwo7clLNJ4=JAyrz;=*Yhe8q7<f70~h#PTjf4! z`0u<%63_AFLQ|%)&bj!``Adogjy)l3r;m#MCF2Ll@`C+DbXll(5X*?|^R&zX-3nTi zgX@_6?<66$SvPV}kNSPWYtA`Cqk&5%{)w!d%IT$Hwf29}8g}kIASctNu@^*I`02h; z;~Bi)HM29<Bc+-vXFbX5<q1<Hn`1ta&DKiE4CIn%PI!-mi4Wl&UZ0*jccw<R`3d&% zL@$iFZ>*$Du970BXW89Xyq92M4GSkNS-6uVN~Y4r7iG>{OyW=R?@DmRoi9GS^QtbP zFy2DB`|uZTv8|ow|Jcz6?C=10U$*_l2oWiacRwyoLafS!EO%Lv8N-*U8V+2<_~eEA zgPG-klSM19k%(%;3YM|>F||hE4>7GMA(GaOvZBrE{$t|Hvg(C2^<r300+*m%A4XsQ zfiH7ngallK6VqHlYML1}?e$Oz<lC(8-7kHC&IxkJU#Gz3a)OPkV=(kNkLy0Bxm5A~ zcc{pE&{?zIUPuZ>PEs<HpN+yarVkty9riOIB=3P{s0uCLYa6`aQz_aV&@Rrup^a{T zTUBku`R)w=_*fR7p+jOGP?y*LP$rd9b2(C7D2v%Rxkl8$A(2n!M(aM%FeOa}8QGbF z`hPlm3$Ut|_I+4HLP9|4?(R<M?(W!hONW$ncXxw;G}0m6-6;(sU4jVwx4+|g@oas4 z&-X2^b#36<bKf(w*3>i4+)w#P4jE2XDi2SBm1?<Kqf$kM@%mEPmcN#r-lwaPc=Mg) z*m-p&Aw#uy3e*D=c!Su=D$*H%aU(oSf)=;R&*{PKh6q%|{w38RYSi*@a6tKAc<inN zNb4RNNbAl69}bKaO(;chUy82_j5pdyDGnGe@=oYiVf%)@Zc6rOz-<ulQKo}s<`}k$ zz=)K6#!b71jt^)jDE!kxeOg_~;&1!o1GGpA6mm3Kczg<;zQP~%kv}k=HpQ5r@<`cd zb!k6jFf8v1D)wJ97HgyDlElP>6NiSkOp-IT<|r}L9)4tLI_KJ*GKhv16IV}An+Jyx z=Mk`vCXkt-qg|ah5=GD;g5<swWwFJHTUMQ?a%|=(gV}~ftE<qvKh(Z1Rar-oEJY08 zphNLm!uC|e04aV}K+`%33%)`8C7_Qi{)BB1Owc-76aG3*%cRe0&&9-LwVv~O!*$1A ze4@(brH@f8wSP}01~4{G$#xd0GrC=&J7=yV@ZgzSsTgY&OlJZ4K`>gZQugsv!#)$@ zkE=6=6W9u9VWiGjr|MgyF<&XcKX&S3oN{c{jt-*1HHaQgY({yjZiWW97rha^TxZy< z2%-5X;0EBP>(Y9|x*603*Pz-eMF5*#4M;F`QjTBH>r<Tu44nq(OCKtpe>rO$r3iz5 z?_nH<vaQE^^u_XG(5Lr(oB6nao4oQ=8KZ&JS)ZJ;U$^j52(`Qhcb3d1zaXxH?Z+kK zo{eCoc#L2r61&TkC9Kh*^Ll2d0jYxGOHsw95>ysyjnizhZQMXo1gz7b{p`yZ8Q78^ zFJ3&CzM9fzAqb6ac}@00d*zjW`)TBzL=s$M`X*0{z8$pkd2@#4CGyKEhzq<e3VICa zVXrK@mmDpkVLJj>QR!7*Lo@mhw`yNEE6~+nF3p;Qp;x#-C)N5qQD)z#rmZ#)g*~Nk z)#HPdF_V$0wlJ4f3HFy&fTB#7Iq|HwGdd#P3k=p3dcpfCfn$O)<o&8NigX+IQx(_k zt!6Q*5R+hsCh4Y5&L}(2M7@TsP%b<s3-cE9`BP8(x0*Zxu|;tEsxvN1!CX^JH+%d{ z#%PF!5ur~~Lz^$}zXbQdR+-dsE=BRoSrrP_RT)>C7;y;;J4Za_;+DEH%|8nKwnWcD zBgHX)JrDRqtn(hC+?fV5QVpv1^3=t2!q~AVwMBXohuW@6p`!h>>C58%sth4+Baw|u zh&>N1`t(FHKv(P+@nT$Mvcl){&d%Y5dx|&jkUxjpUO3ii1*^l<K6aNn&v)ViNI=Dr ztm6c`Y(E;r83@Bjjq?m89qD)2N8WVbZnT@Rqwk`qe%u_QX;6s(1_KFnNvb;9|KvKq zdzV4af>$zCE*>59`AvAja%`Bfry-`?(Oo?5wY|b4YM0lC?*o7_G$QC~QwKslQTWac z#;%`sWIt8-mVa1|2KH=u!^ukn-3xyQcm4@|+Ra&~nNBi0F81BZT$XgH@$2h2wk2W% znp<w2i)e>o1OZuQ1N>bX52II+lsnQ`WVUxmZ?4fR_f0243_m`mbc3`?iy*HBJI)p2 z`GQ{`uS;@;e1COn-vgE2D!>EheLBCF-+ok-x5X8Cu>4H}98dH^O(VlqQwE>jlLcs> zNG`aSgDNHnH8zWw?h!<tB)vN)5FY@JvMJNFRSGqQ)8Y6+POoR72O@Ig1amD;pbM}A z!_G?aSt=r|UNn^U7MVI;joP{9U6+jGunRq9JBhz0DQl2OWLL^=?3M-W(HD3y4YIDj zts@CkCLg5q>tye^aN|%>@k;h`Z_H6*py3hHO^6PE1-GSbkhG%wg;+vVo&dc)3~9&` zPtZtJyCqCdrFUIEt%Gs_?J``ycD16pKm^bZn>4xq3i>9{b`Ri6y<g65g>H|K>kfC; zI5l&P)4NHPR)*R0DUcyB4!|2cir(Y1&Bsn3X8v4D(#QW<drU=}`!M~>8Dtv@D)CCO zadQC85Zy=Rkrhm9&csynbm>B_nwMTFah9ETd<oWfFWz$bNGQ;eULeJVXJ<f7A9L45 zz2(RndIlv@ndf(ylJ5dB@f_#i*f_<xdRxxiY#<f0XG}~)k}3h-pn*{5Wf+rkTNVfU zvu7LcI^RM=F>NcLU@J{haekA|9*DA2pY&A|FS*L!*O+>@Q$00FeL+2lg2NWLITxH5 z0l;yj=vQWI@q~jVn~+5MG!mV@Y`gE958tV#UcO#56<kOrH+so9B!Y&j2j%NPHe&Kt z*Jlv04zu4KkKUnN@2v|A_s~D)D~i}2TX9VI`eb&ulUJY3bIAP`=W_MQ<p6TmCi3~9 zR#!gE)1vNaJJy(qr<zWLByJV5kQKURonRb61RNn2oARD`y%(o&)^UlpvCR>hn>b69 zM;lq+P@M<xt{ZP2IVZlQmaF6)dV;0T>W=cIvIXkQmKS$*7l|}AW%6zETA2b`qD*cL z(=k4-4=t<W8Q>6FzQo#uMXVwF{4HvE%%tGbiOlO)Q3<z@q8w;^CEVc#*w>Y6D<5W$ z9pm>%TBUI99MC`N9S$crpOCr4sW<X@hV&j6o?mAg;rC>JHP)$Zg#N<bE4!*`Hm2T= z5r3kQe#GwXxWTu&KEtE5F89pZOnr%ScQ_|6s2i!Oem1r{mi@4bd|9F-80WNw;hloq ztW<@XG@{uj{gHBv_JxQ5>Xa~j?WeVo03P3}_w%##A@F|Bjo-nNxJZX%lbcyQtG8sO zWKHes>38e-!hu1$6VvY+W-z?<942r=i&i<88UGWdQHuMQjWC-rs$7xE<e><_-PNgC z_aIqBfG^4puRkogKc%I-rLIVF=M8jCh?C4!M|Q=_kO&3gwwjv$ay{FUDs?k7xr%jD zHreor1+#e1_;6|2wGPtz$``x}nzWQFj8V&Wm8Tu#oaqM<$BLh+Xis=Tt+bzEpC}w) z_c&qJ6u&eWHDb<>p;%F_>|`0p6kXYpw0B_3sIT@!=fWHH`M{FYdkF}*CxT|`v%pvx z#F#^4tdS0|O9M1#db<blyW@3UaX=m-VC3C*AzpL%Zt@gt;JJjmzbO>%MF(5Opy;i( zL(Pc2aM4*f_Bme@o{xMrsO=)&>YKQw+)P-`FwEHR4vjU>#9~X7ElQ#sRMjR^Cd)wl zg^67Bgn9CK=WP%Ar>T4J<WQ3|95lNv@(iOW)=p_LUL!1-VhctD*z|@+j`co`Z66v! z`^J+M`P0R<B6PayFir_vpL#_kCgB2Hfy(fZ`D(OZ){RgJUm9WP5I9_=8@>!}DcLDe z=ehSmTp##KyQ78cmArL=IjOD6+n@jHCbOatm)#4l$t5YV?q-J86T&;>lEyK&9(XLh zr{kPuX+P8LN%rd%8&&Ia)iKX_%=j`Mr*)c)cO1`-B$XBvoT3yQCDKA>8F0KL$GpHL zPe?6dkE&T+VX=<EEjvDP6;>uJOjXyrq$BQ`a8H@wN1%0nw4qBI$2zBx)ID^6;Ux+? z<t2Ij61D<6$bR>u{?X$_1hoz9d^jkDJpT-N6+HDNo%^MQ2~yqsSBJj4@5;|<UR`pr zmGgR>1@w+BE04#@Jo4I63<~?O?ok%g%vQakTJKpMsk&oeVES1>cnaF7ZkFpqN6lx` zzD+YhR%wq2DP0fJCNC}CXK`g{AA6*}!O}%#0!Tdho4ooh&a5&{xtcFmjO4%Kj$f(1 zTk||{u|*?tAT{{<)?PmD_$JVA;dw;UF+x~|!q-EE*Oy?gFIlB*^``@ob2VL?rogtP z0M34@?<n^#kB5}f662tf-FPK7+=$$^thQJ_=0e1;hhQ6iIZGfFw7z@lxmVdxQ!#YD z@@+IDMv|25+N!a_rCLgW@%EzHQeAiFUBy6#_b&Y<Vx~v$D%CCO9<Fm)nU)Mm7hLx( z+OBhjHD+ka+uR|MOWPrSpW8<_sF$kMvQ>2$;}n;^OAV2?o|zHg`+@Adk+&@Syd!rS zWvW$e5w{onua4sp+jHuJ&olMz#V53Z5y-FkcJDz>Wk%_J>COk5<0ya*aZLZl9LH}A zJhJ`Q-n9K+c8=0`FWE^x^xn4Fa7PDUc;v2+us(dSaoIUR4D#QQh91R!${|j{)=Zy1 zG;hqgdhSklM-VKL6HNC3&B(p1B)2Nshe7)F=-HBe=8o%OhK1MN*Gq6dBuPvqDRVJ{ z;zVNY?wSB%W0s^OMR_HL(Ws)va7eWGF*MWx<1wG7hZ}o=B62D?i|&0b1<IN$XX^T5 z%N70y9}Uqn!<;jV>4_7UG287YDr%?aYMMpeCkY1i`b+H!J9sqrvKc<L)uh7BYtp$7 zYI<!fmPPG_ASTr99~(~+PA}ncJZBQ?zzQuZe0K(a=Q&Avvvj5@5|G_LL3Njq`V7sn zdf%<~EuFL#J#oEiV!xvl<9P$2kDP5VAd93fXChCQ#nOSmIT%G(2uky?4Qo5owmXID zPAT@9%6D<uJTww5Ja3&599`v^?a;}l{us=_Wtuw*M$fZqKYpqvO`YjRlffDE!{g+5 zVdH3imDV!Daaif@$DR7rx0Nn5OU^lipTe=3Ner}<ZD~S<wM>#Y6c8At@QiLSwj)<? z9DFyN;{iDvLI|AY0%>@ifz~Z|c$lOMA@?cPqFRmZ%_>bz2X4(B=`^3;MDjsEeAO=? zSoD&+L>A|fGt7+6kF2@LqhL06sD%|~YsIe=EcWqy{e_61N_D(*CacnMvyXMjP87HI z4PT6!$fzxx{}=>jeqzkkoN+!r9e|@lZUN4pn(T28v`k=_vIhTn^i9O3qTqd)-%!QQ z<jd9AM{1@GwcZVg<oQg*6vUa`*N7fm@6=<JHmDT7;wr~7?PE;ryAmsxO3!F)HdDJZ zZ!QzyVesZ-HR*BPFuEi5(@$^b*$~XJQiKDz`*!s!?0jTk#y+Jfp`+uM$1-O~4B%yX z+cTq@3cZxR=L2UN^929r#)5p0epn*#txp3Qq5sue|JMuum+kq-?!4lG4Dc8x&m=-E zHI?>YB6*6B@&b(!#X4C~59SLZuorNU_wWZA36{>O%iX)VS5NNZh49C_ppI>?)<pHg zoC~M~7N1<%@2qgyAM)&s4At>wwml}_0MLzOXT>lmo#&Ew6d?mu8~~I_^4VGBQtC<b z$ksvC_Au5I^}~oep6LYDHQgU=CfBT&wctG#OGlZ`U53Xlwd_zWXP=>Ake;RQa5DL` z1PFDPsKb3CS$v;RhlQ1J@AHa1VRuuxp}NOIvrC>4$$A0Ix0VpAc0lfG%8{mR{TRQ( zbXM#1Tci3H*Wt>cVuMta^6^z`=^B@j+YhJqq9?>zZPxyg2U(wvod=uwJs{8gtpyab zXHQX<0FOGW6+dw&%c_qMUOI^+Rnb?&HB7Fee|33p4#8i>%_ev(aTm7N1f#6lV%28O zQ`tQh$VDjy8x(Lh#$rg1Kco$Bw%gULq+lc4$&HFGvLMO30QBSDvZ#*~hEHVZ`5=Kw z3y^9D512@P%d~s{x!lrHeL4!TzL`9(ITC97`Cwnn8PSdxPG@0_v{No|kfu3DbtF}K zuoP+88j4dP+Bn7hlGwU$BJy+LN6g&d3HJWMAd1P9xCXG-_P)raipYg5R{KQO$j;I9 z1y1cw#13K|&kfsRZ@qQC<>j=|OC?*v1|VrY$s=2!{}e33aQcZghqc@YsHKq^)kpkg z><rgHH|yOI5lM&60F%aLmgk4vw(wR1EIb5`i$0Au5`<D^w)OTd#h}+IH6bl|l~ASd zLvu(rRS+}&bCyqZ2wVl~^^prNkrvH9d*H8$eC}I=Lk<&VlA|Xu!EY|<BPc7hfuH3{ zimrE3hw4oVADm#2l1S}++Z=<wd?>B;CWNX+K=u|y#N)O>n5YuyvPl5cO6B^scmG?J z<KOI{m8vYOVhJPPOIHCptG0+(@oDno9z%+WYeJZ3o5CpRcLo;t)SxsGOR8ZyZ5#$b zw(a#c9c64Zm4A&1!R7I|JUTv-JF-k*?(bpUN=jpL-X2NkYP)G-a=sjG^STB9fZ|TI z2KStBC4MY)u~}R~ttZ4!`Q6EyTl1IJpg2DjV&bKCsOK}4;{gbj^8rouy{R|+?!@hZ zrT8zc1rczMy*pB^4vA?9JM$^^MTHWwjZ`LC+#=fXFtkk&w}E=6W6@I%j`<aBr76yF z2iRr;;zXsJGboB12C5WuO)1^<>C8ix)E1P<X_%5bHEQmQRnjC@<;fyCELPI*x5IBe zghy@a&H_C7B&O2<y933&B7r5YnoJG>lhNaw8FpD+b|D$z`Id^4)rJe78MNiBga?Z- z0$L&MRTieSB1_E#KaN*H#Ns1}?zOA%Ybr{G+Sn3moXTVZj=L`nt?D&-MjOMz-Yq&@ z$P3h23d_F8Dcf*?txX7}p>nM<l{Qn?uEgn@yf~}YdEK1CaY|S)sKm5%E_A>*s+65t z1il8bHHsBynUK|aEXSjzY6sz1nZ%|%XeWTcGLRyRl@q4YAR)JovbdTTY&7u>@}28A zgV^<e**a7WuD0_57|#$M-bDY4Dl!_>Npp<sAnkx7os>?}I!?3K7IXu9ml-Lw;w@9m zBYTeU+Seh8uJ-w?4e_6byq0f7>O3xm<T-=IFd&#pS0-Fods9njUMwYsRqq<?zWYLD zfQV)Ux0VAf-GhE9#2pvi*%}6TncJD=b`5tFqkr(+7|GT8HW*ql076rUzXuFAn0$b} ztB~=fV48IwJVxdQEM(u+^GG9i|8m!}@Gx>(hO}Y=fgU5^vW|>0yQ^0+?}LT55ei$i zzlU-iRbd8TRX9Ept<Y_yCY)9AN#@44E0QQ@l+Wm>%h%ariV=%u%F@@FA>U*XdA<Yz zYB4@$IL70Mq*UiYdF!wju0Kb}PJtpQQvXI<J8O>alcH%>#5_a&w)g`<dBOnWNnH-A z)lTdxEzfBq6Cw*wE~=_CdPlgv%<-3OKBrm26@_ub2DA4t-_B!rBRHZ>uW%3}m?vP- zc5}DkuF6ruKDwEYj+2YTSQ9=rkp19U5P@(zRm(nLod(sG9{~nw1BUoS2OFDXa{xfw zZ~UaZLFUZxfQ*9?_X?*~`d;nn-BbaefLJ`DT13KF6?T5Mnt;v5d>H}s)aAIzJcs#B z|CuXPJKww}hWBKsUfks#Kh$)ptp?5U1b@ttXFRbe_BZ&_R9XC6CA4WhWhMUE9Y2H4 z{w#CBCR<)Fd1M;mx*m?Z=L-^1kv1WKtqG(BjMiR4M^5yN4rlFM6oGUS2Wf~7Z@e*- ze84Vr`Bmi!(a1y}-m^HHMpbAiKPVEv<svW7U^@p-C~aOLvRc5ptq-Alx_+3VLn`vT z$Arz4y`QO%gQ^)TU&5;~ub7;p4l60Z!kSr0$0Tamnl#x6jaDz5FuP`OnuT?gam3fh zW-E3aE=~5{^<i!iqEDAEgjm^e(`^Zl3sQqfd^*9z4K~ILcPZAUdvFIn#Zy+03^i4d zQ0rAf=8gKh!XB~PebW8sh##BYjM5Ao@$rEpKJ~9X;V&+D|8QYf)cilih1xor1HybQ zWPWnAY?^@(v}U=ICkzB;U3rkcuLQZ&1HKRSG)P`Bqol1@S-}!fLLQf*7^C~N`N-n# z$>|(7=|}D#Ihfk+-S5Hlkf<mO<>ch02z&$(zS3vrYz2g*ic{xBy~*gIp(eG}^gMc7 zPu2Eivnp@BH3SOgx!aJXttx*()!=2)%Bf$Gs^4cCs@)=(PJNxhH5lVY&qSZ<ermz; z*AW$ATa5X{5*^QV)cKip=adjN-+zu<Qbj`b8aKDJ-$7YqrHCn=V==a76z#J|4|C=6 ziQA;AHP{X4+#{W}C>Yaa?A^LhZW`B9(N?fx<^gCb(VE%3QpA*_Pohgp6vCB36iVaq zc1TI%L2Le?kuv?6Dq`H+W>AqnjyEzUBK948|DB|)U0_4DzWF#7L{agwo%y$hC>->r z4|_g_6ZC!n2=GF4RqVh6$$reQ(bG0K)i9(oC1t6kY)R@DNxicxGxejwL2sB<>l#w4 zE$QkyFI^(kZ#eE5srv*JDRIqRp2Totc8I%{jWhC$GrPWVc&gE1(8#?k!xDEQ)Tu~e zdU@aD8enALmN@%1FmWUz;4p}41)@c>Fg}1vv~q>xD}KC#sF|L&FU);^Ye|Q;1#^ps z)WmmdQI2;%?S%6i86-GD88>r|(nJackvJ#50vG6fm$1GWf*f6>oBiDKG0Kkw<U}GI zL^YF-xy$sYcJMmiN0ngM(V(;uc)nRNAg-<2BZ%P*)kWkIK)?XHJSmc`?IKMeee=02 z(zZ=v_AxI~%FVp$EfT<efis=BFx^Xmds6b*A?AKP<|2-I4XThUW&>b17KPnS%7CKb zB7$V58cTd8x*NXg=uEX8Man_cDu;)4+P}<f?2h4JBG{uqPK9;88sh{0wi3bcn?YjF z^|SE#+I9}S_<Im1WVZg0@?>BuC<Z#C{(lf6;dchu8U3}j{gVnnsj@ck@Dn0Wop$4a zQzc6Fn;?A(fkDx1$Y=x<Vtp}8<{~qnS9J#(Xmw57hcl6x>vYH6P|`x-#CMOp;%u$e z&BZNHgXz-KlbLp;j)s<RuYu0Dk0j^92qMJcIT=sQByuycFbf!R2}09C)Zr<&m8ngG z0P@{2A&&){_n0x4%21>i^~BI{<VR;A%GE_$&6BTtwdzHzZV-&q3D$YomWT8a9H7dS z$%$Wg)<_@Gc6KJ~xm38<4rN(#v6!<GfV&NgbR){Tbm#Q(jThOK4Ea-d@V@PH!cd6R zd0k-es%5)FmCAJ~x4G3~F<`E?{xhbZCj36*WNPr8MOFJp6}VGB=P&?z3I*=5D)%ef zrVKh)paJ6Zd?6fwcCTu!j5xfj@(cEjAHHYt{7uWQ57*1(0u{TI9P|C8Qx^s8U5KJA zBJaG2P_>!yNLWs5fK+!##G;yVWq|<>7TlosfaWN-;C@oag~V`3rZM_HN`kpF`u1p# ztNTl4`j*Lf>>3NIoiu{ZrM9&E5H~ozq-Qz@Lkbp-xdm>FbHQ2KCc8WD7kt?=R*kG# z!rQ178&ZoU(~U<;lsg@n216Ze3rB2FwqjbZ=u|J?nN%<4J9(Bl(90xevE|7ejUYm9 zg@E_xX}u2d%O1mpA2XzjRwWinvSeg)gHABeMH(2!A^g@~4l%8e0WWAkBvv60Cr>TR zQB1%EQ<IS=62wktFl3@bq?<$7Fi$7<vgVpF%7SI3{e{_?kPXB}oJoDi)6AVm9yCe> zUoZeUdqjh+1gFo6h~C~z#A57mf5i<Z>bmq$y_uVtA_kWv8X)CzfVEooDaY!#P?5$Y zGPKXbE<75nc%D-|w4OrP#;87oL@2^4+sxKah;a-5&z_&SUf~-z(1}bP=tM^GYtR3a z!x4zjSa^)KWG6jxfUI#{<26g$iAI;o_+B{LXY@WfWEdEl6%#8s3@b`?&Tm#aSK!~| z^%DdrXnijW`d!ajWuKApw&{L+WCPpFialo&^dZ9jC7A%BO`2ZF&YUDe;Yu|zFuv`2 z)BE*7Lkay)M7uohJ)446X``0x0%PzPTWY92`1Oq4a2D_7V0wypPnXFR)WM0IlFgg@ zqz#hv<D0q2`XDaeeh`;vW;a@)LS>2xJEQL8eu}O;e(w4rSA?5|eZHbS6jEN<LYrmK zS>ytJBq59?bOf>Wrl8<u806q;G;j++Cn$-#N@*)|YQ1Oc1>ySZH36H(6fGR#vHM6q zn}!7!I@4$*+LFXs{x?|=q2*QtYT%Lw3+5(8uc0j8o3}TrG(zSV#>4wo6~)u|R+Yx# z?0$AspZDjv{df<u+~yn3WB3HDes|!>v417~C17Oy%Fal{%+B6H(NX`$Bl>II-L3N3 zZc+sKZbqewU*&_Xt;9k=%4*aVYBvE1n&JZS7Uqjd%n8nOQmzh^x#vWK{;In~=QO)g zT-n3OU(1<pC%^0p(x=6TZRnBNp&Ne%Y8tlj>@3QfL|$g1d2xeBb@O15Rl0<wkYLDD z+ie<()sq`w;04+|&9rf+B&EVGd3>1+hmpup2De7p%Yrd$E7(In!*R+;IJZh}v!svi z;7N~pq8<?3Y@|9}TxvDz@LqvwilszncIcoPuvf?2l)cCypvx@P+0bR<xPW)Z*}?M4 zdDSjh8q#cdVFp76x>KZDXXap0qd_D=Y^B)rz4S0^SF=&v6YYTAV$ad43#x!+n~-6< zK{8*vWoAdW(gGGt&URD}@g6tMoY(+Lw=vvxhfIIK9AjvNF_(W}1Rxn(mp;tJfDV<0 zbJN0t(@Xb8UeO{&T{$$uDrs7)j$}=?WsuDl+T2N5Y<4TMHGOMcocPr$%~(yvtKv(n z`U96d!D0cb9>Dx2zz$m&lAhazs%UeR^K*gb>d8CPs+?qlpfA;t{InX<O#)_wb~ZJz zOhlV1HZTS-A}6%HRsloNhRCT$<a1q#*t!(tfKY;MvTUEGs}w*#+XT*;w;2zk8}UYw zZvt{!yPw_#j^N(vBPyr&>a)^2ryC(FU(Zc6Xbnnh`lg`K&g^JeS>}^c0MJKUCfV+~ z<?PrWsmA0`k*MHcTSYZ^SNU>V(EN0Z5ztoN;hqcj!8V+VRbSltJ<~|y`U+9#wv|~H zNE!j9uXa=dec@JQSgJ6N6@Il&tzCBJv9#ldR`Lm*<)YwH4tdlAlG0Fl8Nfa(J~<et z>c%DQ2AA-}x8D=p(l#n1+hgx;N;1Aq?lq@{Lt9FKu89CjnnHD1G_@p;%L<QHk<<H# z%Q>p`+b@ttb33!E_Xt;QUD9~nRQl&xAro9-{+&6^ljK2f-d>&q<xz}hO>y&d#0xwH z@slNv@ULKp!Cf*JHuS@#4c?F->WjPc)yiuSargAIEg>muRxzY?Hzdq@G5CS)U1*Et zE2SLh=@DI1J(guiy2Igq(?(xI9WL%g^f@{5Hmr|!Qz4`vn|LjrtO=b~I6~5EU5Fxy z;-#<)6w#<N5=AghGQ%g7({wVc#=`dCLpXV5h7n_i_A0Szj?Fpl7Bn=iuV=WGtR6Uu zNu&$<aar<@2rLPLnQ=VQHv9D;70`**YaQz!mx@i<tJ2gK&Scqi7A(<=BTPK&WDwnB za&}mmmJHiIiN4{Xt$&GnOmI<>w=DkpSthAu+E;OL?!?6C9Mwt*o(@68(Jhvs-eX4V z=d=>HI|`3J%H5X|gSrC8KH^IL?h5=3ID6svwHH@(wRbSG`Zsor^q4`3PCn#-(YX?< z_q8+T)51$E0xyKR{L!LN(G=+9K6$3#PDT^IAe|Igkx=!4#rqKWoXiZdh`&ocjp=Ok zemJe6*{it~>;sr(B0fSmp(S#*y5I0)OOz~Oe6Im+($S}e3tyx7Y6pA8vKCBmSEQDa zLfkm*;uMbTLpcR0)tF_v-lbK%`5>POyI2E(!)2=Rj0p;WKi=|UNt6HsQv0xR3QIK9 zsew(AFyzH!7Azxum{%VC^`cqhGdGbABGQ4cYdNBPTx+XpJ=NUEDeP^e^w^AOE1pQI zP{Us-sk!v$gj}@684E!uWjzvpoF|%v-6hwnitN1sCSg@(>RDCVgU8Ile_-xX`hL6u zzI4*Q)AVu(-ef8{#~P9STQ5t|qIMRoh&S?7Oq+cL6vxG?{NUr@k(~7^%w)<J@%A@# zd>P6nPbDa~4Jw}*p-|cT4p1?)!c0FoB(^DNJ+FDg+LoP6=RgB7Or673WD5MG&C!4< zerd6<aS!6%omMUG1rB_ibwC3Z<FAkOr-Av0C-5&b(0Ijm>q$ODkBvFoy*%cpHGKSt z3uDC6Sc=x<LEuPYV)CJJ#eplV!COGThUp&RV_B{GH(lNctGS5PP8t)W3PB`p#!e<H z&2`mxw>vv@kDzRD)aIO`x}BaWLycA%<XY$Q_q9GVL~J84+zWr<Ganp)3n;}BJ36+T z`Z86y8`!LHC<2a@0gbZjQf1m>(w-D`Pd+uL*rL|etagQ;U&xt_9?7#}=}5HI)cU-0 z%pMA`>Xb7s)|Y)4HKSZOu;{lg=KjeIyXb0{@EM`FTDkLRH`!W%z*lQJ74P%Ka76)H zblrSIzf+dMWbO`g;=(b@{pS)zUcO&GrIFe%&?YeX4r8B2bBArB%-5ZrQ+vonr%AYy z1+u0*K{UVUmV>h5vD!F;6}a%KdMZQLs04oGkp<Pp5IZ^H15**bp}t?2!g>iaC)zI( zT2U9qta5o|6Y+It1)sE8>u&0)W~l$NX@ZQ8UZfB=`($EW6?FT%<IH$}PfK)-mk4_P zM2R%A;4_B^=pW}8R*%_A(E0^3ZUs_4IKk5=Kb3eYmuv+yu}vu`$TpaKMBldJqb{z; zIuVCVdUCjq8w56cf)C~yB)uDYvYVpK6nxz>{EoRhOrb9)z@3r8y?Z99FNLDE;7V=Q zotj&igu*Rh^VQn3MQKBq!T{yTwGhn1YL6k*?j?{_ek5xe8#i#GG4S-a_Re2lssG!} z`Y-d0BcOdB@!m?4y&hMN68}#0-IIlm_xO)d#}ugX{q^OZe{-@LeJyv`cY&ze4t2~! zKb{qX-j;kt{?gC(vW%}X4pm@1F?~LH{^Q8d@X$dy@5ff~p!J3zmA>H`A)y+6RB_h* zZfIO+bd=*LiymR<B*bH}4|&jZ1;Rlt)T_a7*adShM5rJM;aVs<hX!30`%#oQG{-jm zD&ObtHpCT_P=%-~4Wp~enl~CtGYwtLvfj^jpzaLp>w{asW%xxaVl33_xtdVrrqIPn zc@y8oMJvNtgcO~4i0`f)GCFkWY8EF?4duLVjHTdb6oYLnO9}Q-pe{CKQJL)hV8)JI z$mVA0Dq&7Z1TbYdSC(WbJ+IBjXngZTu&I+vHF|>Zo$757{8lL;8Zr-Exkf?3jzN5k z_d<rMUMzxj_t06F^6GdZlck$oF?EfDxYJCXv!(uHV~h_Kt`vB!PA>9I>{>^J?!l)< zNd$7E9FVrta}3qy3L7Ys$^fRWNuu^hs^{*eXvazd&+Q*?lTfc>2+EdP(o0P_Z05HX zVKsfFAQ{t^CRu~Dw(CuJ>tvx*p$5@flA>QRl455b&{*U?xU8`)nF2T$uu_(l8VNtq z?pBiRQIckGzk8W&SFSB=g6eG`<XB)>ZC;6v9w`?eF*S}3E@N`2ropeHP<uVaPLTC% zqy$w^_c}_<gmvnjqf4z8v7W!kbnw_pCaVr@HqPLVRLgLa+U(mBw1Y1YkfZY`o(ZmI zd$;9N{-eGjxL*hB#@*grySry0?F595vZc)8)T9=+q`pOl^9Z0qhdJuJjlQIHm{=Ha zw0sV438TXrP8;JG9kFkVG7FZP+b_fzpy`n6`xQ+37%?1*xBUVl<5-os{un%b(3f|O z_e}*Zp|oPuL9Xl3qjk<Yt~bs+$fVKCE^jU8x$L?}!RFx>)E}o?qJkyVEI;K$!)bWY zt9>4WmDVJh7U~m$|K`T#hF!v|znj^=M;69uXrFys#51XT;DbMr4H)>7UQ1e2(cuQf z4kr~Tt1tpBB2GaJ(|j~lHgW40EgMMVqR6eJoJig1SBg|2=$~4I3P0eP$q%_`sS&4~ z26=&a&tLjQbch1`cVXa-2fTl1y8}->|Nqu?uVrNTov!=VKh)g89wUPTgAzkSKZ57_ zr=B^mcldE3K04t4{;RaG5<iW&j0o`H?mv@F)~_PT0KZ@gypg|u^T(Cv{e3O@pJdYf zGU6h_ib}N7B0nR*f)E2ox*ykf;v(Sh-(}VRlmfV7`YFZmj}$+I*#DI9#oy^3eW$wm zXTl%4`oA!n`WPDL0>3&9yovq;@aR#VHx+R1^^*kr-vEEd!uea68Z<{R%_DD6fn&T4 zu;fDj07L-(_fLSJGdkeh&c&7A(ZLj`7iwnkAcqUexU;WjUkqeg1m1-IU<?H2;Wv!p zf0yB}^(83YqL?rx1khY22YeFWw~GJ1^1N+<rsUt?0mV!$9qoZ9l|;-ejra`=jO^{D z0oDMMzlH8W2@J};8})#dfe0j_`5PVhndjXLr2GxRk09DWYlX3yi6c-M7$}eZ`@92V zq`lh)mKp=t4Cwy>ZTIZA(4dtr2Gr`e{BIejlCgS<33MB=1!8?a74!F%=Uo7N`F@k} ze+1C_eU4Y_$mvdjc<J~+Q~lq7z|TDICs@D51fC3Y09ac79!+!!v#SGG{&--6|7X=Q zQ~nlB;MFUK?|}+zzDFnrnmQlC6M#N7W6i}Aeqgn}2ZlWVuH63jmFLY*_gm;6Pfggx z$iVSGTF#)Tpn=pMQ2T(3p5Iid=<ma-gW`h5%zVIQ0k+ED;QpxA|JJNQqoqCIg-ZMu z?;nr;zr{`i%{6E&t_PM_so%2vxg~?f7<qt$QTorQ^rK_@?a1_V{=Q3kfQATp089e{ z{<nZ3peWx{0h|B-UEc$3{B?crw9}OTQOfT!A)twaK*DbPojlKb@YlqDZ}<N+4F)lJ zg2IEwCwPDdQ~xDzKf(T3urd7FEWg(qXe59K)PsM6`iHmxp!o+CUVmWP)%q9t|JmyY zSUQ`ZW!OQfKvl0FsAhEkMD<@ZutDKL^_w5yEdYOl|M4XLq~;7t1*#?aKqX=JFR1>i zwEtCIphA2PBzBhng5=M#e4r%)RW5rVD|_`PvY$7BK`}w~d>%0O9sY#*<AMAI{nsBq z?WuvXfGWp4u<Sbh!t#G;zv{?<VuGHkf561|{1x-xpRot!0X^ycz;o{P3(v2e$G<xF z4ay06Qul%Lt<V4F{C#%?>LUAq=^OjMF^PY5m<7!=s5jyRfosCQAo#hL`h5vN-M}6Q z0Li}){5?wi8)GVHNkF|U9*8V5ej)nhb^TLw1KqiPK(@{P1^L&P=`ZNt?_+}&0(8Uh zfyyZFPgMV7ECt;Jdw|`|{}b$w4&x77VxR>8wUs|GQ5FBf1UlvasqX$qfk5rI4>Wfr zztH>y`=daAef**C12yJ7;LDf&3;h3X+5@dGPy@vS(RSs3CWimbTp=g<eRcTg5gqWy N6sQqkUiJO2{|C|0X_No} diff --git a/commonmark-android-test/gradle/wrapper/gradle-wrapper.properties b/commonmark-android-test/gradle/wrapper/gradle-wrapper.properties index 122a0dca2..1b16c34a7 100644 --- a/commonmark-android-test/gradle/wrapper/gradle-wrapper.properties +++ b/commonmark-android-test/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Mon Dec 28 10:00:20 PST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/commonmark-android-test/gradlew b/commonmark-android-test/gradlew index 9d82f7891..2fe81a7d9 100755 --- a/commonmark-android-test/gradlew +++ b/commonmark-android-test/gradlew @@ -1,4 +1,20 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed 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 +# +# https://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. +# ############################################################################## ## @@ -6,20 +22,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# 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 +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"' + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +64,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +75,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# 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 -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +105,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +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 @@ -105,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# 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"` @@ -134,27 +154,30 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` 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" ;; + 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 -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +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" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/commonmark-android-test/gradlew.bat b/commonmark-android-test/gradlew.bat index aec99730b..24467a141 100644 --- a/commonmark-android-test/gradlew.bat +++ b/commonmark-android-test/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -8,14 +24,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@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= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@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" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +62,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +75,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/commonmark-android-test/run.sh b/commonmark-android-test/run.sh deleted file mode 100755 index 840614fdb..000000000 --- a/commonmark-android-test/run.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -set -e -set -x - -version=$(cd .. && mvn help:evaluate -Dexpression=project.version | grep -v '^\[' | tail -1) -autolink_version=$(cd ../commonmark-ext-autolink && mvn help:evaluate -Dexpression=autolink.version | grep -v '^\[' | tail -1) - -touch test.properties -echo "path.report=../report" >> test.properties -echo "version.maven=0.16.1" >> test.properties -echo "version.maven_autolink=0.10.0" >> test.properties -echo "version.snapshot=$version" >> test.properties -echo "version.snapshot_autolink=$autolink_version" >> test.properties - -echo no | android create avd --force -n test -t "android-16" -emulator -avd test -no-audio -no-window & -android-wait-for-emulator - -TERM=dumb ./gradlew --stacktrace :app:connectedSnapshotDebugAndroidTest From 1cced842e89ad04fe6fb1ff82c3b25ca49579f65 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Wed, 16 Dec 2020 01:19:46 +0300 Subject: [PATCH 471/815] Add android compatibility to Github action workflow --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d4893158..9b6d9c795 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,3 +38,17 @@ jobs: - name: Publish coverage uses: codecov/codecov-action@v1 + + android-compatibility: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Android Lint checks + run: cd commonmark-android-test && ./gradlew :app:lint From 78b2fdc3971773d55cd796a896efff7213fe06ff Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Mon, 21 Dec 2020 14:47:18 +0300 Subject: [PATCH 472/815] Remove unused Android test class --- .../android/test/AndroidSupportTest.java | 100 ------------------ 1 file changed, 100 deletions(-) delete mode 100644 commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java diff --git a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java b/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java deleted file mode 100644 index 1a27dd720..000000000 --- a/commonmark-android-test/app/src/androidTest/java/com/atlassian/commonmark/android/test/AndroidSupportTest.java +++ /dev/null @@ -1,100 +0,0 @@ -//package com.atlassian.commonmark.android.test; -// -//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.heading.anchor.HeadingAnchorExtension; -//import org.commonmark.ext.ins.InsExtension; -//import org.commonmark.node.Node; -//import org.commonmark.parser.Parser; -//import org.commonmark.renderer.html.HtmlRenderer; -//import org.commonmark.testutil.TestResources; -//import org.junit.Before; -//import org.junit.Test; -//import org.junit.runner.RunWith; -// -//import android.support.test.runner.AndroidJUnit4; -//import android.test.suitebuilder.annotation.SmallTest; -// -//import java.util.Collections; -// -//import static org.junit.Assert.assertNotNull; -// -//@RunWith(AndroidJUnit4.class) -//@SmallTest -//public class AndroidSupportTest { -// -// private String spec; -// -// @Before -// public void setUp() throws Exception { -// spec = TestResources.readAsString(TestResources.getSpec()); -// } -// -// @Test -// public void parseTest() throws Exception { -// Parser parser = new Parser.Builder().build(); -// -// Node document = parser.parse(spec); -// -// assertNotNull(document); -// } -// -// @Test -// public void autolinkExtensionTest() throws Exception { -// parseWithExtensionsTest(AutolinkExtension.create()); -// } -// -// @Test -// public void strikethroughExtensionTest() throws Exception { -// parseWithExtensionsTest(StrikethroughExtension.create()); -// } -// -// @Test -// public void tablesExtensionTest() throws Exception { -// parseWithExtensionsTest(TablesExtension.create()); -// } -// -// @Test -// public void headingAnchorExtensionTest() throws Exception { -// parseWithExtensionsTest(HeadingAnchorExtension.create()); -// } -// -// @Test -// public void insExtensionTest() throws Exception { -// parseWithExtensionsTest(InsExtension.create()); -// } -// -// @Test -// public void yamlFrontMatterExtensionTest() throws Exception { -// parseWithExtensionsTest(YamlFrontMatterExtension.create()); -// } -// -// @Test -// public void htmlRendererTest() throws Exception { -// Parser parser = Parser.builder().build(); -// HtmlRenderer renderer = HtmlRenderer.builder().build(); -// -// String renderedString = renderer.render(parser.parse(spec)); -// -// assertNotNull(renderedString); -// } -// -// private void parseWithExtensionsTest(Extension extension) throws Exception { -// Parser parser = Parser.builder() -// .extensions(Collections.singletonList(extension)) -// .build(); -// -// Node document = parser.parse(spec); -// assertNotNull(document); -// -// HtmlRenderer renderer = HtmlRenderer.builder() -// .extensions(Collections.singletonList(extension)) -// .build(); -// -// String renderedString = renderer.render(document); -// assertNotNull(renderedString); -// } -//} From 10ecdbd8aadfa62d6d62b89ce6135011ebb2eeee Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Mon, 28 Dec 2020 12:20:07 +0300 Subject: [PATCH 473/815] Increase Android test app min sdk to 19 --- README.md | 2 +- commonmark-android-test/README.md | 1 + commonmark-android-test/app/build.gradle | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d5fa98f4b..895be3108 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ full library with a nice API and the following features: The library is supported on Java 8 and Java 9. 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 15, see the +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/commonmark-android-test/README.md b/commonmark-android-test/README.md index 458b7206b..ed0201725 100644 --- a/commonmark-android-test/README.md +++ b/commonmark-android-test/README.md @@ -2,6 +2,7 @@ commonmark-android-test ======================= This module ensures that commonmark-java is supported on Android by running `lint` checks on library sources. +Current `minSdk` is 19 Requirements: diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 86a7dd1e3..18d91c0e5 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "com.atlassian.commonmark.android.test" - minSdkVersion 16 + minSdkVersion 19 targetSdkVersion 30 versionCode 1 versionName "1.0" From ef99858ab35fe298a56d032144d4bc4b2c2e84b1 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <di@noties.io> Date: Mon, 28 Dec 2020 12:24:41 +0300 Subject: [PATCH 474/815] Update ci.yml to run on pull_request also --- .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 9b6d9c795..19ad24bb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: ci -on: [push] +on: [push, pull_request] jobs: build: From 5b14e31becc91d478f19c7149af603c6f2673b01 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 22:33:16 +1100 Subject: [PATCH 475/815] Change maven groupId and release process --- .github/workflows/release.yml | 35 ++++++ README.md | 4 +- commonmark-ext-autolink/pom.xml | 6 +- commonmark-ext-gfm-strikethrough/pom.xml | 6 +- commonmark-ext-gfm-tables/pom.xml | 6 +- commonmark-ext-heading-anchor/pom.xml | 6 +- commonmark-ext-image-attributes/pom.xml | 6 +- commonmark-ext-ins/pom.xml | 6 +- commonmark-ext-task-list-items/pom.xml | 6 +- commonmark-ext-yaml-front-matter/pom.xml | 6 +- commonmark-integration-test/pom.xml | 20 ++-- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 4 +- pom.xml | 142 ++++++++++++++++++----- 14 files changed, 187 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..3d4c4f50f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,35 @@ +# See: +# https://docs.github.com/en/free-pro-team@latest/actions/guides/publishing-java-packages-with-maven +# https://central.sonatype.org/pages/apache-maven.html +# https://github.com/actions/setup-java + +name: release + +on: + release: + types: [created] + +jobs: + release: + environment: release + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Set up Maven Central repository + uses: actions/setup-java@v1 + with: + java-version: 1.8 + 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 + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable to use for passphrase in release + + - name: Release + run: mvn -B release:prepare release:perform + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/README.md b/README.md index 895be3108..d24fba10d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Coordinates for core library (see all on [Maven Central]): ```xml <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> <version>0.16.1</version> </dependency> @@ -231,7 +231,7 @@ First, add an additional dependency (see [Maven Central] for others): ```xml <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> <version>0.16.1</version> </dependency> diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index d6a19df40..80566ae55 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -17,7 +17,7 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> @@ -27,7 +27,7 @@ </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 80d01118f..0572ff34e 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,12 +13,12 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index babf54640..b37856773 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,12 +13,12 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 07b840e63..de3053966 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,12 +13,12 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 166ea02b1..a16127a34 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,12 +13,12 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 223837493..f33a77001 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,12 +13,12 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 78829c1e0..62a5130ad 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,12 +13,12 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 06c8ce933..ec58b941f 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -3,7 +3,7 @@ <modelVersion>4.0.0</modelVersion> <parent> <artifactId>commonmark-parent</artifactId> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,12 +13,12 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index aa43a6619..1296652af 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,35 +13,35 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> </dependency> @@ -53,7 +53,7 @@ </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index ff1459728..dd8ea6173 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index ff5a74381..fab32886b 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -2,7 +2,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> </parent> @@ -13,7 +13,7 @@ <dependencies> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <scope>test</scope> </dependency> diff --git a/pom.xml b/pom.xml index 4907cedc5..b62834429 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>com.atlassian.pom</groupId> - <artifactId>central-pom</artifactId> - <version>5.0.13</version> - </parent> - <packaging>pom</packaging> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> <version>0.16.2-SNAPSHOT</version> <name>commonmark-java parent</name> @@ -17,7 +11,7 @@ Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted text. </description> - <url>https://github.com/atlassian/commonmark-java</url> + <url>https://github.com/commonmark/commonmark-java</url> <modules> <module>commonmark</module> @@ -53,86 +47,114 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.2</version> + <version>3.2.0</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-install-plugin</artifactId> + <version>3.0.0-M1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> + <version>3.2.0</version> <configuration> <excludePackageNames>*.internal,*.internal.*</excludePackageNames> <!-- The offline links make links from extensions to core work. --> <detectOfflineLinks>false</detectOfflineLinks> <offlineLinks> <offlineLink> - <url>http://static.javadoc.io/com.atlassian.commonmark/commonmark/${project.version}/</url> + <url>http://static.javadoc.io/org.commonmark/commonmark/${project.version}/ + </url> <location>${commonmark.javadoc.location}</location> </offlineLink> </offlineLinks> </configuration> </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-release-plugin</artifactId> - </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>2.22.1</version> + <version>2.22.1</version> </plugin> </plugins> </pluginManagement> + + <plugins> + <plugin> + <groupId>org.sonatype.plugins</groupId> + <artifactId>nexus-staging-maven-plugin</artifactId> + <version>1.6.8</version> + <extensions>true</extensions> + <configuration> + <serverId>ossrh</serverId> + <nexusUrl>https://oss.sonatype.org/</nexusUrl> + <autoReleaseAfterClose>true</autoReleaseAfterClose> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-release-plugin</artifactId> + <version>3.0.0-M1</version> + <configuration> + <autoVersionSubmodules>true</autoVersionSubmodules> + <useReleaseProfile>false</useReleaseProfile> + <releaseProfiles>release</releaseProfiles> + <goals>deploy</goals> + </configuration> + </plugin> + </plugins> </build> <dependencyManagement> <dependencies> <!-- For dependencies between modules --> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> <version>0.16.2-SNAPSHOT</version> </dependency> @@ -157,6 +179,58 @@ </dependencyManagement> <profiles> + <profile> + <id>release</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.2.1</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar-no-fork</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <version>1.6</version> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + <configuration> + <gpgArguments> + <arg>--pinentry-mode</arg> + <arg>loopback</arg> + </gpgArguments> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> <profile> <id>coverage</id> <build> @@ -204,11 +278,21 @@ </developers> <scm> - <connection>scm:git:git@github.com:atlassian/commonmark-java.git</connection> - <!-- Push URL is used for pushing commits and tag when doing a release on internal systems, see https://maven.apache.org/scm/git.html --> - <developerConnection>scm:git:[fetch=]git@github.com:atlassian/commonmark-java.git[push=]git@bitbucket.org:atlassian/commonmark-java.git</developerConnection> - <url>https://github.com/atlassian/commonmark-java</url> + <connection>scm:git:git@github.com:commonmark/commonmark-java.git</connection> + <developerConnection>scm:git:git@github.com:commonmark/commonmark-java.git</developerConnection> + <url>https://github.com/commonmark/commonmark-java</url> <tag>HEAD</tag> </scm> + <distributionManagement> + <snapshotRepository> + <id>ossrh</id> + <url>https://oss.sonatype.org/content/repositories/snapshots</url> + </snapshotRepository> + <repository> + <id>ossrh</id> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> + </project> From 97ee049766e30f04648e949584c3235607beacf7 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 22:35:29 +1100 Subject: [PATCH 476/815] Bump version to 0.17.0-SNAPSHOT mvn release:update-versions -DdevelopmentVersion=0.17.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 80566ae55..ea4959c1d 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 0572ff34e..b49c6d8d1 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index b37856773..755c44fb4 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index de3053966..2f5163f00 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index a16127a34..977023861 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index f33a77001..0f1ad4e6f 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 62a5130ad..26dc79303 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index ec58b941f..e95755f01 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 1296652af..2cbc0b6db 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index dd8ea6173..aa161c369 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index fab32886b..1a1bd3c8a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index b62834429..64bee1b08 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.16.2-SNAPSHOT</version> + <version>0.17.0-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> From 0ae1fa4a4680da28f0ee7abcb194b70c622f89e8 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 22:42:09 +1100 Subject: [PATCH 477/815] Don't change groupId in README yet --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d24fba10d..d86c6f7af 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Coordinates for core library (see all on [Maven Central]): ```xml <dependency> - <groupId>org.commonmark</groupId> + <groupId>com.atlassian.commonmark</groupId> <artifactId>commonmark</artifactId> <version>0.16.1</version> </dependency> From 05d5a0383408aa01f3766d4fcc2f9edb66a45a10 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 22:42:18 +1100 Subject: [PATCH 478/815] Try workflow_dispatch for triggering release --- .github/workflows/release.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d4c4f50f..0c1f09df6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,7 @@ name: release on: - release: - types: [created] + workflow_dispatch: jobs: release: From b46cbf806662ee68ef49ae517a583162a2fbd059 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 22:56:42 +1100 Subject: [PATCH 479/815] Add git user.name and user.email, rename environment --- .github/workflows/release.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c1f09df6..231704bca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ on: jobs: release: - environment: release + environment: maven_central runs-on: ubuntu-latest steps: - name: Checkout sources @@ -26,6 +26,11 @@ jobs: gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable to use for passphrase in release + - name: Set up Git user + run: | + git config --global user.name "${{ secrets.GIT_USER_NAME }}" + git config --global user.email "${{ secrets.GIT_USER_EMAIL }}" + - name: Release run: mvn -B release:prepare release:perform env: From 9635bc5f741060fed88fe5c7183c485dc05ede33 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 23:00:51 +1100 Subject: [PATCH 480/815] Use "actions/checkout@v2" to be able to push (maybe) --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 231704bca..5a1cf615a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,6 +32,7 @@ jobs: git config --global user.email "${{ secrets.GIT_USER_EMAIL }}" - name: Release + uses: actions/checkout@v2 run: mvn -B release:prepare release:perform env: MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} From 9ff5ab49a03cf66c2e32525274c5def2d6bcece9 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 23:12:48 +1100 Subject: [PATCH 481/815] Use personal access token for pushing --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a1cf615a..4edf451c0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,8 +32,9 @@ jobs: git config --global user.email "${{ secrets.GIT_USER_EMAIL }}" - name: Release - uses: actions/checkout@v2 - run: mvn -B release:prepare release:perform + run: | + 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 }} From 4e7392d43217c73282795c7623baac776fbd848f Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 23:18:24 +1100 Subject: [PATCH 482/815] Try with push over https instead of ssh in developerConnection --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 64bee1b08..d67127c76 100644 --- a/pom.xml +++ b/pom.xml @@ -278,8 +278,8 @@ </developers> <scm> - <connection>scm:git:git@github.com:commonmark/commonmark-java.git</connection> - <developerConnection>scm:git:git@github.com:commonmark/commonmark-java.git</developerConnection> + <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> + <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> <tag>HEAD</tag> </scm> From 6f5454f1f096777e39f369d18eb22f98478d76e9 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 14 Jan 2021 23:42:30 +1100 Subject: [PATCH 483/815] Fix maven-gpg-plugin erroring out on commonmark-integration-test module --- .../commonmark/integration/IntegrationTests.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 commonmark-integration-test/src/main/java/org/commonmark/integration/IntegrationTests.java diff --git a/commonmark-integration-test/src/main/java/org/commonmark/integration/IntegrationTests.java b/commonmark-integration-test/src/main/java/org/commonmark/integration/IntegrationTests.java new file mode 100644 index 000000000..48e1ee5ba --- /dev/null +++ b/commonmark-integration-test/src/main/java/org/commonmark/integration/IntegrationTests.java @@ -0,0 +1,16 @@ +package org.commonmark.integration; + +// Prevent maven-gpg-plugin from failing with this error: +// The project artifact has not been assembled yet. +// Please do not invoke this goal before the lifecycle phase "package". +// +// Apparently it doesn't like a module that doesn't have any classes in main, +// because that means no jar is generated. +// And the javadoc plugin doesn't like if there's no classes with documentation, +// + +/** + * Module with integration tests. + */ +public class IntegrationTests { +} From c0e12f4ecfaddba051767f1695a755b519a117f5 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 15 Jan 2021 00:34:54 +0000 Subject: [PATCH 484/815] [maven-release-plugin] prepare release commonmark-parent-0.17.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 ea4959c1d..624183eab 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index b49c6d8d1..3b8cf6d2e 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 755c44fb4..5dcc5c9fd 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 2f5163f00..0547977f7 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 977023861..dfa7789cf 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 0f1ad4e6f..dfede42ac 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 26dc79303..a20924f6d 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e95755f01..6497bd7d3 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 2cbc0b6db..a1b0865f9 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index aa161c369..af89eca67 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 1a1bd3c8a..6f21a5585 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index d67127c76..1dd2a58c3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.17.0-SNAPSHOT</version> + <version>0.17.0</version> </dependency> <!-- Common test dependencies --> @@ -281,7 +281,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.17.0</tag> </scm> <distributionManagement> From a0ba3b616e5c9b776125b563967ce50e2bbcc2b2 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 15 Jan 2021 00:35:01 +0000 Subject: [PATCH 485/815] [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 624183eab..5999f772a 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 3b8cf6d2e..37b887d1a 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 5dcc5c9fd..64e495ba9 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 0547977f7..75fd42107 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index dfa7789cf..4f14bdba0 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index dfede42ac..4acca48be 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index a20924f6d..55efb4544 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 6497bd7d3..b25a107ee 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index a1b0865f9..d6a0187eb 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index af89eca67..73165e02b 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 6f21a5585..264282513 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 1dd2a58c3..d6e8d80a6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.17.0</version> + <version>0.17.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -281,7 +281,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.17.0</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From 90a6747c3a569c1cc40749b7769f9b342f0d693b Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 15 Jan 2021 14:47:17 +1100 Subject: [PATCH 486/815] CHANGELOG for 0.17.0, update documentation --- CHANGELOG.md | 56 +++++++++++-------- CONTRIBUTING.md | 29 ++-------- README.md | 18 +++--- commonmark-android-test/app/build.gradle | 2 +- .../app/src/main/AndroidManifest.xml | 2 +- 5 files changed, 49 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0000368ba..ec4151810 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. +## [0.17.0] - 2021-01-15 +### Changed +- **ACTION REQUIRED**: Maven groupId has changed from `com.atlassian.commonmark` to `org.commonmark` + - To continue getting new versions of commonmark-java, change the Maven coordinates in your dependencies: + - Old: `<groupId>com.atlassian.commonmark</groupId>` + - New: `<groupId>org.commonmark</groupId>` + ## [0.16.1] - 2020-12-11 ### Added - Support for including source spans on block and inline nodes (#1): @@ -307,27 +314,28 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[0.16.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.1 -[0.15.2]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.1...commonmark-parent-0.15.2 -[0.15.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.15.0...commonmark-parent-0.15.1 -[0.15.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.14.0...commonmark-parent-0.15.0 -[0.14.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.1...commonmark-parent-0.14.0 -[0.13.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.13.0...commonmark-parent-0.13.1 -[0.13.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.12.1...commonmark-parent-0.13.0 -[0.12.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.11.0...commonmark-parent-0.12.1 -[0.11.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.10.0...commonmark-parent-0.11.0 -[0.10.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.9.0...commonmark-parent-0.10.0 -[0.9.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.8.0...commonmark-parent-0.9.0 -[0.8.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.7.1...commonmark-parent-0.8.0 -[0.7.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.7.0...commonmark-parent-0.7.1 -[0.7.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.6.0...commonmark-parent-0.7.0 -[0.6.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.5.1...commonmark-parent-0.6.0 -[0.5.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.5.0...commonmark-parent-0.5.1 -[0.5.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.4.1...commonmark-parent-0.5.0 -[0.4.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.4.0...commonmark-parent-0.4.1 -[0.4.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.3.2...commonmark-parent-0.4.0 -[0.3.2]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.3.1...commonmark-parent-0.3.2 -[0.3.1]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.3.0...commonmark-parent-0.3.1 -[0.3.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.2.0...commonmark-parent-0.3.0 -[0.2.0]: https://github.com/atlassian/commonmark-java/compare/commonmark-parent-0.1.0...commonmark-parent-0.2.0 -[0.1.0]: https://github.com/atlassian/commonmark-java/commits/commonmark-parent-0.1.0 +[0.17.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.16.1...commonmark-parent-0.17.0 +[0.16.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.1 +[0.15.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.1...commonmark-parent-0.15.2 +[0.15.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.0...commonmark-parent-0.15.1 +[0.15.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.14.0...commonmark-parent-0.15.0 +[0.14.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.13.1...commonmark-parent-0.14.0 +[0.13.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.13.0...commonmark-parent-0.13.1 +[0.13.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.12.1...commonmark-parent-0.13.0 +[0.12.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.11.0...commonmark-parent-0.12.1 +[0.11.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.10.0...commonmark-parent-0.11.0 +[0.10.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.9.0...commonmark-parent-0.10.0 +[0.9.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.8.0...commonmark-parent-0.9.0 +[0.8.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.7.1...commonmark-parent-0.8.0 +[0.7.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.7.0...commonmark-parent-0.7.1 +[0.7.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.6.0...commonmark-parent-0.7.0 +[0.6.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.5.1...commonmark-parent-0.6.0 +[0.5.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.5.0...commonmark-parent-0.5.1 +[0.5.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.4.1...commonmark-parent-0.5.0 +[0.4.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.4.0...commonmark-parent-0.4.1 +[0.4.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.3.2...commonmark-parent-0.4.0 +[0.3.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.3.1...commonmark-parent-0.3.2 +[0.3.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.3.0...commonmark-parent-0.3.1 +[0.3.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.2.0...commonmark-parent-0.3.0 +[0.2.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.1.0...commonmark-parent-0.2.0 +[0.1.0]: https://github.com/commonmark/commonmark-java/commits/commonmark-parent-0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86c141127..6bb2f1640 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,32 +16,15 @@ existing issues with label "help wanted". For bigger changes, make sure you start a discussion first by creating an issue and explaining the intended change. -The [sourcespy dashboard](https://sourcespy.com/github/atlassiancommonmarkjava/) +The [sourcespy dashboard](https://sourcespy.com/github/commonmarkcommonmarkjava/) provides a high level overview of the repository including -[class diagram](https://sourcespy.com/github/atlassiancommonmarkjava/xx-omodel-.html), -[module dependencies](https://sourcespy.com/github/atlassiancommonmarkjava/xx-omodulesc-.html), -[module hierarchy](https://sourcespy.com/github/atlassiancommonmarkjava/xx-omodules-.html), -[external libraries](https://sourcespy.com/github/atlassiancommonmarkjava/xx-ojavalibs-.html), +[class diagram](https://sourcespy.com/github/commonmarkcommonmarkjava/xx-omodel-.html), +[module dependencies](https://sourcespy.com/github/commonmarkcommonmarkjava/xx-omodulesc-.html), +[module hierarchy](https://sourcespy.com/github/commonmarkcommonmarkjava/xx-omodules-.html), +[external libraries](https://sourcespy.com/github/commonmarkcommonmarkjava/xx-ojavalibs-.html), and other components of the system. -CLA ---- - -Atlassian requires contributors to sign a Contributor License Agreement, -known as a CLA. This serves as a record stating that the contributor is -entitled to contribute the code/documentation/translation to the project -and is willing to have it used in distributions and derivative works -(or is willing to transfer ownership). - -Prior to accepting your first contribution we ask that you please follow the -appropriate link below to digitally sign the CLA. The Corporate CLA is for those -who are contributing as a member of an organization and the individual CLA is -for those contributing as an individual. - -https://opensource.atlassian.com/cla - Releasing --------- -Releases are done from an Atlassian internal build server: -https://engservices-bamboo.internal.atlassian.com/browse/CM +Releases are done from the "release" workflow on GitHub Actions. diff --git a/README.md b/README.md index d86c6f7af..d74c107cd 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ commonmark-java Java library for parsing and rendering [Markdown] text according to the [CommonMark] specification (and some extensions). -[![Maven Central status](https://img.shields.io/maven-central/v/com.atlassian.commonmark/commonmark.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.atlassian.commonmark%22) -[![javadoc](https://www.javadoc.io/badge/com.atlassian.commonmark/commonmark.svg?color=blue)](https://www.javadoc.io/doc/com.atlassian.commonmark/commonmark) -[![ci](https://github.com/atlassian/commonmark-java/workflows/ci/badge.svg)](https://github.com/atlassian/commonmark-java/actions?query=workflow%3Aci) -[![codecov](https://codecov.io/gh/atlassian/commonmark-java/branch/master/graph/badge.svg)](https://codecov.io/gh/atlassian/commonmark-java) -[![SourceSpy Dashboard](https://sourcespy.com/shield.svg)](https://sourcespy.com/github/atlassiancommonmarkjava/) +[![Maven Central status](https://img.shields.io/maven-central/v/org.commonmark/commonmark.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.commonmark%22) +[![javadoc](https://www.javadoc.io/badge/org.commonmark/commonmark.svg?color=blue)](https://www.javadoc.io/doc/org.commonmark/commonmark) +[![ci](https://github.com/commonmark/commonmark-java/workflows/ci/badge.svg)](https://github.com/commonmark/commonmark-java/actions?query=workflow%3Aci) +[![codecov](https://codecov.io/gh/commonmark/commonmark-java/branch/master/graph/badge.svg)](https://codecov.io/gh/commonmark/commonmark-java) +[![SourceSpy Dashboard](https://sourcespy.com/shield.svg)](https://sourcespy.com/github/commonmarkcommonmarkjava/) Introduction ------------ @@ -32,9 +32,9 @@ Coordinates for core library (see all on [Maven Central]): ```xml <dependency> - <groupId>com.atlassian.commonmark</groupId> + <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.16.1</version> + <version>0.17.0</version> </dependency> ``` @@ -215,7 +215,7 @@ report an issue. ### API documentation Javadocs are available online on -[javadoc.io](https://www.javadoc.io/doc/com.atlassian.commonmark/commonmark). +[javadoc.io](https://www.javadoc.io/doc/org.commonmark/commonmark). Extensions @@ -390,7 +390,7 @@ BSD (2-clause) licensed, see LICENSE.txt file. [Markdown]: https://daringfireball.net/projects/markdown/ [commonmark.js]: https://github.com/jgm/commonmark.js [CommonMark Dingus]: http://spec.commonmark.org/dingus/ -[Maven Central]: https://search.maven.org/#search|ga|1|g%3A%22com.atlassian.commonmark%22 +[Maven Central]: https://search.maven.org/#search|ga|1|g%3A%22org.commonmark%22 [Semantic Versioning]: http://semver.org/ [autolink-java]: https://github.com/robinst/autolink-java [gfm-tables]: https://help.github.com/articles/organizing-information-with-tables/ diff --git a/commonmark-android-test/app/build.gradle b/commonmark-android-test/app/build.gradle index 18d91c0e5..1b39c87c8 100644 --- a/commonmark-android-test/app/build.gradle +++ b/commonmark-android-test/app/build.gradle @@ -5,7 +5,7 @@ android { buildToolsVersion "30.0.2" defaultConfig { - applicationId "com.atlassian.commonmark.android.test" + applicationId "org.commonmark.android.test" minSdkVersion 19 targetSdkVersion 30 versionCode 1 diff --git a/commonmark-android-test/app/src/main/AndroidManifest.xml b/commonmark-android-test/app/src/main/AndroidManifest.xml index de2cd5725..0343d4fdc 100644 --- a/commonmark-android-test/app/src/main/AndroidManifest.xml +++ b/commonmark-android-test/app/src/main/AndroidManifest.xml @@ -1,3 +1,3 @@ -<manifest package="com.atlassian.commonmark.android.test"> +<manifest package="org.commonmark.android.test"> <application /> </manifest> From bd18061644f1278113249060cc36cb664ae1f1e0 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 18 Jan 2021 15:58:52 +1100 Subject: [PATCH 487/815] Adjust main branch name and links --- README.md | 2 +- commonmark-test-util/src/main/resources/README.md | 2 +- etc/update-spec.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d74c107cd..dd58bace0 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Java library for parsing and rendering [Markdown] text according to the [![Maven Central status](https://img.shields.io/maven-central/v/org.commonmark/commonmark.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.commonmark%22) [![javadoc](https://www.javadoc.io/badge/org.commonmark/commonmark.svg?color=blue)](https://www.javadoc.io/doc/org.commonmark/commonmark) [![ci](https://github.com/commonmark/commonmark-java/workflows/ci/badge.svg)](https://github.com/commonmark/commonmark-java/actions?query=workflow%3Aci) -[![codecov](https://codecov.io/gh/commonmark/commonmark-java/branch/master/graph/badge.svg)](https://codecov.io/gh/commonmark/commonmark-java) +[![codecov](https://codecov.io/gh/commonmark/commonmark-java/branch/main/graph/badge.svg)](https://codecov.io/gh/commonmark/commonmark-java) [![SourceSpy Dashboard](https://sourcespy.com/shield.svg)](https://sourcespy.com/github/commonmarkcommonmarkjava/) Introduction diff --git a/commonmark-test-util/src/main/resources/README.md b/commonmark-test-util/src/main/resources/README.md index a749c59b5..f51e88358 100644 --- a/commonmark-test-util/src/main/resources/README.md +++ b/commonmark-test-util/src/main/resources/README.md @@ -1,6 +1,6 @@ These files are copied from the CommonMark repositories, namely: -https://github.com/commonmark/CommonMark/blob/master/spec.txt +https://github.com/commonmark/commonmark-spec/blob/master/spec.txt https://github.com/commonmark/cmark/blob/master/test/regression.txt https://github.com/commonmark/commonmark.js/blob/master/test/regression.txt diff --git a/etc/update-spec.sh b/etc/update-spec.sh index c31613e5d..d4930438e 100755 --- a/etc/update-spec.sh +++ b/etc/update-spec.sh @@ -6,7 +6,7 @@ if [ "$#" -ne 1 ]; then fi version=$1 -curl -L "https://raw.githubusercontent.com/commonmark/CommonMark/$version/spec.txt" -o commonmark-test-util/src/main/resources/spec.txt +curl -L "https://raw.githubusercontent.com/commonmark/commonmark-spec/$version/spec.txt" -o commonmark-test-util/src/main/resources/spec.txt curl -L "https://raw.githubusercontent.com/github/cmark-gfm/master/test/spec.txt" -o commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt echo "Check cmark and commonmark.js regression.txt:" From 0e97c3d8ca67d48092aa7b6688336831596e7029 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 18 Jan 2021 16:02:23 +1100 Subject: [PATCH 488/815] Update some more links --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dd58bace0..8bace8329 100644 --- a/README.md +++ b/README.md @@ -386,11 +386,11 @@ Copyright (c) 2015-2019 Atlassian and others. BSD (2-clause) licensed, see LICENSE.txt file. -[CommonMark]: http://commonmark.org/ +[CommonMark]: https://commonmark.org/ [Markdown]: https://daringfireball.net/projects/markdown/ -[commonmark.js]: https://github.com/jgm/commonmark.js -[CommonMark Dingus]: http://spec.commonmark.org/dingus/ +[commonmark.js]: https://github.com/commonmark/commonmark.js +[CommonMark Dingus]: https://spec.commonmark.org/dingus/ [Maven Central]: https://search.maven.org/#search|ga|1|g%3A%22org.commonmark%22 -[Semantic Versioning]: http://semver.org/ +[Semantic Versioning]: https://semver.org/ [autolink-java]: https://github.com/robinst/autolink-java [gfm-tables]: https://help.github.com/articles/organizing-information-with-tables/ From 881be5370aaa4c200cabe00a88082075d941d6d6 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 18 Jan 2021 22:22:20 +1100 Subject: [PATCH 489/815] Fix Unicode handling in emphasis parsing (flanking rules) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unicode characters before/after delimiters that use more than one UTF-16 code unit were not handled correctly. This change fixes it by using code points instead of chars. It also removes another unnecessary usage of regexes. Before: Benchmark Mode Cnt Score Error Units SpecBenchmark.parseExamples thrpt 100 573.528 ± 3.119 ops/s After: SpecBenchmark.parseExamples thrpt 100 639.922 ± 2.627 ops/s --- CHANGELOG.md | 8 ++++ .../commonmark/internal/InlineParserImpl.java | 21 +++------- .../commonmark/internal/inline/Scanner.java | 31 +++++++++++++- .../org/commonmark/internal/util/Parsing.java | 42 +++++++++++++++++++ .../internal/inline/ScannerTest.java | 29 +++++++++---- .../commonmark/internal/util/ParsingTest.java | 23 ++++++++++ .../org/commonmark/test/SpecialInputTest.java | 8 +++- 7 files changed, 136 insertions(+), 26 deletions(-) create mode 100644 commonmark/src/test/java/org/commonmark/internal/util/ParsingTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ec4151810..98705140e 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 emphasis surrounded by non-BMP punctuation/whitespace characters + (characters that are longer than one UTF-16 "char"). Note that this is + an edge case with rarely used Unicode characters, which a lot of other + implementations don't handle correctly. + ## [0.17.0] - 2021-01-15 ### Changed - **ACTION REQUIRED**: Maven groupId has changed from `com.atlassian.commonmark` to `org.commonmark` @@ -314,6 +321,7 @@ 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.17.0...HEAD [0.17.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.16.1...commonmark-parent-0.17.0 [0.16.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.1 [0.15.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.1...commonmark-parent-0.15.2 diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index 021fc9255..c90e61fb2 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -12,16 +12,9 @@ import org.commonmark.parser.delimiter.DelimiterProcessor; import java.util.*; -import java.util.regex.Pattern; public class InlineParserImpl implements InlineParser, InlineParserState { - private static final String ASCII_PUNCTUATION = "!\"#\\$%&'\\(\\)\\*\\+,\\-\\./:;<=>\\?@\\[\\\\\\]\\^_`\\{\\|\\}~"; - private static final Pattern PUNCTUATION = Pattern - .compile("^[" + ASCII_PUNCTUATION + "\\p{Pc}\\p{Pd}\\p{Pe}\\p{Pf}\\p{Pi}\\p{Po}\\p{Ps}]"); - - private static final Pattern UNICODE_WHITESPACE_CHAR = Pattern.compile("^[\\p{Zs}\t\r\n\f]"); - private final BitSet specialCharacters; private final Map<Character, DelimiterProcessor> delimiterProcessors; private final InlineParserContext context; @@ -511,7 +504,7 @@ private Node parseText() { * @return information about delimiter run, or {@code null} */ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) { - char charBefore = scanner.peekPrevious(); + int before = scanner.peekPreviousCodePoint(); Position start = scanner.position(); // Quick check to see if we have enough delimiters. @@ -530,15 +523,13 @@ private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char positionBefore = scanner.position(); } - char charAfter = scanner.peek(); - String before = charBefore == Scanner.END ? "\n" : String.valueOf(charBefore); - String after = charAfter == Scanner.END ? "\n" : String.valueOf(charAfter); + int after = scanner.peekCodePoint(); // We could be more lazy here, in most cases we don't need to do every match case. - boolean beforeIsPunctuation = PUNCTUATION.matcher(before).matches(); - boolean beforeIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(before).matches(); - boolean afterIsPunctuation = PUNCTUATION.matcher(after).matches(); - boolean afterIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(after).matches(); + 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 leftFlanking = !afterIsWhitespace && (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation); diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java index 5533cb8ee..9de96a587 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/Scanner.java @@ -57,10 +57,37 @@ public char peek() { } } - public char peekPrevious() { + public int peekCodePoint() { + if (index < lineLength) { + char c = line.getContent().charAt(index); + if (Character.isHighSurrogate(c) && index + 1 < lineLength) { + char low = line.getContent().charAt(index + 1); + if (Character.isLowSurrogate(low)) { + return Character.toCodePoint(c, low); + } + } + return c; + } else { + if (lineIndex < lines.size() - 1) { + return '\n'; + } else { + // Don't return newline for end of last line + return END; + } + } + } + + public int peekPreviousCodePoint() { if (index > 0) { int prev = index - 1; - return line.getContent().charAt(prev); + char c = line.getContent().charAt(prev); + if (Character.isLowSurrogate(c) && prev > 0) { + char high = line.getContent().charAt(prev - 1); + if (Character.isHighSurrogate(high)) { + return Character.toCodePoint(high, c); + } + } + return c; } else { if (lineIndex > 0) { return '\n'; 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 7c44ea6fe..8b02e99b1 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Parsing.java @@ -111,6 +111,48 @@ public static boolean isEscapable(char c) { 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} */ diff --git a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java index 68968d59a..030a765af 100644 --- a/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/inline/ScannerTest.java @@ -33,40 +33,55 @@ public void testMultipleLines() { SourceLine.of("cde", null)), 0, 0); assertTrue(scanner.hasNext()); - assertEquals('\0', scanner.peekPrevious()); + assertEquals('\0', scanner.peekPreviousCodePoint()); assertEquals('a', scanner.peek()); scanner.next(); assertTrue(scanner.hasNext()); - assertEquals('a', scanner.peekPrevious()); + assertEquals('a', scanner.peekPreviousCodePoint()); assertEquals('b', scanner.peek()); scanner.next(); assertTrue(scanner.hasNext()); - assertEquals('b', scanner.peekPrevious()); + assertEquals('b', scanner.peekPreviousCodePoint()); assertEquals('\n', scanner.peek()); scanner.next(); assertTrue(scanner.hasNext()); - assertEquals('\n', scanner.peekPrevious()); + assertEquals('\n', scanner.peekPreviousCodePoint()); assertEquals('c', scanner.peek()); scanner.next(); assertTrue(scanner.hasNext()); - assertEquals('c', scanner.peekPrevious()); + assertEquals('c', scanner.peekPreviousCodePoint()); assertEquals('d', scanner.peek()); scanner.next(); assertTrue(scanner.hasNext()); - assertEquals('d', scanner.peekPrevious()); + assertEquals('d', scanner.peekPreviousCodePoint()); assertEquals('e', scanner.peek()); scanner.next(); assertFalse(scanner.hasNext()); - assertEquals('e', scanner.peekPrevious()); + assertEquals('e', scanner.peekPreviousCodePoint()); assertEquals('\0', scanner.peek()); } + @Test + public void testCodePoints() { + Scanner scanner = new Scanner(Arrays.asList(SourceLine.of("\uD83D\uDE0A", null)), 0, 0); + + assertTrue(scanner.hasNext()); + assertEquals('\0', scanner.peekPreviousCodePoint()); + assertEquals(128522, scanner.peekCodePoint()); + 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()); + } @Test public void testTextBetween() { diff --git a/commonmark/src/test/java/org/commonmark/internal/util/ParsingTest.java b/commonmark/src/test/java/org/commonmark/internal/util/ParsingTest.java new file mode 100644 index 000000000..f51c8647b --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/internal/util/ParsingTest.java @@ -0,0 +1,23 @@ +package org.commonmark.internal.util; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class ParsingTest { + + @Test + public void isPunctuation() { + // From https://spec.commonmark.org/0.29/#ascii-punctuation-character + char[] chars = { + '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', // (U+0021–2F) + ':', ';', '<', '=', '>', '?', '@', // (U+003A–0040) + '[', '\\', ']', '^', '_', '`', // (U+005B–0060) + '{', '|', '}', '~' // (U+007B–007E) + }; + + for (char c : chars) { + assertTrue("Expected to be punctuation: " + c, Parsing.isPunctuationCodePoint(c)); + } + } +} diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 2ed93c92d..002b146a9 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -176,7 +176,11 @@ public void trailingTabs() { } @Test - public void emph() { - assertRendering("*foo bar*\n", "<p><em>foo bar</em></p>\n"); + public void unicodePunctuationEmphasis() { + // The character here is: U+12470 CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER + // Which is in Unicode category "Po" and needs 2 code units in UTF-16. That means to implement + // it correctly, we need to check code points, not Java chars. + // Note that currently the reference implementation doesn't implement this correctly (resulting in no <em>). + assertRendering("foo\uD809\uDC70_(bar)_", "<p>foo\uD809\uDC70<em>(bar)</em></p>\n"); } } From dfebddc2a020725ed1930a8451f5f067873620df Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 3 Feb 2021 20:17:30 +1100 Subject: [PATCH 490/815] Fix tables with spaces before an initial `|` This bug is present in 0.16.1 and 0.17.0 and was introduced with the source span refactorings. Fixes #199. --- CHANGELOG.md | 3 +++ .../gfm/tables/internal/TableBlockParser.java | 3 ++- .../commonmark/ext/gfm/tables/TablesTest.java | 20 ++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98705140e..c75596e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ with the exception that 0.x versions can break between minor versions. (characters that are longer than one UTF-16 "char"). Note that this is an edge case with rarely used Unicode characters, which a lot of other implementations don't handle correctly. +- Fix tables where the row starts with spaces and then the first `|` - + rows that didn't have spaces before were not affected (#199). This bug + is present in 0.16.1 and 0.17.0. ## [0.17.0] - 2021-01-15 ### Changed 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 df577e9f4..a8eedb7a6 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 @@ -121,7 +121,8 @@ private TableCell parseCell(SourceLine cell, int column, InlineParser inlinePars private static List<SourceLine> split(SourceLine line) { CharSequence row = line.getContent(); - int cellStart = row.charAt(0) == '|' ? 1 : 0; + int nonSpace = Parsing.skipSpaceTab(row, 0, row.length()); + int cellStart = row.charAt(nonSpace) == '|' ? nonSpace + 1 : nonSpace; List<SourceLine> cells = new ArrayList<>(); StringBuilder sb = new StringBuilder(); for (int i = cellStart; i < row.length(); i++) { 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 20fa1f70e..2ec36cf16 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 @@ -158,6 +158,24 @@ public void oneHeadOneBody() { "</table>\n"); } + @Test + public void spaceBeforeSeparator() { + assertRendering(" |Abc|Def|\n |---|---|\n |1|2|", "<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n"); + } + @Test public void separatorMustNotHaveLessPartsThanHead() { assertRendering("Abc|Def|Ghi\n---|---\n1|2|3", "<p>Abc|Def|Ghi\n---|---\n1|2|3</p>\n"); @@ -688,7 +706,7 @@ public void sourceSpans() { assertEquals(Arrays.asList(SourceSpan.of(3, 0, 8)), bodyRow2.getSourceSpans()); TableCell bodyRow2Cell1 = (TableCell) bodyRow2.getFirstChild(); TableCell bodyRow2Cell2 = (TableCell) bodyRow2.getLastChild(); - assertEquals(Arrays.asList(SourceSpan.of(3, 0, 2)), bodyRow2Cell1.getSourceSpans()); + 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()); From e272625d41a513cf1a5464c9a4ed57913f0e2ac0 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 3 Feb 2021 20:32:19 +1100 Subject: [PATCH 491/815] CHANGELOG for 0.17.1 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c75596e91..c07bf665c 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.17.1] - 2021-02-03 ### Fixed - Fix emphasis surrounded by non-BMP punctuation/whitespace characters (characters that are longer than one UTF-16 "char"). Note that this is @@ -324,7 +324,7 @@ 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.17.0...HEAD +[0.17.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.0...commonmark-parent-0.17.1 [0.17.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.16.1...commonmark-parent-0.17.0 [0.16.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.1 [0.15.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.1...commonmark-parent-0.15.2 From d87ad13a9927181956f3f24c395bbffd200a2e84 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 3 Feb 2021 21:23:04 +1100 Subject: [PATCH 492/815] Increase staging timeout from 5 to 10 minutes The last release timed out after 5 minutes. Maybe this helps. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index d6e8d80a6..4143ca714 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,7 @@ <serverId>ossrh</serverId> <nexusUrl>https://oss.sonatype.org/</nexusUrl> <autoReleaseAfterClose>true</autoReleaseAfterClose> + <stagingProgressTimeoutMinutes>10</stagingProgressTimeoutMinutes> </configuration> </plugin> <plugin> From 4b26cd2bd0611c53b7c220e0c641610e560b3a1c Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Wed, 3 Feb 2021 23:47:01 +0000 Subject: [PATCH 493/815] [maven-release-plugin] prepare release commonmark-parent-0.17.1 --- 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 5999f772a..e8aaf4a7c 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 37b887d1a..252aaaf9d 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 64e495ba9..10819f7d2 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 75fd42107..f81be6072 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 4f14bdba0..dcdeb36d6 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 4acca48be..2e0da39bb 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 55efb4544..e799e5fca 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index b25a107ee..242cb0abf 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index d6a0187eb..87a21e20d 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 73165e02b..136303090 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 264282513..53ea84137 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 4143ca714..331358518 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.17.1-SNAPSHOT</version> + <version>0.17.1</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.17.1</tag> </scm> <distributionManagement> From cd4ac30009ba51e5717a8747c696bdb1e6b1b411 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Wed, 3 Feb 2021 23:47:07 +0000 Subject: [PATCH 494/815] [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 e8aaf4a7c..88460ec64 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 252aaaf9d..62f2b7421 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 10819f7d2..577482c8b 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index f81be6072..6f92640e9 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index dcdeb36d6..2dac9b179 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 2e0da39bb..ac485f352 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index e799e5fca..1eb458aa6 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 242cb0abf..e8c3bfe04 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 87a21e20d..c19b103cc 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 136303090..84e134614 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 53ea84137..c568ca5c8 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 331358518..850647bdd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.17.1</version> + <version>0.17.2-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.17.1</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From bb5df203d6e0b84e7537d14043d8837011b18ae0 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 4 Feb 2021 11:08:13 +1100 Subject: [PATCH 495/815] Bump version in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bace8329..5fe88bcb2 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.0</version> + <version>0.17.1</version> </dependency> ``` From 68a8f67e16b6847af1390731e32bc9be767a57d7 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 13 May 2021 12:31:14 +1000 Subject: [PATCH 496/815] Pass original label to InlineParserContext, let it normalize it for lookup This is useful for implementing a custom context that wants access to the original label instead of lowercased and whitespace-collapsed. Fixes #204. --- CHANGELOG.md | 8 +++ .../commonmark/internal/DocumentParser.java | 8 +-- .../internal/InlineParserContextImpl.java | 4 +- .../commonmark/internal/InlineParserImpl.java | 3 +- .../internal/LinkReferenceDefinitions.java | 27 +++++++++ .../parser/InlineParserContext.java | 2 + .../java/org/commonmark/parser/Parser.java | 18 ++---- .../test/InlineParserContextTest.java | 59 +++++++++++++++++++ 8 files changed, 105 insertions(+), 24 deletions(-) create mode 100644 commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java create mode 100644 commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c07bf665c..65cc40e61 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 +- Pass original instead of normalized label to `InlineParserContext` for lookup (#204). + This allows custom contexts to change the lookup logic and have access to the original + label content. + In case you have a custom implementation of `InlineParserContext`, you might need to adjust + it to do normalization. + ## [0.17.1] - 2021-02-03 ### Fixed - Fix emphasis surrounded by non-BMP punctuation/whitespace characters diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index 179ad1140..ed8ae7412 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -68,7 +68,7 @@ public class DocumentParser implements ParserState { private final List<DelimiterProcessor> delimiterProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; - private final Map<String, LinkReferenceDefinition> definitions = new LinkedHashMap<>(); + private final LinkReferenceDefinitions definitions = new LinkReferenceDefinitions(); private final List<OpenBlockParser> openBlockParsers = new ArrayList<>(); private final List<BlockParser> allBlockParsers = new ArrayList<>(); @@ -460,11 +460,7 @@ private void addDefinitionsFrom(ParagraphParser paragraphParser) { // Add nodes into document before paragraph. paragraphParser.getBlock().insertBefore(definition); - String label = definition.getLabel(); - // spec: When there are multiple matching link reference definitions, the first is used - if (!definitions.containsKey(label)) { - definitions.put(label, definition); - } + definitions.add(definition); } } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java index bff085ad8..f485614d5 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserContextImpl.java @@ -10,10 +10,10 @@ public class InlineParserContextImpl implements InlineParserContext { private final List<DelimiterProcessor> delimiterProcessors; - private final Map<String, LinkReferenceDefinition> linkReferenceDefinitions; + private final LinkReferenceDefinitions linkReferenceDefinitions; public InlineParserContextImpl(List<DelimiterProcessor> delimiterProcessors, - Map<String, LinkReferenceDefinition> linkReferenceDefinitions) { + LinkReferenceDefinitions linkReferenceDefinitions) { this.delimiterProcessors = delimiterProcessors; this.linkReferenceDefinitions = linkReferenceDefinitions; } diff --git a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java index c90e61fb2..c14b9e885 100644 --- a/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java +++ b/commonmark/src/main/java/org/commonmark/internal/InlineParserImpl.java @@ -323,8 +323,7 @@ private Node parseCloseBracket() { } if (ref != null) { - String label = Escaping.normalizeLabelContent(ref); - LinkReferenceDefinition definition = context.getLinkReferenceDefinition(label); + LinkReferenceDefinition definition = context.getLinkReferenceDefinition(ref); if (definition != null) { dest = definition.getDestination(); title = definition.getTitle(); diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java new file mode 100644 index 000000000..8fbdb982a --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitions.java @@ -0,0 +1,27 @@ +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<String, LinkReferenceDefinition> 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/parser/InlineParserContext.java b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java index 467742e2c..dae96e2c8 100644 --- a/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java +++ b/commonmark/src/main/java/org/commonmark/parser/InlineParserContext.java @@ -17,6 +17,8 @@ public interface InlineParserContext { /** * Look up a {@link LinkReferenceDefinition} for a given label. + * <p> + * Note that the label is not normalized yet; implementations are responsible for normalizing before lookup. * * @param label the link label to look up * @return the definition if one exists, {@code null} otherwise diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index ae1f700b8..63cebb2eb 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -4,23 +4,14 @@ import org.commonmark.internal.DocumentParser; import org.commonmark.internal.InlineParserContextImpl; import org.commonmark.internal.InlineParserImpl; -import org.commonmark.node.Block; -import org.commonmark.node.BlockQuote; -import org.commonmark.node.FencedCodeBlock; -import org.commonmark.node.Heading; -import org.commonmark.node.HtmlBlock; -import org.commonmark.node.IndentedCodeBlock; -import org.commonmark.node.LinkReferenceDefinition; -import org.commonmark.node.ListBlock; -import org.commonmark.node.Node; -import org.commonmark.node.ThematicBreak; +import org.commonmark.internal.LinkReferenceDefinitions; +import org.commonmark.node.*; 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.Collections; import java.util.List; import java.util.Set; @@ -51,8 +42,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. - this.inlineParserFactory.create(new InlineParserContextImpl(delimiterProcessors, - Collections.<String, LinkReferenceDefinition>emptyMap())); + this.inlineParserFactory.create(new InlineParserContextImpl(delimiterProcessors, new LinkReferenceDefinitions())); } /** @@ -179,7 +169,7 @@ public Builder extensions(Iterable<? extends Extension> extensions) { * </pre> * * @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<Class<? extends Block>> enabledBlockTypes) { diff --git a/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java new file mode 100644 index 000000000..b7d083df3 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/InlineParserContextTest.java @@ -0,0 +1,59 @@ +package org.commonmark.test; + +import org.commonmark.internal.InlineParserImpl; +import org.commonmark.node.LinkReferenceDefinition; +import org.commonmark.parser.InlineParser; +import org.commonmark.parser.InlineParserContext; +import org.commonmark.parser.InlineParserFactory; +import org.commonmark.parser.Parser; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.renderer.html.HtmlRenderer; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class InlineParserContextTest { + + @Test + public void labelShouldBeOriginalNotNormalized() { + CapturingInlineParserFactory inlineParserFactory = new CapturingInlineParserFactory(); + + Parser parser = Parser.builder().inlineParserFactory(inlineParserFactory).build(); + String input = "[link with special label][FooBarBaz]\n\n[foobarbaz]: /url"; + + String rendered = HtmlRenderer.builder().build().render(parser.parse(input)); + + // Lookup should pass original label to context + assertEquals(Collections.singletonList("FooBarBaz"), inlineParserFactory.lookups); + + // Context should normalize label for finding reference + assertEquals("<p><a href=\"/url\">link with special label</a></p>\n", rendered); + } + + static class CapturingInlineParserFactory implements InlineParserFactory { + + private List<String> lookups = new ArrayList<>(); + + @Override + public InlineParser create(final InlineParserContext inlineParserContext) { + InlineParserContext wrappedContext = new InlineParserContext() { + @Override + public List<DelimiterProcessor> getCustomDelimiterProcessors() { + return inlineParserContext.getCustomDelimiterProcessors(); + } + + @Override + public LinkReferenceDefinition getLinkReferenceDefinition(String label) { + lookups.add(label); + return inlineParserContext.getLinkReferenceDefinition(label); + } + }; + + return new InlineParserImpl(wrappedContext); + } + } +} From 7143c0d9b5e5eacacc4b6703ac85b4474acc8981 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 14 May 2021 23:23:02 +1000 Subject: [PATCH 497/815] CHANGELOG for 0.18.0 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65cc40e61..b12718a85 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.18.0] - 2021-05-14 ### Changed - Pass original instead of normalized label to `InlineParserContext` for lookup (#204). This allows custom contexts to change the lookup logic and have access to the original @@ -332,6 +332,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.18.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.1...commonmark-parent-0.18.0 [0.17.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.0...commonmark-parent-0.17.1 [0.17.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.16.1...commonmark-parent-0.17.0 [0.16.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.1 From dc4b2239c8fd5136589f7a72f775b4861a46e788 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 14 May 2021 13:25:50 +0000 Subject: [PATCH 498/815] [maven-release-plugin] prepare release commonmark-parent-0.17.2 --- 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 88460ec64..6eb3abad1 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 62f2b7421..cc176b061 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 577482c8b..b5e9d4ca5 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 6f92640e9..5d3e51c00 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 2dac9b179..61099a61b 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index ac485f352..331e3d744 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 1eb458aa6..eede4e904 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index e8c3bfe04..7e76d949a 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index c19b103cc..0e66bf6eb 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 84e134614..eccefe1c6 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index c568ca5c8..159836116 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 850647bdd..2690a93c4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.17.2-SNAPSHOT</version> + <version>0.17.2</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.17.2</tag> </scm> <distributionManagement> From 21ffb89e53ea6fbcdf5cd9e3ce19c362d56e2f0d Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Fri, 14 May 2021 13:25:56 +0000 Subject: [PATCH 499/815] [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 6eb3abad1..6e37671d1 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index cc176b061..632d6b658 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index b5e9d4ca5..4b5972d14 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 5d3e51c00..1efdf1a36 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 61099a61b..7668cfa34 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 331e3d744..20e5ea657 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index eede4e904..9f0cb417d 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 7e76d949a..699b7fc47 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 0e66bf6eb..9426113a6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index eccefe1c6..2f1786617 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 159836116..ea3736c01 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 2690a93c4..6eb07e941 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.17.2</version> + <version>0.17.3-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.17.2</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From 4168cfc09c306f73364a38ae63fb0a08e6e7a75f Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 14 May 2021 23:36:54 +1000 Subject: [PATCH 500/815] Fix version --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12718a85..18a4adbdb 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. -## [0.18.0] - 2021-05-14 +## [0.17.2] - 2021-05-14 ### Changed - Pass original instead of normalized label to `InlineParserContext` for lookup (#204). This allows custom contexts to change the lookup logic and have access to the original @@ -332,7 +332,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. -[0.18.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.1...commonmark-parent-0.18.0 +[0.17.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.1...commonmark-parent-0.17.2 [0.17.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.0...commonmark-parent-0.17.1 [0.17.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.16.1...commonmark-parent-0.17.0 [0.16.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.15.2...commonmark-parent-0.16.1 From 2b4f0f7ae55da991b6ac9ad5874ad0f61c1583fc Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Fri, 14 May 2021 23:37:22 +1000 Subject: [PATCH 501/815] README: Bump version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5fe88bcb2..d94bd0ee6 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.1</version> + <version>0.17.2</version> </dependency> ``` From 6a3af53c1ea0b4208094cec031734a8664e4b53c Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 28 Jun 2021 11:23:05 +1000 Subject: [PATCH 502/815] Update spec to 0.30 --- .../src/main/resources/spec.txt | 3234 +++++++++-------- 1 file changed, 1640 insertions(+), 1594 deletions(-) diff --git a/commonmark-test-util/src/main/resources/spec.txt b/commonmark-test-util/src/main/resources/spec.txt index 3913de442..e6f313757 100644 --- a/commonmark-test-util/src/main/resources/spec.txt +++ b/commonmark-test-util/src/main/resources/spec.txt @@ -1,8 +1,8 @@ --- title: CommonMark Spec author: John MacFarlane -version: 0.29 -date: '2019-04-06' +version: 0.30 +date: '2021-06-19' license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)' ... @@ -270,6 +270,16 @@ of representing the structural distinctions we need to make, and the choice of HTML for the tests makes it possible to run the tests against an implementation without writing an abstract syntax tree renderer. +Note that not every feature of the HTML samples is mandated by +the spec. For example, the spec says what counts as a link +destination, but it doesn't mandate that non-ASCII characters in +the URL be percent-encoded. To use the automatic tests, +implementers will need to provide a renderer that conforms to +the expectations of the spec examples (percent-encoding +non-ASCII characters in URLs). But a conforming implementation +can use a different renderer and may choose not to +percent-encode non-ASCII characters in URLs. + This document is generated from a text file, `spec.txt`, written in Markdown with a small extension for the side-by-side tests. The script `tools/makespec.py` can be used to convert `spec.txt` into @@ -294,37 +304,31 @@ of [characters] rather than bytes. A conforming parser may be limited to a certain encoding. A [line](@) is a sequence of zero or more [characters] -other than newline (`U+000A`) or carriage return (`U+000D`), +other than line feed (`U+000A`) or carriage return (`U+000D`), followed by a [line ending] or by the end of file. -A [line ending](@) is a newline (`U+000A`), a carriage return -(`U+000D`) not followed by a newline, or a carriage return and a -following newline. +A [line ending](@) is a line feed (`U+000A`), a carriage return +(`U+000D`) not followed by a line feed, or a carriage return and a +following line feed. A line containing no characters, or a line containing only spaces (`U+0020`) or tabs (`U+0009`), is called a [blank line](@). The following definitions of character classes will be used in this spec: -A [whitespace character](@) is a space -(`U+0020`), tab (`U+0009`), newline (`U+000A`), line tabulation (`U+000B`), -form feed (`U+000C`), or carriage return (`U+000D`). - -[Whitespace](@) is a sequence of one or more [whitespace -characters]. - A [Unicode whitespace character](@) is any code point in the Unicode `Zs` general category, or a tab (`U+0009`), -carriage return (`U+000D`), newline (`U+000A`), or form feed -(`U+000C`). +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]. -[Unicode whitespace](@) is a sequence of one -or more [Unicode whitespace characters]. +A [tab](@) is `U+0009`. A [space](@) is `U+0020`. -A [non-whitespace character](@) is any character -that is not a [whitespace character]. +An [ASCII control character](@) is a character between `U+0000–1F` (both +including) or `U+007F`. An [ASCII punctuation character](@) is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, @@ -333,14 +337,14 @@ is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`, `[`, `\`, `]`, `^`, `_`, `` ` `` (U+005B–0060), `{`, `|`, `}`, or `~` (U+007B–007E). -A [punctuation character](@) is an [ASCII +A [Unicode punctuation character](@) is an [ASCII punctuation character] or anything in the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`. ## Tabs Tabs in lines are not expanded to [spaces]. However, -in contexts where whitespace helps to define block structure, +in contexts where spaces help to define block structure, tabs behave as if they were replaced by spaces with a tab stop of 4 characters. @@ -478,1575 +482,1916 @@ bar For security reasons, the Unicode character `U+0000` must be replaced with the REPLACEMENT CHARACTER (`U+FFFD`). -# Blocks and inlines - -We can think of a document as a sequence of -[blocks](@)---structural elements like paragraphs, block -quotations, lists, headings, rules, and code blocks. Some blocks (like -block quotes and list items) contain other blocks; others (like -headings and paragraphs) contain [inline](@) content---text, -links, emphasized text, images, code spans, and so on. -## Precedence +## Backslash escapes -Indicators of block structure always take precedence over indicators -of inline structure. So, for example, the following is a list with -two items, not a list with one item containing a code span: +Any ASCII punctuation character may be backslash-escaped: ```````````````````````````````` example -- `one -- two` +\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~ . -<ul> -<li>`one</li> -<li>two`</li> -</ul> +<p>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</p> ```````````````````````````````` -This means that parsing can proceed in two steps: first, the block -structure of the document can be discerned; second, text lines inside -paragraphs, headings, and other block constructs can be parsed for inline -structure. The second step requires information about link reference -definitions that will be available only at the end of the first -step. Note that the first step requires processing lines in sequence, -but the second can be parallelized, since the inline parsing of -one block element does not affect the inline parsing of any other. - -## Container blocks and leaf blocks - -We can divide blocks into two types: -[container blocks](@), -which can contain other blocks, and [leaf blocks](@), -which cannot. - -# Leaf blocks +Backslashes before other characters are treated as literal +backslashes: -This section describes the different kinds of leaf block that make up a -Markdown document. +```````````````````````````````` example +\→\A\a\ \3\φ\« +. +<p>\→\A\a\ \3\φ\«</p> +```````````````````````````````` -## Thematic breaks -A line consisting of 0-3 spaces of indentation, followed by a sequence -of three or more matching `-`, `_`, or `*` characters, each followed -optionally by any number of spaces or tabs, forms a -[thematic break](@). +Escaped characters are treated as regular characters and do +not have their usual Markdown meanings: ```````````````````````````````` example -*** ---- -___ +\*not emphasized* +\<br/> not a tag +\[not a link](/foo) +\`not code` +1\. not a list +\* not a list +\# not a heading +\[foo]: /url "not a reference" +\ö not a character entity . -<hr /> -<hr /> -<hr /> +<p>*not emphasized* +<br/> not a tag +[not a link](/foo) +`not code` +1. not a list +* not a list +# not a heading +[foo]: /url "not a reference" +&ouml; not a character entity</p> ```````````````````````````````` -Wrong characters: +If a backslash is itself escaped, the following character is not: ```````````````````````````````` example -+++ +\\*emphasis* . -<p>+++</p> +<p>\<em>emphasis</em></p> ```````````````````````````````` +A backslash at the end of the line is a [hard line break]: + ```````````````````````````````` example -=== +foo\ +bar . -<p>===</p> +<p>foo<br /> +bar</p> ```````````````````````````````` -Not enough characters: +Backslash escapes do not work in code blocks, code spans, autolinks, or +raw HTML: ```````````````````````````````` example --- -** -__ +`` \[\` `` . -<p>-- -** -__</p> +<p><code>\[\`</code></p> ```````````````````````````````` -One to three spaces indent are allowed: - ```````````````````````````````` example - *** - *** - *** + \[\] . -<hr /> -<hr /> -<hr /> +<pre><code>\[\] +</code></pre> ```````````````````````````````` -Four spaces is too many: - ```````````````````````````````` example - *** +~~~ +\[\] +~~~ . -<pre><code>*** +<pre><code>\[\] </code></pre> ```````````````````````````````` ```````````````````````````````` example -Foo - *** +<http://example.com?find=\*> . -<p>Foo -***</p> +<p><a href="http://example.com?find=%5C*">http://example.com?find=\*</a></p> ```````````````````````````````` -More than three characters may be used: - ```````````````````````````````` example -_____________________________________ +<a href="/bar\/)"> . -<hr /> +<a href="/bar\/)"> ```````````````````````````````` -Spaces are allowed between the characters: +But they work in all other contexts, including URLs and link titles, +link references, and [info strings] in [fenced code blocks]: ```````````````````````````````` example - - - - +[foo](/bar\* "ti\*tle") . -<hr /> +<p><a href="/bar*" title="ti*tle">foo</a></p> ```````````````````````````````` ```````````````````````````````` example - ** * ** * ** * ** +[foo] + +[foo]: /bar\* "ti\*tle" . -<hr /> +<p><a href="/bar*" title="ti*tle">foo</a></p> ```````````````````````````````` ```````````````````````````````` example -- - - - +``` foo\+bar +foo +``` . -<hr /> +<pre><code class="language-foo+bar">foo +</code></pre> ```````````````````````````````` -Spaces are allowed at the end: +## Entity and numeric character references -```````````````````````````````` example -- - - - -. -<hr /> -```````````````````````````````` +Valid HTML entity references and numeric character references +can be used in place of the corresponding Unicode character, +with the following exceptions: +- Entity and character references are not recognized in code + blocks and code spans. -However, no other characters may occur in the line: +- Entity and character references cannot stand in place of + special characters that define structural elements in + CommonMark. For example, although `*` can be used + in place of a literal `*` character, `*` cannot replace + `*` in emphasis delimiters, bullet list markers, or thematic + breaks. -```````````````````````````````` example -_ _ _ _ a +Conforming CommonMark parsers need not store information about +whether a particular character was represented in the source +using a Unicode character or an entity reference. -a------ +[Entity references](@) consist of `&` + any of the valid +HTML5 entity names + `;`. The +document <https://html.spec.whatwg.org/entities.json> +is used as an authoritative source for the valid entity +references and their corresponding code points. ----a--- +```````````````````````````````` example +  & © Æ Ď +¾ ℋ ⅆ +∲ ≧̸ . -<p>_ _ _ _ a</p> -<p>a------</p> -<p>---a---</p> +<p>  & © Æ Ď +¾ ℋ ⅆ +∲ ≧̸</p> ```````````````````````````````` -It is required that all of the [non-whitespace characters] be the same. -So, this is not a thematic break: +[Decimal numeric character +references](@) +consist of `&#` + a string of 1--7 arabic digits + `;`. A +numeric character reference is parsed as the corresponding +Unicode character. Invalid Unicode code points will be replaced by +the REPLACEMENT CHARACTER (`U+FFFD`). For security reasons, +the code point `U+0000` will also be replaced by `U+FFFD`. ```````````````````````````````` example - *-* +# Ӓ Ϡ � . -<p><em>-</em></p> +<p># Ӓ Ϡ �</p> ```````````````````````````````` -Thematic breaks do not need blank lines before or after: +[Hexadecimal numeric character +references](@) consist of `&#` + +either `X` or `x` + a string of 1-6 hexadecimal digits + `;`. +They too are parsed as the corresponding Unicode character (this +time specified with a hexadecimal numeral instead of decimal). ```````````````````````````````` example -- foo -*** -- bar +" ആ ಫ . -<ul> -<li>foo</li> -</ul> -<hr /> -<ul> -<li>bar</li> -</ul> +<p>" ആ ಫ</p> ```````````````````````````````` -Thematic breaks can interrupt a paragraph: +Here are some nonentities: ```````````````````````````````` example -Foo -*** -bar +  &x; &#; &#x; +� +&#abcdef0; +&ThisIsNotDefined; &hi?; . -<p>Foo</p> -<hr /> -<p>bar</p> +<p>&nbsp &x; &#; &#x; +&#87654321; +&#abcdef0; +&ThisIsNotDefined; &hi?;</p> ```````````````````````````````` -If a line of dashes that meets the above conditions for being a -thematic break could also be interpreted as the underline of a [setext -heading], the interpretation as a -[setext heading] takes precedence. Thus, for example, -this is a setext heading, not a paragraph followed by a thematic break: +Although HTML5 does accept some entity references +without a trailing semicolon (such as `©`), these are not +recognized here, because it makes the grammar too ambiguous: ```````````````````````````````` example -Foo ---- -bar +© . -<h2>Foo</h2> -<p>bar</p> +<p>&copy</p> ```````````````````````````````` -When both a thematic break and a list item are possible -interpretations of a line, the thematic break takes precedence: +Strings that are not on the list of HTML5 named entities are not +recognized as entity references either: ```````````````````````````````` example -* Foo -* * * -* Bar +&MadeUpEntity; . -<ul> -<li>Foo</li> -</ul> -<hr /> -<ul> -<li>Bar</li> -</ul> +<p>&MadeUpEntity;</p> ```````````````````````````````` -If you want a thematic break in a list item, use a different bullet: +Entity and numeric character references are recognized in any +context besides code spans or code blocks, including +URLs, [link titles], and [fenced code block][] [info strings]: ```````````````````````````````` example -- Foo -- * * * +<a href="öö.html"> . -<ul> -<li>Foo</li> -<li> -<hr /> -</li> -</ul> +<a href="öö.html"> ```````````````````````````````` -## ATX headings - -An [ATX heading](@) -consists of a string of characters, parsed as inline content, between an -opening sequence of 1--6 unescaped `#` characters and an optional -closing sequence of any number of unescaped `#` characters. -The opening sequence of `#` characters must be followed by a -[space] or by the end of line. The optional closing sequence of `#`s must be -preceded by a [space] and may be followed by spaces only. The opening -`#` character may be indented 0-3 spaces. The raw contents of the -heading are stripped of leading and trailing spaces before being parsed -as inline content. The heading level is equal to the number of `#` -characters in the opening sequence. +```````````````````````````````` example +[foo](/föö "föö") +. +<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p> +```````````````````````````````` -Simple headings: ```````````````````````````````` example -# foo -## foo -### foo -#### foo -##### foo -###### foo +[foo] + +[foo]: /föö "föö" . -<h1>foo</h1> -<h2>foo</h2> -<h3>foo</h3> -<h4>foo</h4> -<h5>foo</h5> -<h6>foo</h6> +<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p> ```````````````````````````````` -More than six `#` characters is not a heading: - ```````````````````````````````` example -####### foo +``` föö +foo +``` . -<p>####### foo</p> +<pre><code class="language-föö">foo +</code></pre> ```````````````````````````````` -At least one space is required between the `#` characters and the -heading's contents, unless the heading is empty. Note that many -implementations currently do not require the space. However, the -space was required by the -[original ATX implementation](http://www.aaronsw.com/2002/atx/atx.py), -and it helps prevent things like the following from being parsed as -headings: +Entity and numeric character references are treated as literal +text in code spans and code blocks: ```````````````````````````````` example -#5 bolt +`föö` +. +<p><code>f&ouml;&ouml;</code></p> +```````````````````````````````` -#hashtag + +```````````````````````````````` example + föfö . -<p>#5 bolt</p> -<p>#hashtag</p> +<pre><code>f&ouml;f&ouml; +</code></pre> ```````````````````````````````` -This is not a heading, because the first `#` is escaped: +Entity and numeric character references cannot be used +in place of symbols indicating structure in CommonMark +documents. ```````````````````````````````` example -\## foo +*foo* +*foo* . -<p>## foo</p> +<p>*foo* +<em>foo</em></p> ```````````````````````````````` +```````````````````````````````` example +* foo -Contents are parsed as inlines: +* foo +. +<p>* foo</p> +<ul> +<li>foo</li> +</ul> +```````````````````````````````` ```````````````````````````````` example -# foo *bar* \*baz\* +foo bar . -<h1>foo <em>bar</em> *baz*</h1> +<p>foo + +bar</p> ```````````````````````````````` +```````````````````````````````` example + foo +. +<p>→foo</p> +```````````````````````````````` -Leading and trailing [whitespace] is ignored in parsing inline content: ```````````````````````````````` example -# foo +[a](url "tit") . -<h1>foo</h1> +<p>[a](url "tit")</p> ```````````````````````````````` -One to three spaces indentation are allowed: + +# Blocks and inlines + +We can think of a document as a sequence of +[blocks](@)---structural elements like paragraphs, block +quotations, lists, headings, rules, and code blocks. Some blocks (like +block quotes and list items) contain other blocks; others (like +headings and paragraphs) contain [inline](@) content---text, +links, emphasized text, images, code spans, and so on. + +## Precedence + +Indicators of block structure always take precedence over indicators +of inline structure. So, for example, the following is a list with +two items, not a list with one item containing a code span: ```````````````````````````````` example - ### foo - ## foo - # foo +- `one +- two` . -<h3>foo</h3> -<h2>foo</h2> -<h1>foo</h1> +<ul> +<li>`one</li> +<li>two`</li> +</ul> ```````````````````````````````` -Four spaces are too much: +This means that parsing can proceed in two steps: first, the block +structure of the document can be discerned; second, text lines inside +paragraphs, headings, and other block constructs can be parsed for inline +structure. The second step requires information about link reference +definitions that will be available only at the end of the first +step. Note that the first step requires processing lines in sequence, +but the second can be parallelized, since the inline parsing of +one block element does not affect the inline parsing of any other. + +## Container blocks and leaf blocks + +We can divide blocks into two types: +[container blocks](#container-blocks), +which can contain other blocks, and [leaf blocks](#leaf-blocks), +which cannot. + +# Leaf blocks + +This section describes the different kinds of leaf block that make up a +Markdown document. + +## Thematic breaks + +A line consisting of optionally up to three spaces of indentation, followed by a +sequence of three or more matching `-`, `_`, or `*` characters, each followed +optionally by any number of spaces or tabs, forms a +[thematic break](@). ```````````````````````````````` example - # foo +*** +--- +___ . -<pre><code># foo -</code></pre> +<hr /> +<hr /> +<hr /> ```````````````````````````````` +Wrong characters: + ```````````````````````````````` example -foo - # bar ++++ . -<p>foo -# bar</p> +<p>+++</p> ```````````````````````````````` -A closing sequence of `#` characters is optional: - ```````````````````````````````` example -## foo ## - ### bar ### +=== . -<h2>foo</h2> -<h3>bar</h3> +<p>===</p> ```````````````````````````````` -It need not be the same length as the opening sequence: +Not enough characters: ```````````````````````````````` example -# foo ################################## -##### foo ## +-- +** +__ . -<h1>foo</h1> -<h5>foo</h5> +<p>-- +** +__</p> ```````````````````````````````` -Spaces are allowed after the closing sequence: +Up to three spaces of indentation are allowed: ```````````````````````````````` example -### foo ### + *** + *** + *** . -<h3>foo</h3> +<hr /> +<hr /> +<hr /> ```````````````````````````````` -A sequence of `#` characters with anything but [spaces] following it -is not a closing sequence, but counts as part of the contents of the -heading: +Four spaces of indentation is too many: ```````````````````````````````` example -### foo ### b + *** . -<h3>foo ### b</h3> +<pre><code>*** +</code></pre> ```````````````````````````````` -The closing sequence must be preceded by a space: - ```````````````````````````````` example -# foo# +Foo + *** . -<h1>foo#</h1> +<p>Foo +***</p> ```````````````````````````````` -Backslash-escaped `#` characters do not count as part -of the closing sequence: +More than three characters may be used: ```````````````````````````````` example -### foo \### -## foo #\## -# foo \# +_____________________________________ . -<h3>foo ###</h3> -<h2>foo ###</h2> -<h1>foo #</h1> +<hr /> ```````````````````````````````` -ATX headings need not be separated from surrounding content by blank -lines, and they can interrupt paragraphs: +Spaces and tabs are allowed between the characters: ```````````````````````````````` example -**** -## foo -**** + - - - . <hr /> -<h2>foo</h2> -<hr /> ```````````````````````````````` ```````````````````````````````` example -Foo bar -# baz -Bar foo + ** * ** * ** * ** . -<p>Foo bar</p> -<h1>baz</h1> -<p>Bar foo</p> +<hr /> ```````````````````````````````` -ATX headings can be empty: - ```````````````````````````````` example -## -# -### ### +- - - - . -<h2></h2> -<h1></h1> -<h3></h3> +<hr /> ```````````````````````````````` -## Setext headings - -A [setext heading](@) consists of one or more -lines of text, each containing at least one [non-whitespace -character], with no more than 3 spaces indentation, followed by -a [setext heading underline]. The lines of text must be such -that, were they not followed by the setext heading underline, -they would be interpreted as a paragraph: they cannot be -interpretable as a [code fence], [ATX heading][ATX headings], -[block quote][block quotes], [thematic break][thematic breaks], -[list item][list items], or [HTML block][HTML blocks]. - -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 and tabs are allowed at the end: -The heading is a level 1 heading if `=` characters are used in -the [setext heading underline], and a level 2 heading if `-` -characters are used. The contents of the heading are the result -of parsing the preceding lines of text as CommonMark inline -content. +```````````````````````````````` example +- - - - +. +<hr /> +```````````````````````````````` -In general, a setext heading need not be preceded or followed by a -blank line. However, it cannot interrupt a paragraph, so when a -setext heading comes after a paragraph, a blank line is needed between -them. -Simple examples: +However, no other characters may occur in the line: ```````````````````````````````` example -Foo *bar* -========= +_ _ _ _ a -Foo *bar* ---------- +a------ + +---a--- . -<h1>Foo <em>bar</em></h1> -<h2>Foo <em>bar</em></h2> +<p>_ _ _ _ a</p> +<p>a------</p> +<p>---a---</p> ```````````````````````````````` -The content of the header may span more than one line: +It is required that all of the characters other than spaces or tabs be the same. +So, this is not a thematic break: ```````````````````````````````` example -Foo *bar -baz* -==== + *-* . -<h1>Foo <em>bar -baz</em></h1> +<p><em>-</em></p> ```````````````````````````````` -The contents are the result of parsing the headings's raw -content as inlines. The heading's raw content is formed by -concatenating the lines and removing initial and final -[whitespace]. + +Thematic breaks do not need blank lines before or after: ```````````````````````````````` example - Foo *bar -baz*→ -==== +- foo +*** +- bar . -<h1>Foo <em>bar -baz</em></h1> +<ul> +<li>foo</li> +</ul> +<hr /> +<ul> +<li>bar</li> +</ul> ```````````````````````````````` -The underlining can be any length: +Thematic breaks can interrupt a paragraph: ```````````````````````````````` example Foo -------------------------- - -Foo -= +*** +bar . -<h2>Foo</h2> -<h1>Foo</h1> +<p>Foo</p> +<hr /> +<p>bar</p> ```````````````````````````````` -The heading content can be indented up to three spaces, and need -not line up with the underlining: +If a line of dashes that meets the above conditions for being a +thematic break could also be interpreted as the underline of a [setext +heading], the interpretation as a +[setext heading] takes precedence. Thus, for example, +this is a setext heading, not a paragraph followed by a thematic break: ```````````````````````````````` example - Foo +Foo --- - - Foo ------ - - Foo - === +bar . <h2>Foo</h2> -<h2>Foo</h2> -<h1>Foo</h1> +<p>bar</p> ```````````````````````````````` -Four spaces indent is too much: +When both a thematic break and a list item are possible +interpretations of a line, the thematic break takes precedence: ```````````````````````````````` example - Foo - --- - - Foo ---- +* Foo +* * * +* Bar . -<pre><code>Foo ---- +<ul> +<li>Foo</li> +</ul> +<hr /> +<ul> +<li>Bar</li> +</ul> +```````````````````````````````` -Foo -</code></pre> + +If you want a thematic break in a list item, use a different bullet: + +```````````````````````````````` example +- Foo +- * * * +. +<ul> +<li>Foo</li> +<li> <hr /> +</li> +</ul> ```````````````````````````````` -The setext heading underline can be indented up to three spaces, and -may have trailing spaces: +## ATX headings + +An [ATX heading](@) +consists of a string of characters, parsed as inline content, between an +opening sequence of 1--6 unescaped `#` characters and an optional +closing sequence of any number of unescaped `#` characters. +The opening sequence of `#` characters must be followed by spaces or tabs, or +by the end of line. The optional closing sequence of `#`s must be preceded by +spaces or tabs and may be followed by spaces or tabs only. The opening +`#` character may be preceded by up to three spaces of indentation. The raw +contents of the heading are stripped of leading and trailing space or tabs +before being parsed as inline content. The heading level is equal to the number +of `#` characters in the opening sequence. + +Simple headings: ```````````````````````````````` example -Foo - ---- +# foo +## foo +### foo +#### foo +##### foo +###### foo . -<h2>Foo</h2> +<h1>foo</h1> +<h2>foo</h2> +<h3>foo</h3> +<h4>foo</h4> +<h5>foo</h5> +<h6>foo</h6> ```````````````````````````````` -Four spaces is too much: +More than six `#` characters is not a heading: ```````````````````````````````` example -Foo - --- +####### foo . -<p>Foo ----</p> +<p>####### foo</p> ```````````````````````````````` -The setext heading underline cannot contain internal spaces: +At least one space or tab is required between the `#` characters and the +heading's contents, unless the heading is empty. Note that many +implementations currently do not require the space. However, the +space was required by the +[original ATX implementation](http://www.aaronsw.com/2002/atx/atx.py), +and it helps prevent things like the following from being parsed as +headings: ```````````````````````````````` example -Foo -= = +#5 bolt -Foo ---- - +#hashtag . -<p>Foo -= =</p> -<p>Foo</p> -<hr /> +<p>#5 bolt</p> +<p>#hashtag</p> ```````````````````````````````` -Trailing spaces in the content line do not cause a line break: +This is not a heading, because the first `#` is escaped: ```````````````````````````````` example -Foo ------ +\## foo . -<h2>Foo</h2> +<p>## foo</p> ```````````````````````````````` -Nor does a backslash at the end: +Contents are parsed as inlines: ```````````````````````````````` example -Foo\ ----- +# foo *bar* \*baz\* . -<h2>Foo\</h2> +<h1>foo <em>bar</em> *baz*</h1> ```````````````````````````````` -Since indicators of block structure take precedence over -indicators of inline structure, the following are setext headings: +Leading and trailing spaces or tabs are ignored in parsing inline content: ```````````````````````````````` example -`Foo ----- -` - -<a title="a lot ---- -of dashes"/> +# foo . -<h2>`Foo</h2> -<p>`</p> -<h2><a title="a lot</h2> -<p>of dashes"/></p> +<h1>foo</h1> ```````````````````````````````` -The setext heading underline cannot be a [lazy continuation -line] in a list item or block quote: +Up to three spaces of indentation are allowed: ```````````````````````````````` example -> Foo ---- + ### foo + ## foo + # foo . -<blockquote> -<p>Foo</p> -</blockquote> -<hr /> +<h3>foo</h3> +<h2>foo</h2> +<h1>foo</h1> ```````````````````````````````` +Four spaces of indentation is too many: + ```````````````````````````````` example -> foo -bar -=== + # foo . -<blockquote> -<p>foo -bar -===</p> -</blockquote> +<pre><code># foo +</code></pre> ```````````````````````````````` ```````````````````````````````` example -- Foo ---- +foo + # bar . -<ul> -<li>Foo</li> -</ul> -<hr /> +<p>foo +# bar</p> ```````````````````````````````` -A blank line is needed between a paragraph and a following -setext heading, since otherwise the paragraph becomes part -of the heading's content: +A closing sequence of `#` characters is optional: ```````````````````````````````` example -Foo -Bar ---- +## foo ## + ### bar ### . -<h2>Foo -Bar</h2> +<h2>foo</h2> +<h3>bar</h3> ```````````````````````````````` -But in general a blank line is not required before or after -setext headings: +It need not be the same length as the opening sequence: ```````````````````````````````` example ---- -Foo ---- -Bar ---- -Baz +# foo ################################## +##### foo ## . -<hr /> -<h2>Foo</h2> -<h2>Bar</h2> -<p>Baz</p> +<h1>foo</h1> +<h5>foo</h5> ```````````````````````````````` -Setext headings cannot be empty: +Spaces or tabs are allowed after the closing sequence: ```````````````````````````````` example +### foo ### +. +<h3>foo</h3> +```````````````````````````````` -==== + +A sequence of `#` characters with anything but spaces or tabs following it +is not a closing sequence, but counts as part of the contents of the +heading: + +```````````````````````````````` example +### foo ### b . -<p>====</p> +<h3>foo ### b</h3> ```````````````````````````````` -Setext heading text lines must not be interpretable as block -constructs other than paragraphs. So, the line of dashes -in these examples gets interpreted as a thematic break: +The closing sequence must be preceded by a space or tab: ```````````````````````````````` example ---- ---- +# foo# . -<hr /> -<hr /> +<h1>foo#</h1> ```````````````````````````````` +Backslash-escaped `#` characters do not count as part +of the closing sequence: + ```````````````````````````````` example -- foo ------ +### foo \### +## foo #\## +# foo \# . -<ul> -<li>foo</li> -</ul> -<hr /> +<h3>foo ###</h3> +<h2>foo ###</h2> +<h1>foo #</h1> ```````````````````````````````` +ATX headings need not be separated from surrounding content by blank +lines, and they can interrupt paragraphs: + ```````````````````````````````` example - foo ---- +**** +## foo +**** . -<pre><code>foo -</code></pre> +<hr /> +<h2>foo</h2> <hr /> ```````````````````````````````` ```````````````````````````````` example -> foo ------ +Foo bar +# baz +Bar foo . -<blockquote> -<p>foo</p> -</blockquote> -<hr /> +<p>Foo bar</p> +<h1>baz</h1> +<p>Bar foo</p> ```````````````````````````````` -If you want a heading with `> foo` as its literal text, you can -use backslash escapes: +ATX headings can be empty: ```````````````````````````````` example -\> foo ------- +## +# +### ### . -<h2>> foo</h2> +<h2></h2> +<h1></h1> +<h3></h3> ```````````````````````````````` -**Compatibility note:** Most existing Markdown implementations -do not allow the text of setext headings to span multiple lines. -But there is no consensus about how to interpret +## Setext headings -``` markdown -Foo -bar ---- -baz -``` +A [setext heading](@) consists of one or more +lines of text, not interrupted by a blank line, of which the first line does not +have more than 3 spaces of indentation, followed by +a [setext heading underline]. The lines of text must be such +that, were they not followed by the setext heading underline, +they would be interpreted as a paragraph: they cannot be +interpretable as a [code fence], [ATX heading][ATX headings], +[block quote][block quotes], [thematic break][thematic breaks], +[list item][list items], or [HTML block][HTML blocks]. -One can find four different interpretations: +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]. -1. paragraph "Foo", heading "bar", paragraph "baz" -2. paragraph "Foo bar", thematic break, paragraph "baz" -3. paragraph "Foo bar --- baz" -4. heading "Foo bar", paragraph "baz" +The heading is a level 1 heading if `=` characters are used in +the [setext heading underline], and a level 2 heading if `-` +characters are used. The contents of the heading are the result +of parsing the preceding lines of text as CommonMark inline +content. -We find interpretation 4 most natural, and interpretation 4 -increases the expressive power of CommonMark, by allowing -multiline headings. Authors who want interpretation 1 can -put a blank line after the first paragraph: +In general, a setext heading need not be preceded or followed by a +blank line. However, it cannot interrupt a paragraph, so when a +setext heading comes after a paragraph, a blank line is needed between +them. + +Simple examples: ```````````````````````````````` example -Foo +Foo *bar* +========= -bar ---- -baz +Foo *bar* +--------- . -<p>Foo</p> -<h2>bar</h2> -<p>baz</p> +<h1>Foo <em>bar</em></h1> +<h2>Foo <em>bar</em></h2> ```````````````````````````````` -Authors who want interpretation 2 can put blank lines around -the thematic break, +The content of the header may span more than one line: ```````````````````````````````` example -Foo -bar +Foo *bar +baz* +==== +. +<h1>Foo <em>bar +baz</em></h1> +```````````````````````````````` ---- +The contents are the result of parsing the headings's raw +content as inlines. The heading's raw content is formed by +concatenating the lines and removing initial and final +spaces or tabs. -baz +```````````````````````````````` example + Foo *bar +baz*→ +==== . -<p>Foo -bar</p> -<hr /> -<p>baz</p> +<h1>Foo <em>bar +baz</em></h1> ```````````````````````````````` -or use a thematic break that cannot count as a [setext heading -underline], such as +The underlining can be any length: ```````````````````````````````` example Foo -bar -* * * -baz +------------------------- + +Foo += . -<p>Foo -bar</p> -<hr /> -<p>baz</p> +<h2>Foo</h2> +<h1>Foo</h1> ```````````````````````````````` -Authors who want interpretation 3 can use backslash escapes: +The heading content can be preceded by up to three spaces of indentation, and +need not line up with the underlining: ```````````````````````````````` example -Foo -bar -\--- -baz -. -<p>Foo -bar + Foo --- -baz</p> -```````````````````````````````` + Foo +----- -## Indented code blocks + Foo + === +. +<h2>Foo</h2> +<h2>Foo</h2> +<h1>Foo</h1> +```````````````````````````````` -An [indented code block](@) is composed of one or more -[indented chunks] separated by blank lines. -An [indented chunk](@) is a sequence of non-blank lines, -each indented four or more spaces. The contents of the code block are -the literal contents of the lines, including trailing -[line endings], minus four spaces of indentation. -An indented code block has no [info string]. -An indented code block cannot interrupt a paragraph, so there must be -a blank line between a paragraph and a following indented code block. -(A blank line is not needed, however, between a code block and a following -paragraph.) +Four spaces of indentation is too many: ```````````````````````````````` example - a simple - indented code block + Foo + --- + + Foo +--- . -<pre><code>a simple - indented code block +<pre><code>Foo +--- + +Foo </code></pre> +<hr /> ```````````````````````````````` -If there is any ambiguity between an interpretation of indentation -as a code block and as indicating that material belongs to a [list -item][list items], the list item interpretation takes precedence: +The setext heading underline can be preceded by up to three spaces of +indentation, and may have trailing spaces or tabs: ```````````````````````````````` example - - foo - - bar +Foo + ---- . -<ul> -<li> -<p>foo</p> -<p>bar</p> -</li> -</ul> +<h2>Foo</h2> ```````````````````````````````` -```````````````````````````````` example -1. foo +Four spaces of indentation is too many: - - bar +```````````````````````````````` example +Foo + --- . -<ol> -<li> -<p>foo</p> -<ul> -<li>bar</li> -</ul> -</li> -</ol> +<p>Foo +---</p> ```````````````````````````````` - -The contents of a code block are literal text, and do not get parsed -as Markdown: +The setext heading underline cannot contain internal spaces or tabs: ```````````````````````````````` example - <a/> - *hi* +Foo += = - - one +Foo +--- - . -<pre><code><a/> -*hi* - -- one -</code></pre> +<p>Foo += =</p> +<p>Foo</p> +<hr /> ```````````````````````````````` -Here we have three chunks separated by blank lines: +Trailing spaces or tabs in the content line do not cause a hard line break: ```````````````````````````````` example - chunk1 - - chunk2 - - - - chunk3 +Foo +----- . -<pre><code>chunk1 - -chunk2 - - - -chunk3 -</code></pre> +<h2>Foo</h2> ```````````````````````````````` -Any initial spaces beyond four will be included in the content, even -in interior blank lines: +Nor does a backslash at the end: ```````````````````````````````` example - chunk1 - - chunk2 +Foo\ +---- . -<pre><code>chunk1 - - chunk2 -</code></pre> +<h2>Foo\</h2> ```````````````````````````````` -An indented code block cannot interrupt a paragraph. (This -allows hanging indents and the like.) +Since indicators of block structure take precedence over +indicators of inline structure, the following are setext headings: ```````````````````````````````` example -Foo - bar +`Foo +---- +` +<a title="a lot +--- +of dashes"/> . -<p>Foo -bar</p> +<h2>`Foo</h2> +<p>`</p> +<h2><a title="a lot</h2> +<p>of dashes"/></p> ```````````````````````````````` -However, any non-blank line with fewer than four leading spaces ends -the code block immediately. So a paragraph may occur immediately -after indented code: +The setext heading underline cannot be a [lazy continuation +line] in a list item or block quote: ```````````````````````````````` example - foo -bar +> Foo +--- . -<pre><code>foo -</code></pre> -<p>bar</p> +<blockquote> +<p>Foo</p> +</blockquote> +<hr /> ```````````````````````````````` -And indented code can occur immediately before and after other kinds of -blocks: - ```````````````````````````````` example -# Heading - foo -Heading ------- - foo ----- +> foo +bar +=== . -<h1>Heading</h1> -<pre><code>foo -</code></pre> -<h2>Heading</h2> -<pre><code>foo -</code></pre> -<hr /> +<blockquote> +<p>foo +bar +===</p> +</blockquote> ```````````````````````````````` -The first line can be indented more than four spaces: - ```````````````````````````````` example - foo - bar +- Foo +--- . -<pre><code> foo -bar -</code></pre> +<ul> +<li>Foo</li> +</ul> +<hr /> ```````````````````````````````` -Blank lines preceding or following an indented code block -are not included in it: +A blank line is needed between a paragraph and a following +setext heading, since otherwise the paragraph becomes part +of the heading's content: ```````````````````````````````` example - - - foo - - +Foo +Bar +--- . -<pre><code>foo -</code></pre> +<h2>Foo +Bar</h2> ```````````````````````````````` -Trailing spaces are included in the code block's content: +But in general a blank line is not required before or after +setext headings: ```````````````````````````````` example - foo +--- +Foo +--- +Bar +--- +Baz . -<pre><code>foo -</code></pre> +<hr /> +<h2>Foo</h2> +<h2>Bar</h2> +<p>Baz</p> ```````````````````````````````` +Setext headings cannot be empty: -## Fenced code blocks - -A [code fence](@) is a sequence -of at least three consecutive backtick characters (`` ` ``) or -tildes (`~`). (Tildes and backticks cannot be mixed.) -A [fenced code block](@) -begins with a code fence, indented no more than three spaces. - -The line with the opening code fence may optionally contain some text -following the code fence; this is trimmed of leading and trailing -whitespace and called the [info string](@). If the [info string] comes -after a backtick fence, it may not contain any backtick -characters. (The reason for this restriction is that otherwise -some inline code would be incorrectly interpreted as the -beginning of a fenced code block.) +```````````````````````````````` example -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. If the leading code fence is -indented N spaces, then up to N spaces of indentation are removed from -each line of the content (if present). (If a content line is not -indented, it is preserved unchanged. If it is indented less than N -spaces, all of the indentation is removed.) +==== +. +<p>====</p> +```````````````````````````````` -The closing code fence may be indented up to three spaces, and may be -followed only by spaces, which are ignored. If the end of the -containing block (or document) is reached and no closing code fence -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 -behavior described here.) -A fenced code block may interrupt a paragraph, and does not require -a blank line either before or after. +Setext heading text lines must not be interpretable as block +constructs other than paragraphs. So, the line of dashes +in these examples gets interpreted as a thematic break: -The content of a code fence is treated as literal text, not parsed -as inlines. The first word of the [info string] is typically used to -specify the language of the code sample, and rendered in the `class` -attribute of the `code` tag. However, this spec does not mandate any -particular treatment of the [info string]. +```````````````````````````````` example +--- +--- +. +<hr /> +<hr /> +```````````````````````````````` -Here is a simple example with backticks: ```````````````````````````````` example -``` -< - > -``` +- foo +----- . -<pre><code>< - > -</code></pre> +<ul> +<li>foo</li> +</ul> +<hr /> ```````````````````````````````` -With tildes: - ```````````````````````````````` example -~~~ -< - > -~~~ + foo +--- . -<pre><code>< - > +<pre><code>foo </code></pre> +<hr /> ```````````````````````````````` -Fewer than three backticks is not enough: ```````````````````````````````` example -`` -foo -`` +> foo +----- . -<p><code>foo</code></p> +<blockquote> +<p>foo</p> +</blockquote> +<hr /> ```````````````````````````````` -The closing code fence must use the same character as the opening -fence: + +If you want a heading with `> foo` as its literal text, you can +use backslash escapes: ```````````````````````````````` example -``` -aaa -~~~ -``` +\> foo +------ . -<pre><code>aaa -~~~ -</code></pre> +<h2>> foo</h2> ```````````````````````````````` -```````````````````````````````` example -~~~ -aaa -``` -~~~ -. -<pre><code>aaa +**Compatibility note:** Most existing Markdown implementations +do not allow the text of setext headings to span multiple lines. +But there is no consensus about how to interpret + +``` markdown +Foo +bar +--- +baz ``` -</code></pre> -```````````````````````````````` +One can find four different interpretations: -The closing code fence must be at least as long as the opening fence: +1. paragraph "Foo", heading "bar", paragraph "baz" +2. paragraph "Foo bar", thematic break, paragraph "baz" +3. paragraph "Foo bar --- baz" +4. heading "Foo bar", paragraph "baz" + +We find interpretation 4 most natural, and interpretation 4 +increases the expressive power of CommonMark, by allowing +multiline headings. Authors who want interpretation 1 can +put a blank line after the first paragraph: ```````````````````````````````` example -```` -aaa -``` -`````` +Foo + +bar +--- +baz . -<pre><code>aaa -``` -</code></pre> +<p>Foo</p> +<h2>bar</h2> +<p>baz</p> ```````````````````````````````` +Authors who want interpretation 2 can put blank lines around +the thematic break, + ```````````````````````````````` example -~~~~ -aaa -~~~ -~~~~ +Foo +bar + +--- + +baz . -<pre><code>aaa -~~~ -</code></pre> +<p>Foo +bar</p> +<hr /> +<p>baz</p> ```````````````````````````````` -Unclosed code blocks are closed by the end of the document -(or the enclosing [block quote][block quotes] or [list item][list items]): +or use a thematic break that cannot count as a [setext heading +underline], such as ```````````````````````````````` example -``` +Foo +bar +* * * +baz . -<pre><code></code></pre> +<p>Foo +bar</p> +<hr /> +<p>baz</p> ```````````````````````````````` -```````````````````````````````` example -````` +Authors who want interpretation 3 can use backslash escapes: -``` -aaa +```````````````````````````````` example +Foo +bar +\--- +baz . -<pre><code> -``` -aaa -</code></pre> +<p>Foo +bar +--- +baz</p> ```````````````````````````````` -```````````````````````````````` example -> ``` -> aaa +## Indented code blocks -bbb +An [indented code block](@) is composed of one or more +[indented chunks] separated by blank lines. +An [indented chunk](@) is a sequence of non-blank lines, +each preceded by four or more spaces of indentation. The contents of the code +block are the literal contents of the lines, including trailing +[line endings], minus four spaces of indentation. +An indented code block has no [info string]. + +An indented code block cannot interrupt a paragraph, so there must be +a blank line between a paragraph and a following indented code block. +(A blank line is not needed, however, between a code block and a following +paragraph.) + +```````````````````````````````` example + a simple + indented code block . -<blockquote> -<pre><code>aaa +<pre><code>a simple + indented code block </code></pre> -</blockquote> -<p>bbb</p> ```````````````````````````````` -A code block can have all empty lines as its content: +If there is any ambiguity between an interpretation of indentation +as a code block and as indicating that material belongs to a [list +item][list items], the list item interpretation takes precedence: ```````````````````````````````` example -``` + - foo - -``` + bar . -<pre><code> - -</code></pre> +<ul> +<li> +<p>foo</p> +<p>bar</p> +</li> +</ul> ```````````````````````````````` -A code block can be empty: - ```````````````````````````````` example -``` -``` +1. foo + + - bar . -<pre><code></code></pre> +<ol> +<li> +<p>foo</p> +<ul> +<li>bar</li> +</ul> +</li> +</ol> ```````````````````````````````` -Fences can be indented. If the opening fence is indented, -content lines will have equivalent opening indentation removed, -if present: + +The contents of a code block are literal text, and do not get parsed +as Markdown: ```````````````````````````````` example - ``` - aaa -aaa -``` + <a/> + *hi* + + - one . -<pre><code>aaa -aaa +<pre><code><a/> +*hi* + +- one </code></pre> ```````````````````````````````` +Here we have three chunks separated by blank lines: + ```````````````````````````````` example - ``` -aaa - aaa -aaa - ``` + chunk1 + + chunk2 + + + + chunk3 . -<pre><code>aaa -aaa -aaa +<pre><code>chunk1 + +chunk2 + + + +chunk3 </code></pre> ```````````````````````````````` +Any initial spaces or tabs beyond four spaces of indentation will be included in +the content, even in interior blank lines: + ```````````````````````````````` example - ``` - aaa - aaa - aaa - ``` + chunk1 + + chunk2 . -<pre><code>aaa - aaa -aaa +<pre><code>chunk1 + + chunk2 </code></pre> ```````````````````````````````` -Four spaces indentation produces an indented code block: +An indented code block cannot interrupt a paragraph. (This +allows hanging indents and the like.) ```````````````````````````````` example - ``` - aaa - ``` +Foo + bar + . -<pre><code>``` -aaa -``` -</code></pre> +<p>Foo +bar</p> ```````````````````````````````` -Closing fences may be indented by 0-3 spaces, and their indentation -need not match that of the opening fence: +However, any non-blank line with fewer than four spaces of indentation ends +the code block immediately. So a paragraph may occur immediately +after indented code: ```````````````````````````````` example -``` -aaa - ``` + foo +bar . -<pre><code>aaa +<pre><code>foo </code></pre> +<p>bar</p> ```````````````````````````````` +And indented code can occur immediately before and after other kinds of +blocks: + ```````````````````````````````` example - ``` -aaa - ``` +# Heading + foo +Heading +------ + foo +---- . -<pre><code>aaa +<h1>Heading</h1> +<pre><code>foo +</code></pre> +<h2>Heading</h2> +<pre><code>foo </code></pre> +<hr /> ```````````````````````````````` -This is not a closing fence, because it is indented 4 spaces: +The first line can be preceded by more than four spaces of indentation: ```````````````````````````````` example -``` -aaa - ``` + foo + bar . -<pre><code>aaa - ``` +<pre><code> foo +bar </code></pre> ```````````````````````````````` - -Code fences (opening and closing) cannot contain internal spaces: +Blank lines preceding or following an indented code block +are not included in it: ```````````````````````````````` example -``` ``` -aaa + + + foo + + . -<p><code> </code> -aaa</p> +<pre><code>foo +</code></pre> ```````````````````````````````` +Trailing spaces or tabs are included in the code block's content: + ```````````````````````````````` example -~~~~~~ -aaa -~~~ ~~ + foo . -<pre><code>aaa -~~~ ~~ +<pre><code>foo </code></pre> ```````````````````````````````` -Fenced code blocks can interrupt paragraphs, and can be followed -directly by paragraphs, without a blank line between: + +## Fenced code blocks + +A [code fence](@) is a sequence +of at least three consecutive backtick characters (`` ` ``) or +tildes (`~`). (Tildes and backticks cannot be mixed.) +A [fenced code block](@) +begins with a code fence, preceded by up to three spaces of indentation. + +The line with the opening code fence may optionally contain some text +following the code fence; this is trimmed of leading and trailing +spaces or tabs and called the [info string](@). If the [info string] comes +after a backtick fence, it may not contain any backtick +characters. (The reason for this restriction is that otherwise +some inline code would be incorrectly interpreted as the +beginning of a fenced code block.) + +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. If the leading code fence is +preceded by N spaces of indentation, then up to N spaces of indentation are +removed from each line of the content (if present). (If a content line is not +indented, it is preserved unchanged. If it is indented N spaces or less, all +of the indentation is removed.) + +The closing code fence may be preceded by up to three spaces of indentation, and +may be followed only by spaces or tabs, which are ignored. If the end of the +containing block (or document) is reached and no closing code fence +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 +behavior described here.) + +A fenced code block may interrupt a paragraph, and does not require +a blank line either before or after. + +The content of a code fence is treated as literal text, not parsed +as inlines. The first word of the [info string] is typically used to +specify the language of the code sample, and rendered in the `class` +attribute of the `code` tag. However, this spec does not mandate any +particular treatment of the [info string]. + +Here is a simple example with backticks: ```````````````````````````````` example -foo ``` -bar +< + > ``` -baz . -<p>foo</p> -<pre><code>bar +<pre><code>< + > </code></pre> -<p>baz</p> ```````````````````````````````` -Other blocks can also occur before and after fenced code blocks -without an intervening blank line: +With tildes: ```````````````````````````````` example -foo ---- ~~~ -bar +< + > ~~~ -# baz . -<h2>foo</h2> -<pre><code>bar +<pre><code>< + > </code></pre> -<h1>baz</h1> ```````````````````````````````` +Fewer than three backticks is not enough: + +```````````````````````````````` example +`` +foo +`` +. +<p><code>foo</code></p> +```````````````````````````````` -An [info string] can be provided after the opening code fence. -Although this spec doesn't mandate any particular treatment of -the info string, the first word is typically used to specify -the language of the code block. In HTML output, the language is -normally indicated by adding a class to the `code` element consisting -of `language-` followed by the language name. +The closing code fence must use the same character as the opening +fence: ```````````````````````````````` example -```ruby -def foo(x) - return 3 -end +``` +aaa +~~~ ``` . -<pre><code class="language-ruby">def foo(x) - return 3 -end +<pre><code>aaa +~~~ </code></pre> ```````````````````````````````` ```````````````````````````````` example -~~~~ ruby startline=3 $%@#$ -def foo(x) - return 3 -end -~~~~~~~ +~~~ +aaa +``` +~~~ . -<pre><code class="language-ruby">def foo(x) - return 3 -end +<pre><code>aaa +``` </code></pre> ```````````````````````````````` +The closing code fence must be at least as long as the opening fence: + ```````````````````````````````` example -````; ```` +aaa +``` +`````` . -<pre><code class="language-;"></code></pre> +<pre><code>aaa +``` +</code></pre> ```````````````````````````````` -[Info strings] for backtick code blocks cannot contain backticks: +```````````````````````````````` example +~~~~ +aaa +~~~ +~~~~ +. +<pre><code>aaa +~~~ +</code></pre> +```````````````````````````````` + + +Unclosed code blocks are closed by the end of the document +(or the enclosing [block quote][block quotes] or [list item][list items]): ```````````````````````````````` example -``` aa ``` -foo +``` . -<p><code>aa</code> -foo</p> +<pre><code></code></pre> ```````````````````````````````` -[Info strings] for tilde code blocks can contain backticks and tildes: +```````````````````````````````` example +````` + +``` +aaa +. +<pre><code> +``` +aaa +</code></pre> +```````````````````````````````` + ```````````````````````````````` example -~~~ aa ``` ~~~ -foo -~~~ +> ``` +> aaa + +bbb . -<pre><code class="language-aa">foo +<blockquote> +<pre><code>aaa </code></pre> +</blockquote> +<p>bbb</p> ```````````````````````````````` -Closing code fences cannot have [info strings]: +A code block can have all empty lines as its content: ```````````````````````````````` example ``` -``` aaa + + ``` . -<pre><code>``` aaa +<pre><code> + </code></pre> ```````````````````````````````` +A code block can be empty: -## HTML blocks - -An [HTML block](@) is a group of lines that is treated -as raw HTML (and will not be escaped in HTML output). +```````````````````````````````` example +``` +``` +. +<pre><code></code></pre> +```````````````````````````````` -There are seven kinds of [HTML block], which can be defined by their -start and end conditions. The block begins with a line that meets a -[start condition](@) (after up to three spaces optional indentation). -It ends with the first subsequent line that meets a matching [end -condition](@), or the last line of the document, or the last line of -the [container block](#container-blocks) containing the current HTML -block, if no line is encountered that meets the [end condition]. If -the first line meets both the [start condition] and the [end -condition], the block will contain just that line. -1. **Start condition:** line begins with the string `<script`, -`<pre`, or `<style` (case-insensitive), followed by whitespace, -the string `>`, or the end of the line.\ -**End condition:** line contains an end tag -`</script>`, `</pre>`, or `</style>` (case-insensitive; it -need not match the start tag). +Fences can be indented. If the opening fence is indented, +content lines will have equivalent opening indentation removed, +if present: -2. **Start condition:** line begins with the string `<!--`.\ -**End condition:** line contains the string `-->`. +```````````````````````````````` example + ``` + aaa +aaa +``` +. +<pre><code>aaa +aaa +</code></pre> +```````````````````````````````` -3. **Start condition:** line begins with the string `<?`.\ -**End condition:** line contains the string `?>`. -4. **Start condition:** line begins with the string `<!` -followed by an uppercase ASCII letter.\ -**End condition:** line contains the character `>`. +```````````````````````````````` example + ``` +aaa + aaa +aaa + ``` +. +<pre><code>aaa +aaa +aaa +</code></pre> +```````````````````````````````` + + +```````````````````````````````` example + ``` + aaa + aaa + aaa + ``` +. +<pre><code>aaa + aaa +aaa +</code></pre> +```````````````````````````````` + + +Four spaces of indentation is too many: + +```````````````````````````````` example + ``` + aaa + ``` +. +<pre><code>``` +aaa +``` +</code></pre> +```````````````````````````````` + + +Closing fences may be preceded by up to three spaces of indentation, and their +indentation need not match that of the opening fence: + +```````````````````````````````` example +``` +aaa + ``` +. +<pre><code>aaa +</code></pre> +```````````````````````````````` + + +```````````````````````````````` example + ``` +aaa + ``` +. +<pre><code>aaa +</code></pre> +```````````````````````````````` + + +This is not a closing fence, because it is indented 4 spaces: + +```````````````````````````````` example +``` +aaa + ``` +. +<pre><code>aaa + ``` +</code></pre> +```````````````````````````````` + + + +Code fences (opening and closing) cannot contain internal spaces or tabs: + +```````````````````````````````` example +``` ``` +aaa +. +<p><code> </code> +aaa</p> +```````````````````````````````` + + +```````````````````````````````` example +~~~~~~ +aaa +~~~ ~~ +. +<pre><code>aaa +~~~ ~~ +</code></pre> +```````````````````````````````` + + +Fenced code blocks can interrupt paragraphs, and can be followed +directly by paragraphs, without a blank line between: + +```````````````````````````````` example +foo +``` +bar +``` +baz +. +<p>foo</p> +<pre><code>bar +</code></pre> +<p>baz</p> +```````````````````````````````` + + +Other blocks can also occur before and after fenced code blocks +without an intervening blank line: + +```````````````````````````````` example +foo +--- +~~~ +bar +~~~ +# baz +. +<h2>foo</h2> +<pre><code>bar +</code></pre> +<h1>baz</h1> +```````````````````````````````` + + +An [info string] can be provided after the opening code fence. +Although this spec doesn't mandate any particular treatment of +the info string, the first word is typically used to specify +the language of the code block. In HTML output, the language is +normally indicated by adding a class to the `code` element consisting +of `language-` followed by the language name. + +```````````````````````````````` example +```ruby +def foo(x) + return 3 +end +``` +. +<pre><code class="language-ruby">def foo(x) + return 3 +end +</code></pre> +```````````````````````````````` + + +```````````````````````````````` example +~~~~ ruby startline=3 $%@#$ +def foo(x) + return 3 +end +~~~~~~~ +. +<pre><code class="language-ruby">def foo(x) + return 3 +end +</code></pre> +```````````````````````````````` + + +```````````````````````````````` example +````; +```` +. +<pre><code class="language-;"></code></pre> +```````````````````````````````` + + +[Info strings] for backtick code blocks cannot contain backticks: + +```````````````````````````````` example +``` aa ``` +foo +. +<p><code>aa</code> +foo</p> +```````````````````````````````` + + +[Info strings] for tilde code blocks can contain backticks and tildes: + +```````````````````````````````` example +~~~ aa ``` ~~~ +foo +~~~ +. +<pre><code class="language-aa">foo +</code></pre> +```````````````````````````````` + + +Closing code fences cannot have [info strings]: + +```````````````````````````````` example +``` +``` aaa +``` +. +<pre><code>``` aaa +</code></pre> +```````````````````````````````` + + + +## HTML blocks + +An [HTML block](@) is a group of lines that is treated +as raw HTML (and will not be escaped in HTML output). + +There are seven kinds of [HTML block], which can be defined by their +start and end conditions. The block begins with a line that meets a +[start condition](@) (after up to three optional spaces of indentation). +It ends with the first subsequent line that meets a matching +[end condition](@), or the last line of the document, or the last line of +the [container block](#container-blocks) containing the current HTML +block, if no line is encountered that meets the [end condition]. If +the first line meets both the [start condition] and the [end +condition], the block will contain just that line. + +1. **Start condition:** line begins with the string `<pre`, +`<script`, `<style`, or `<textarea` (case-insensitive), followed by a space, +a tab, the string `>`, or the end of the line.\ +**End condition:** line contains an end tag +`</pre>`, `</script>`, `</style>`, or `</textarea>` (case-insensitive; it +need not match the start tag). + +2. **Start condition:** line begins with the string `<!--`.\ +**End condition:** line contains the string `-->`. + +3. **Start condition:** line begins with the string `<?`.\ +**End condition:** line contains the string `?>`. + +4. **Start condition:** line begins with the string `<!` +followed by an ASCII letter.\ +**End condition:** line contains the character `>`. 5. **Start condition:** line begins with the string `<![CDATA[`.\ @@ -2063,14 +2408,14 @@ followed by one of the strings (case-insensitive) `address`, `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, `section`, `source`, `summary`, `table`, `tbody`, `td`, `tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed -by [whitespace], the end of the line, the string `>`, or +by a space, a tab, the end of the line, the string `>`, or the string `/>`.\ **End condition:** line is followed by a [blank line]. 7. **Start condition:** line begins with a complete [open tag] -(with any [tag name] other than `script`, -`style`, or `pre`) or a complete [closing tag], -followed only by [whitespace] or the end of the line.\ +(with any [tag name] other than `pre`, `script`, +`style`, or `textarea`) or a complete [closing tag], +followed by zero or more spaces and tabs, followed by the end of the line.\ **End condition:** line is followed by a [blank line]. HTML blocks continue until they are closed by their appropriate @@ -2080,7 +2425,7 @@ block** that might otherwise be recognised as a start condition will be ignored by the parser and passed through as-is, without changing the parser's state. -For instance, `<pre>` within a HTML block started by `<table>` will not affect +For instance, `<pre>` within an HTML block started by `<table>` will not affect the parser state; as the HTML block was started in by start condition 6, it will end at any blank line. This can be surprising: @@ -2101,7 +2446,7 @@ _world_. </td></tr></table> ```````````````````````````````` -In this case, the HTML block is terminated by the newline — the `**Hello**` +In this case, the HTML block is terminated by the blank line — the `**Hello**` text remains verbatim — and regular parsing resumes, with a paragraph, emphasised `world` and inline and block HTML following. @@ -2379,7 +2724,7 @@ rather than an [HTML block].) HTML tags designed to contain literal content -(`script`, `style`, `pre`), comments, processing instructions, +(`pre`, `script`, `style`, `textarea`), comments, processing instructions, and declarations are treated somewhat differently. Instead of ending at the first blank line, these blocks end at the first line containing a corresponding end tag. @@ -2425,6 +2770,26 @@ document.getElementById("demo").innerHTML = "Hello JavaScript!"; ```````````````````````````````` +A textarea tag (type 1): + +```````````````````````````````` example +<textarea> + +*foo* + +_bar_ + +</textarea> +. +<textarea> + +*foo* + +_bar_ + +</textarea> +```````````````````````````````` + A style tag (type 1): ```````````````````````````````` example @@ -2603,7 +2968,8 @@ function matchwo(a,b) ```````````````````````````````` -The opening tag can be indented 1-3 spaces, but not 4: +The opening tag can be preceded by up to three spaces of indentation, but not +four: ```````````````````````````````` example <!-- foo --> @@ -2679,7 +3045,7 @@ specification, which says: > The only restrictions are that block-level HTML elements — > e.g. `<div>`, `<table>`, `<pre>`, `<p>`, etc. — must be separated from > surrounding content by blank lines, and the start and end tags of the -> block should not be indented with tabs or spaces. +> block should not be indented with spaces or tabs. In some ways Gruber's rule is more restrictive than the one given here: @@ -2797,14 +3163,15 @@ deleted. The exception is inside `<pre>` tags, but as described ## Link reference definitions A [link reference definition](@) -consists of a [link label], indented up to three spaces, followed -by a colon (`:`), optional [whitespace] (including up to one +consists of a [link label], optionally preceded by up to three spaces of +indentation, followed +by a colon (`:`), optional spaces or tabs (including up to one [line ending]), a [link destination], -optional [whitespace] (including up to one +optional spaces or tabs (including up to one [line ending]), and an optional [link title], which if it is present must be separated -from the [link destination] by [whitespace]. -No further [non-whitespace characters] may occur on the line. +from the [link destination] by spaces or tabs. +No further character may occur. A [link reference definition] does not correspond to a structural element of a document. Instead, it @@ -2922,7 +3289,7 @@ The link destination may not be omitted: ```````````````````````````````` The title must be separated from the link destination by -whitespace: +spaces or tabs: ```````````````````````````````` example [foo]: <bar>(baz) @@ -2991,8 +3358,11 @@ case-insensitive (see [matches]). ```````````````````````````````` -Here is a link reference definition with no corresponding link. -It contributes nothing to the document. +Whether something is a [link reference definition] is +independent of whether the link reference it defines is +used in the document. Thus, for example, the following +document contains just a link reference definition, and +no visible content: ```````````````````````````````` example [foo]: /url @@ -3013,7 +3383,7 @@ bar This is not a link reference definition, because there are -[non-whitespace characters] after the title: +characters other than spaces or tabs after the title: ```````````````````````````````` example [foo]: /url "title" ok @@ -3145,18 +3515,6 @@ are defined: ```````````````````````````````` -Whether something is a [link reference definition] is -independent of whether the link reference it defines is -used in the document. Thus, for example, the following -document contains just a link reference definition, and -no visible content: - -```````````````````````````````` example -[foo]: /url -. -```````````````````````````````` - - ## Paragraphs A sequence of non-blank lines that cannot be interpreted as other @@ -3164,7 +3522,7 @@ kinds of blocks forms a [paragraph](@). The contents of the paragraph are the result of parsing the paragraph's raw content as inlines. The paragraph's raw content is formed by concatenating the lines and removing initial and final -[whitespace]. +spaces or tabs. A simple example with two paragraphs: @@ -3194,7 +3552,7 @@ ddd</p> ```````````````````````````````` -Multiple blank lines between paragraph have no effect: +Multiple blank lines between paragraphs have no effect: ```````````````````````````````` example aaa @@ -3207,7 +3565,7 @@ bbb ```````````````````````````````` -Leading spaces are skipped: +Leading spaces or tabs are skipped: ```````````````````````````````` example aaa @@ -3232,8 +3590,8 @@ ccc</p> ```````````````````````````````` -However, the first line may be indented at most three spaces, -or an indented code block will be triggered: +However, the first line may be preceded by up to three spaces of indentation. +Four spaces of indentation is too many: ```````````````````````````````` example aaa @@ -3254,7 +3612,7 @@ bbb ```````````````````````````````` -Final spaces are stripped before inline parsing, so a paragraph +Final spaces or tabs are stripped before inline parsing, so a paragraph that ends with two or more spaces will not end with a [hard line break]: @@ -3313,9 +3671,11 @@ these constructions. (A recipe is provided below in the section entitled ## Block quotes -A [block quote marker](@) -consists of 0-3 spaces of initial indent, plus (a) the character `>` together -with a following space, or (b) a single character `>` not followed by a space. +A [block quote marker](@), +optionally preceded by up to three spaces of indentation, +consists of (a) the character `>` together with a following space of +indentation, or (b) a single character `>` not followed by a space of +indentation. The following rules define [block quotes]: @@ -3327,8 +3687,8 @@ The following rules define [block quotes]: 2. **Laziness.** If a string of lines *Ls* constitute a [block quote](#block-quotes) with contents *Bs*, then the result of deleting the initial [block quote marker] from one or - more lines in which the next [non-whitespace character] after the [block - quote marker] is [paragraph continuation + more lines in which the next character other than a space or tab after the + [block quote marker] is [paragraph continuation text] is a block quote with *Bs* as its content. [Paragraph continuation text](@) is text that will be parsed as part of the content of a paragraph, but does @@ -3354,7 +3714,7 @@ baz</p> ```````````````````````````````` -The spaces after the `>` characters can be omitted: +The space or tab after the `>` characters can be omitted: ```````````````````````````````` example ># Foo @@ -3369,7 +3729,7 @@ baz</p> ```````````````````````````````` -The `>` characters can be indented 1-3 spaces: +The `>` characters can be preceded by up to three spaces of indentation: ```````````````````````````````` example > # Foo @@ -3384,7 +3744,7 @@ baz</p> ```````````````````````````````` -Four spaces gives us a code block: +Four spaces of indentation is too many: ```````````````````````````````` example > # Foo @@ -3719,8 +4079,8 @@ baz</p> When including an indented code block in a block quote, remember that the [block quote marker] includes -both the `>` and a following space. So *five spaces* are needed after -the `>`: +both the `>` and a following space of indentation. So *five spaces* are needed +after the `>`: ```````````````````````````````` example > code @@ -3755,10 +4115,10 @@ in some browsers.) The following rules define [list items]: 1. **Basic case.** If a sequence of lines *Ls* constitute a sequence of - blocks *Bs* starting with a [non-whitespace character], and *M* is a - list marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces, 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 + 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 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 @@ -3823,8 +4183,8 @@ with two lines.</p> The most important thing to notice is that the position of the text after the list marker determines how much indentation is needed in subsequent blocks in the list item. If the list -marker takes up two spaces, and there are three spaces between -the list marker and the next [non-whitespace character], then blocks +marker takes up two spaces of indentation, and there are three spaces between +the list marker and the next character other than a space or tab, then blocks must be indented five spaces in order to fall under the list item. @@ -3885,10 +4245,10 @@ put under the list item: It is tempting to think of this in terms of columns: the continuation -blocks must be indented at least to the column of the first -[non-whitespace character] after the list marker. However, that is not quite right. -The spaces after the list marker determine how much relative indentation -is needed. Which column this indentation reaches will depend on +blocks must be indented at least to the column of the first character other than +a space or tab after the list marker. However, that is not quite right. +The spaces of indentation after the list marker determine how much relative +indentation is needed. Which column this indentation reaches will depend on how the list item is embedded in other constructions, as shown by this example: @@ -3935,7 +4295,7 @@ far enough past the blockquote marker: ```````````````````````````````` -Note that at least one space is needed between the list marker and +Note that at least one space or tab is needed between the list marker and any following content, so these are not list items: ```````````````````````````````` example @@ -4067,16 +4427,16 @@ A start number may not be negative: 2. **Item starting with indented code.** If a sequence of lines *Ls* constitute a sequence of blocks *Bs* starting with an indented code block, and *M* is a list marker of width *W* followed by - one space, then the result of prepending *M* and the following - space to the first line of *Ls*, and indenting subsequent lines of - *Ls* by *W + 1* spaces, is a list item with *Bs* as its contents. + one space of indentation, then the result of prepending *M* and the + following space to the first line of *Ls*, and indenting subsequent lines + of *Ls* by *W + 1* spaces, is a list item with *Bs* as its contents. If a line is empty, then it need not be indented. 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 number, based on the ordered list marker. -An indented code block will have to be indented four spaces beyond -the edge of the region where text will be included in the list item. +An indented code block will have to be preceded by four spaces of indentation +beyond the edge of the region where text will be included in the list item. In the following case that is 6 spaces: ```````````````````````````````` example @@ -4112,8 +4472,8 @@ And in this case it is 11 spaces: If the *first* block in the list item is an indented code block, -then by rule #2, the contents must be indented *one* space after the -list marker: +then by rule #2, the contents must be preceded by *one* space of indentation +after the list marker: ```````````````````````````````` example indented code @@ -4149,7 +4509,7 @@ paragraph ```````````````````````````````` -Note that an additional space indent is interpreted as space +Note that an additional space of indentation is interpreted as space inside the code block: ```````````````````````````````` example @@ -4173,10 +4533,10 @@ 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 -[non-whitespace character], and (b) cases in which +characer 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 -a three-space indent, the rules do not allow us to form a list item by +three spaces of indentation, the rules do not allow us to form a list item by indenting the whole thing and prepending a list marker: ```````````````````````````````` example @@ -4201,8 +4561,8 @@ bar ```````````````````````````````` -This is not a significant restriction, because when a block begins -with 1-3 spaces indent, the indentation can always be removed without +This is not a significant restriction, because when a block is preceded by up to +three spaces of indentation, the indentation can always be removed without a change in interpretation, allowing rule #1 to be applied. So, in the above case: @@ -4222,11 +4582,10 @@ the above case: 3. **Item starting with a blank line.** If a sequence of lines *Ls* starting with a single [blank line] constitute a (possibly empty) - sequence of blocks *Bs*, not separated from each other by more than - one blank line, and *M* is a list marker of width *W*, + sequence of blocks *Bs*, and *M* is a list marker of width *W*, then the result of prepending *M* to the first line of *Ls*, and - indenting subsequent lines of *Ls* by *W + 1* spaces, is a list - item with *Bs* as its contents. + preceding subsequent lines of *Ls* by *W + 1* spaces of indentation, is a + list item with *Bs* as its contents. If a line is empty, then it need not be indented. 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 @@ -4301,7 +4660,7 @@ Here is an empty bullet list item: ```````````````````````````````` -It does not matter whether there are spaces following the [list marker]: +It does not matter whether there are spaces or tabs following the [list marker]: ```````````````````````````````` example - foo @@ -4358,9 +4717,9 @@ foo 4. **Indentation.** If a sequence of lines *Ls* constitutes a list item - according to rule #1, #2, or #3, then the result of indenting each line - of *Ls* by 1-3 spaces (the same for each line) also constitutes a - list item with the same contents and attributes. If a line is + according to rule #1, #2, or #3, then the result of preceding each line + of *Ls* by up to three spaces of indentation (the same for each line) also + constitutes a list item with the same contents and attributes. If a line is empty, then it need not be indented. Indented one space: @@ -4459,7 +4818,7 @@ Four spaces indent gives a code block: 5. **Laziness.** If a string of lines *Ls* constitute a [list item](#list-items) with contents *Bs*, then the result of deleting some or all of the indentation from one or more lines in which the - next [non-whitespace character] after the indentation is + next character other than a space or tab after the indentation is [paragraph continuation text] is a list item with the same contents and attributes. The unindented lines are called @@ -4544,7 +4903,7 @@ continued here.</p> The rules for sublists follow from the general rules [above][List items]. A sublist must be indented the same number -of spaces a paragraph would need to be in order to be included +of spaces of indentation a paragraph would need to be in order to be included in the list item. So, in this case we need two spaces indent: @@ -4777,8 +5136,8 @@ The choice of four spaces is arbitrary. It can be learned, but it is not likely to be guessed, and it trips up beginners regularly. Would it help to adopt a two-space rule? The problem is that such -a rule, together with the rule allowing 1--3 spaces indentation of the -initial list marker, allows text that is indented *less than* the +a rule, together with the rule allowing up to three spaces of indentation for +the initial list marker, allows text that is indented *less than* the original list marker to be included in the list item. For example, `Markdown.pl` parses @@ -5170,8 +5529,8 @@ item: </ol> ```````````````````````````````` -Note, however, that list items may not be indented more than -three spaces. Here `- e` is treated as a paragraph continuation +Note, however, that list items may not be preceded by more than +three spaces of indentation. Here `- e` is treated as a paragraph continuation line, because it is indented more than three spaces: ```````````````````````````````` example @@ -5257,7 +5616,7 @@ So is this, with a empty second item: ```````````````````````````````` -These are loose lists, even though there is no space between the items, +These are loose lists, even though there are no blank lines between the items, because one of the items directly contains two block-level elements with a blank line between them: @@ -5265,585 +5624,246 @@ with a blank line between them: - a - b - c -- d -. -<ul> -<li> -<p>a</p> -</li> -<li> -<p>b</p> -<p>c</p> -</li> -<li> -<p>d</p> -</li> -</ul> -```````````````````````````````` - - -```````````````````````````````` example -- a -- b - - [ref]: /url -- d -. -<ul> -<li> -<p>a</p> -</li> -<li> -<p>b</p> -</li> -<li> -<p>d</p> -</li> -</ul> -```````````````````````````````` - - -This is a tight list, because the blank lines are in a code block: - -```````````````````````````````` example -- a -- ``` - b - - - ``` -- c -. -<ul> -<li>a</li> -<li> -<pre><code>b - - -</code></pre> -</li> -<li>c</li> -</ul> -```````````````````````````````` - - -This is a tight list, because the blank line is between two -paragraphs of a sublist. So the sublist is loose while -the outer list is tight: - -```````````````````````````````` example -- a - - b - - c -- d -. -<ul> -<li>a -<ul> -<li> -<p>b</p> -<p>c</p> -</li> -</ul> -</li> -<li>d</li> -</ul> -```````````````````````````````` - - -This is a tight list, because the blank line is inside the -block quote: - -```````````````````````````````` example -* a - > b - > -* c -. -<ul> -<li>a -<blockquote> -<p>b</p> -</blockquote> -</li> -<li>c</li> -</ul> -```````````````````````````````` - - -This list is tight, because the consecutive block elements -are not separated by blank lines: - -```````````````````````````````` example -- a - > b - ``` - c - ``` -- d -. -<ul> -<li>a -<blockquote> -<p>b</p> -</blockquote> -<pre><code>c -</code></pre> -</li> -<li>d</li> -</ul> -```````````````````````````````` - - -A single-paragraph list is tight: - -```````````````````````````````` example -- a -. -<ul> -<li>a</li> -</ul> -```````````````````````````````` - - -```````````````````````````````` example -- a - - b -. -<ul> -<li>a -<ul> -<li>b</li> -</ul> -</li> -</ul> -```````````````````````````````` - - -This list is loose, because of the blank line between the -two block elements in the list item: - -```````````````````````````````` example -1. ``` - foo - ``` - - bar -. -<ol> -<li> -<pre><code>foo -</code></pre> -<p>bar</p> -</li> -</ol> -```````````````````````````````` - - -Here the outer list is loose, the inner list tight: - -```````````````````````````````` example -* foo - * bar - - baz -. -<ul> -<li> -<p>foo</p> -<ul> -<li>bar</li> -</ul> -<p>baz</p> -</li> -</ul> -```````````````````````````````` - - -```````````````````````````````` example -- a - - b - - c - -- d - - e - - f -. -<ul> -<li> -<p>a</p> -<ul> -<li>b</li> -<li>c</li> -</ul> -</li> -<li> -<p>d</p> -<ul> -<li>e</li> -<li>f</li> -</ul> -</li> -</ul> -```````````````````````````````` - - -# Inlines - -Inlines are parsed sequentially from the beginning of the character -stream to the end (left to right, in left-to-right languages). -Thus, for example, in - -```````````````````````````````` example -`hi`lo` -. -<p><code>hi</code>lo`</p> -```````````````````````````````` - -`hi` is parsed as code, leaving the backtick at the end as a literal -backtick. - - -## Backslash escapes - -Any ASCII punctuation character may be backslash-escaped: - -```````````````````````````````` example -\!\"\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~ -. -<p>!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~</p> -```````````````````````````````` - - -Backslashes before other characters are treated as literal -backslashes: - -```````````````````````````````` example -\→\A\a\ \3\φ\« -. -<p>\→\A\a\ \3\φ\«</p> -```````````````````````````````` - - -Escaped characters are treated as regular characters and do -not have their usual Markdown meanings: - -```````````````````````````````` example -\*not emphasized* -\<br/> not a tag -\[not a link](/foo) -\`not code` -1\. not a list -\* not a list -\# not a heading -\[foo]: /url "not a reference" -\ö not a character entity -. -<p>*not emphasized* -<br/> not a tag -[not a link](/foo) -`not code` -1. not a list -* not a list -# not a heading -[foo]: /url "not a reference" -&ouml; not a character entity</p> -```````````````````````````````` - - -If a backslash is itself escaped, the following character is not: - -```````````````````````````````` example -\\*emphasis* -. -<p>\<em>emphasis</em></p> -```````````````````````````````` - - -A backslash at the end of the line is a [hard line break]: - -```````````````````````````````` example -foo\ -bar -. -<p>foo<br /> -bar</p> -```````````````````````````````` - - -Backslash escapes do not work in code blocks, code spans, autolinks, or -raw HTML: - -```````````````````````````````` example -`` \[\` `` -. -<p><code>\[\`</code></p> -```````````````````````````````` - - -```````````````````````````````` example - \[\] -. -<pre><code>\[\] -</code></pre> -```````````````````````````````` - - -```````````````````````````````` example -~~~ -\[\] -~~~ -. -<pre><code>\[\] -</code></pre> -```````````````````````````````` - - -```````````````````````````````` example -<http://example.com?find=\*> -. -<p><a href="http://example.com?find=%5C*">http://example.com?find=\*</a></p> -```````````````````````````````` - - -```````````````````````````````` example -<a href="/bar\/)"> -. -<a href="/bar\/)"> -```````````````````````````````` - - -But they work in all other contexts, including URLs and link titles, -link references, and [info strings] in [fenced code blocks]: - -```````````````````````````````` example -[foo](/bar\* "ti\*tle") -. -<p><a href="/bar*" title="ti*tle">foo</a></p> -```````````````````````````````` - - -```````````````````````````````` example -[foo] - -[foo]: /bar\* "ti\*tle" -. -<p><a href="/bar*" title="ti*tle">foo</a></p> -```````````````````````````````` - - -```````````````````````````````` example -``` foo\+bar -foo -``` -. -<pre><code class="language-foo+bar">foo -</code></pre> -```````````````````````````````` - - - -## Entity and numeric character references - -Valid HTML entity references and numeric character references -can be used in place of the corresponding Unicode character, -with the following exceptions: - -- Entity and character references are not recognized in code - blocks and code spans. - -- Entity and character references cannot stand in place of - special characters that define structural elements in - CommonMark. For example, although `*` can be used - in place of a literal `*` character, `*` cannot replace - `*` in emphasis delimiters, bullet list markers, or thematic - breaks. - -Conforming CommonMark parsers need not store information about -whether a particular character was represented in the source -using a Unicode character or an entity reference. - -[Entity references](@) consist of `&` + any of the valid -HTML5 entity names + `;`. The -document <https://html.spec.whatwg.org/multipage/entities.json> -is used as an authoritative source for the valid entity -references and their corresponding code points. - -```````````````````````````````` example -  & © Æ Ď -¾ ℋ ⅆ -∲ ≧̸ -. -<p>  & © Æ Ď -¾ ℋ ⅆ -∲ ≧̸</p> -```````````````````````````````` - - -[Decimal numeric character -references](@) -consist of `&#` + a string of 1--7 arabic digits + `;`. A -numeric character reference is parsed as the corresponding -Unicode character. Invalid Unicode code points will be replaced by -the REPLACEMENT CHARACTER (`U+FFFD`). For security reasons, -the code point `U+0000` will also be replaced by `U+FFFD`. - -```````````````````````````````` example -# Ӓ Ϡ � -. -<p># Ӓ Ϡ �</p> -```````````````````````````````` - - -[Hexadecimal numeric character -references](@) consist of `&#` + -either `X` or `x` + a string of 1-6 hexadecimal digits + `;`. -They too are parsed as the corresponding Unicode character (this -time specified with a hexadecimal numeral instead of decimal). - -```````````````````````````````` example -" ആ ಫ + c +- d . -<p>" ആ ಫ</p> +<ul> +<li> +<p>a</p> +</li> +<li> +<p>b</p> +<p>c</p> +</li> +<li> +<p>d</p> +</li> +</ul> ```````````````````````````````` -Here are some nonentities: - ```````````````````````````````` example -  &x; &#; &#x; -� -&#abcdef0; -&ThisIsNotDefined; &hi?; +- a +- b + + [ref]: /url +- d . -<p>&nbsp &x; &#; &#x; -&#987654321; -&#abcdef0; -&ThisIsNotDefined; &hi?;</p> +<ul> +<li> +<p>a</p> +</li> +<li> +<p>b</p> +</li> +<li> +<p>d</p> +</li> +</ul> ```````````````````````````````` -Although HTML5 does accept some entity references -without a trailing semicolon (such as `©`), these are not -recognized here, because it makes the grammar too ambiguous: +This is a tight list, because the blank lines are in a code block: ```````````````````````````````` example -© +- a +- ``` + b + + + ``` +- c . -<p>&copy</p> +<ul> +<li>a</li> +<li> +<pre><code>b + + +</code></pre> +</li> +<li>c</li> +</ul> ```````````````````````````````` -Strings that are not on the list of HTML5 named entities are not -recognized as entity references either: +This is a tight list, because the blank line is between two +paragraphs of a sublist. So the sublist is loose while +the outer list is tight: ```````````````````````````````` example -&MadeUpEntity; +- a + - b + + c +- d . -<p>&MadeUpEntity;</p> +<ul> +<li>a +<ul> +<li> +<p>b</p> +<p>c</p> +</li> +</ul> +</li> +<li>d</li> +</ul> ```````````````````````````````` -Entity and numeric character references are recognized in any -context besides code spans or code blocks, including -URLs, [link titles], and [fenced code block][] [info strings]: +This is a tight list, because the blank line is inside the +block quote: ```````````````````````````````` example -<a href="öö.html"> +* a + > b + > +* c . -<a href="öö.html"> +<ul> +<li>a +<blockquote> +<p>b</p> +</blockquote> +</li> +<li>c</li> +</ul> ```````````````````````````````` +This list is tight, because the consecutive block elements +are not separated by blank lines: + ```````````````````````````````` example -[foo](/föö "föö") +- a + > b + ``` + c + ``` +- d . -<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p> +<ul> +<li>a +<blockquote> +<p>b</p> +</blockquote> +<pre><code>c +</code></pre> +</li> +<li>d</li> +</ul> ```````````````````````````````` -```````````````````````````````` example -[foo] +A single-paragraph list is tight: -[foo]: /föö "föö" +```````````````````````````````` example +- a . -<p><a href="/f%C3%B6%C3%B6" title="föö">foo</a></p> +<ul> +<li>a</li> +</ul> ```````````````````````````````` ```````````````````````````````` example -``` föö -foo -``` +- a + - b . -<pre><code class="language-föö">foo -</code></pre> +<ul> +<li>a +<ul> +<li>b</li> +</ul> +</li> +</ul> ```````````````````````````````` -Entity and numeric character references are treated as literal -text in code spans and code blocks: +This list is loose, because of the blank line between the +two block elements in the list item: ```````````````````````````````` example -`föö` -. -<p><code>f&ouml;&ouml;</code></p> -```````````````````````````````` - +1. ``` + foo + ``` -```````````````````````````````` example - föfö + bar . -<pre><code>f&ouml;f&ouml; +<ol> +<li> +<pre><code>foo </code></pre> +<p>bar</p> +</li> +</ol> ```````````````````````````````` -Entity and numeric character references cannot be used -in place of symbols indicating structure in CommonMark -documents. +Here the outer list is loose, the inner list tight: ```````````````````````````````` example -*foo* -*foo* +* foo + * bar + + baz . -<p>*foo* -<em>foo</em></p> +<ul> +<li> +<p>foo</p> +<ul> +<li>bar</li> +</ul> +<p>baz</p> +</li> +</ul> ```````````````````````````````` + ```````````````````````````````` example -* foo +- a + - b + - c -* foo +- d + - e + - f . -<p>* foo</p> <ul> -<li>foo</li> +<li> +<p>a</p> +<ul> +<li>b</li> +<li>c</li> +</ul> +</li> +<li> +<p>d</p> +<ul> +<li>e</li> +<li>f</li> +</ul> +</li> </ul> ```````````````````````````````` -```````````````````````````````` example -foo bar -. -<p>foo -bar</p> -```````````````````````````````` +# Inlines + +Inlines are parsed sequentially from the beginning of the character +stream to the end (left to right, in left-to-right languages). +Thus, for example, in ```````````````````````````````` example - foo +`hi`lo` . -<p>→foo</p> +<p><code>hi</code>lo`</p> ```````````````````````````````` +`hi` is parsed as code, leaving the backtick at the end as a literal +backtick. -```````````````````````````````` example -[a](url "tit") -. -<p>[a](url "tit")</p> -```````````````````````````````` ## Code spans @@ -5854,7 +5874,7 @@ preceded nor followed by a backtick. A [code span](@) begins with a backtick string and ends with a backtick string of equal length. The contents of the code span are -the characters between the two backtick strings, normalized in the +the characters between these two backtick strings, normalized in the following ways: - First, [line endings] are converted to [spaces]. @@ -6133,17 +6153,17 @@ a non-backslash-escaped `_` character. A [left-flanking delimiter run](@) is a [delimiter run] that is (1) not followed by [Unicode whitespace], -and either (2a) not followed by a [punctuation character], or -(2b) followed by a [punctuation character] and -preceded by [Unicode whitespace] or a [punctuation character]. +and either (2a) not followed by a [Unicode punctuation character], or +(2b) followed by a [Unicode punctuation character] and +preceded by [Unicode whitespace] or a [Unicode punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. A [right-flanking delimiter run](@) is a [delimiter run] that is (1) not preceded by [Unicode whitespace], -and either (2a) not preceded by a [punctuation character], or -(2b) preceded by a [punctuation character] and -followed by [Unicode whitespace] or a [punctuation character]. +and either (2a) not preceded by a [Unicode punctuation character], or +(2b) preceded by a [Unicode punctuation character] and +followed by [Unicode whitespace] or a [Unicode punctuation character]. For purposes of this definition, the beginning and the end of the line count as Unicode whitespace. @@ -6198,7 +6218,7 @@ The following rules define emphasis and strong emphasis: it is part of a [left-flanking delimiter run] and either (a) not part of a [right-flanking delimiter run] or (b) part of a [right-flanking delimiter run] - preceded by punctuation. + preceded by a [Unicode punctuation character]. 3. A single `*` character [can close emphasis](@) iff it is part of a [right-flanking delimiter run]. @@ -6207,7 +6227,7 @@ The following rules define emphasis and strong emphasis: it is part of a [right-flanking delimiter run] and either (a) not part of a [left-flanking delimiter run] or (b) part of a [left-flanking delimiter run] - followed by punctuation. + followed by a [Unicode punctuation character]. 5. A double `**` [can open strong emphasis](@) iff it is part of a [left-flanking delimiter run]. @@ -6216,7 +6236,7 @@ The following rules define emphasis and strong emphasis: it is part of a [left-flanking delimiter run] and either (a) not part of a [right-flanking delimiter run] or (b) part of a [right-flanking delimiter run] - preceded by punctuation. + preceded by a [Unicode punctuation character]. 7. A double `**` [can close strong emphasis](@) iff it is part of a [right-flanking delimiter run]. @@ -6225,7 +6245,7 @@ The following rules define emphasis and strong emphasis: it is part of a [right-flanking delimiter run] and either (a) not part of a [left-flanking delimiter run] or (b) part of a [left-flanking delimiter run] - followed by punctuation. + followed by a [Unicode punctuation character]. 9. Emphasis begins with a delimiter that [can open emphasis] and ends with a delimiter that [can close emphasis], and that uses the same @@ -6437,7 +6457,7 @@ whitespace: ```````````````````````````````` -A newline also counts as whitespace: +A line ending also counts as whitespace: ```````````````````````````````` example *foo bar @@ -6602,7 +6622,7 @@ __ foo bar__ ```````````````````````````````` -A newline counts as whitespace: +A line ending counts as whitespace: ```````````````````````````````` example __ foo bar__ @@ -6881,7 +6901,7 @@ emphasis sections in this example: The same condition ensures that the following cases are all strong emphasis nested inside -emphasis, even when the interior spaces are +emphasis, even when the interior whitespace is omitted: @@ -7458,13 +7478,14 @@ following rules apply: A [link destination](@) consists of either - a sequence of zero or more characters between an opening `<` and a - closing `>` that contains no line breaks or unescaped + closing `>` that contains no line endings or unescaped `<` or `>` characters, or -- a nonempty sequence of characters that does not start with - `<`, does not include ASCII space or control characters, and - includes parentheses only if (a) they are backslash-escaped or - (b) they are part of a balanced pair of unescaped parentheses. +- a nonempty sequence of characters that does not start with `<`, + does not include [ASCII control characters][ASCII control character] + or [space] character, and includes parentheses only if (a) they are + backslash-escaped or (b) they are part of a balanced pair of + unescaped parentheses. (Implementations may impose limits on parentheses nesting to avoid performance issues, but at least three levels of nesting should be supported.) @@ -7487,10 +7508,14 @@ Although [link titles] may span multiple lines, they may not contain a [blank line]. An [inline link](@) consists of a [link text] followed immediately -by a left parenthesis `(`, optional [whitespace], an optional -[link destination], an optional [link title] separated from the link -destination by [whitespace], optional [whitespace], and a right -parenthesis `)`. The link's text consists of the inlines contained +by a left parenthesis `(`, an optional [link destination], an optional +[link title], and a right parenthesis `)`. +These four components may be separated by spaces, tabs, and up to one line +ending. +If both [link destination] and [link title] are present, they *must* be +separated by spaces, tabs, and up to one line ending. + +The link's text consists of the inlines contained in the [link text] (excluding the enclosing square brackets). The link's URI consists of the link destination, excluding enclosing `<...>` if present, with backslash-escapes in effect as described @@ -7507,7 +7532,8 @@ Here is a simple inline link: ```````````````````````````````` -The title may be omitted: +The title, the link text and even +the destination may be omitted: ```````````````````````````````` example [link](/uri) @@ -7515,8 +7541,12 @@ The title may be omitted: <p><a href="/uri">link</a></p> ```````````````````````````````` +```````````````````````````````` example +[](./target.md) +. +<p><a href="./target.md"></a></p> +```````````````````````````````` -Both the title and the destination may be omitted: ```````````````````````````````` example [link]() @@ -7531,6 +7561,13 @@ Both the title and the destination may be omitted: <p><a href="">link</a></p> ```````````````````````````````` + +```````````````````````````````` example +[]() +. +<p><a href=""></a></p> +```````````````````````````````` + The destination can only contain spaces if it is enclosed in pointy brackets: @@ -7546,7 +7583,7 @@ enclosed in pointy brackets: <p><a href="/my%20uri">link</a></p> ```````````````````````````````` -The destination cannot contain line breaks, +The destination cannot contain line endings, even if enclosed in pointy brackets: ```````````````````````````````` example @@ -7615,6 +7652,13 @@ balanced: However, if you have unbalanced parentheses, you need to escape or use the `<...>` form: +```````````````````````````````` example +[link](foo(and(bar)) +. +<p>[link](foo(and(bar))</p> +```````````````````````````````` + + ```````````````````````````````` example [link](foo\(and\(bar\)) . @@ -7714,7 +7758,8 @@ may be used in titles: ```````````````````````````````` -Titles must be separated from the link using a [whitespace]. +Titles must be separated from the link using spaces, tabs, and up to one line +ending. Other [Unicode whitespace] like non-breaking space doesn't work. ```````````````````````````````` example @@ -7757,7 +7802,8 @@ titles with no closing quotation mark, though 1.0.2b8 does not. It seems preferable to adopt a simple, rational rule that works the same way in inline links and link reference definitions.) -[Whitespace] is allowed around the destination and title: +Spaces, tabs, and up to one line ending is allowed around the destination and +title: ```````````````````````````````` example [link]( /uri @@ -7908,7 +7954,8 @@ that [matches] a [link reference definition] elsewhere in the document. A [link label](@) begins with a left bracket (`[`) and ends with the first right bracket (`]`) that is not backslash-escaped. -Between these brackets there must be at least one [non-whitespace character]. +Between these brackets there must be at least one character that is not a space, +tab, or line ending. Unescaped square bracket characters are not allowed inside the opening and closing square brackets of [link labels]. A link label can have at most 999 characters inside the square @@ -7918,14 +7965,13 @@ One label [matches](@) another just in case their normalized forms are equal. To normalize a label, strip off the opening and closing brackets, perform the *Unicode case fold*, strip leading and trailing -[whitespace] and collapse consecutive internal -[whitespace] to a single space. If there are multiple +spaces, tabs, and line endings, and collapse consecutive internal +spaces, tabs, and line endings to a single space. If there are multiple matching reference link definitions, the one that comes first in the document is used. (It is desirable in such cases to emit a warning.) -The contents of the first 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]. +The link's URI and title are provided by the matching [link +reference definition]. Here is a simple example: @@ -8018,11 +8064,11 @@ emphasis grouping: ```````````````````````````````` example -[foo *bar][ref] +[foo *bar][ref]* [ref]: /uri . -<p><a href="/uri">foo *bar</a></p> +<p><a href="/uri">foo *bar</a>*</p> ```````````````````````````````` @@ -8070,15 +8116,15 @@ Matching is case-insensitive: Unicode case fold is used: ```````````````````````````````` example -[Толпой][Толпой] is a Russian word. +[ẞ] -[ТОЛПОЙ]: /url +[SS]: /url . -<p><a href="/url">Толпой</a> is a Russian word.</p> +<p><a href="/url">ẞ</a></p> ```````````````````````````````` -Consecutive internal [whitespace] is treated as one space for +Consecutive internal spaces, tabs, and line endings are treated as one space for purposes of determining matching: ```````````````````````````````` example @@ -8091,7 +8137,7 @@ purposes of determining matching: ```````````````````````````````` -No [whitespace] is allowed between the [link text] and the +No spaces, tabs, or line endings are allowed between the [link text] and the [link label]: ```````````````````````````````` example @@ -8221,7 +8267,8 @@ Note that in this example `]` is not backslash-escaped: ```````````````````````````````` -A [link label] must contain at least one [non-whitespace character]: +A [link label] must contain at least one character that is not a space, tab, or +line ending: ```````````````````````````````` example [] @@ -8286,7 +8333,7 @@ The link labels are case-insensitive: -As with full reference links, [whitespace] is not +As with full reference links, spaces, tabs, or line endings are not allowed between the two sets of brackets: ```````````````````````````````` example @@ -8614,7 +8661,7 @@ The labels are case-insensitive: ```````````````````````````````` -As with reference links, [whitespace] is not allowed +As with reference links, spaces, tabs, and line endings, are not allowed between the two sets of brackets: ```````````````````````````````` example @@ -8707,9 +8754,9 @@ 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 than ASCII -[whitespace] and control characters, `<`, and `>`. If -the URI includes these characters, they must be percent-encoded +followed by zero or more characters other [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). For purposes of this spec, a [scheme](@) is any sequence @@ -8895,7 +8942,7 @@ A [tag name](@) consists of an ASCII letter followed by zero or more ASCII letters, digits, or hyphens (`-`). -An [attribute](@) consists of [whitespace], +An [attribute](@) consists of spaces, tabs, and up to one line ending, an [attribute name], and an optional [attribute value specification]. @@ -8905,9 +8952,9 @@ letters, digits, `_`, `.`, `:`, or `-`. (Note: This is the XML specification restricted to ASCII. HTML5 is laxer.) An [attribute value specification](@) -consists of optional [whitespace], -a `=` character, optional [whitespace], and an [attribute -value]. +consists of optional spaces, tabs, and up to one line ending, +a `=` character, optional spaces, tabs, and up to one line ending, +and an [attribute value]. An [attribute value](@) consists of an [unquoted attribute value], @@ -8915,7 +8962,7 @@ a [single-quoted attribute value], or a [double-quoted attribute value]. An [unquoted attribute value](@) is a nonempty string of characters not -including [whitespace], `"`, `'`, `=`, `<`, `>`, or `` ` ``. +including spaces, tabs, line endings, `"`, `'`, `=`, `<`, `>`, or `` ` ``. A [single-quoted attribute value](@) consists of `'`, zero or more @@ -8926,11 +8973,12 @@ consists of `"`, zero or more characters not including `"`, and a final `"`. An [open tag](@) consists of a `<` character, a [tag name], -zero or more [attributes], optional [whitespace], an optional `/` -character, and a `>` character. +zero or more [attributes], optional spaces, tabs, and up to one line ending, +an optional `/` character, and a `>` character. A [closing tag](@) consists of the string `</`, a -[tag name], optional [whitespace], and the character `>`. +[tag name], optional spaces, tabs, and up to one line ending, and the character +`>`. An [HTML comment](@) consists of `<!--` + *text* + `-->`, where *text* does not start with `>` or `->`, does not end with `-`, @@ -8942,10 +8990,8 @@ consists of the string `<?`, a string of characters not including the string `?>`, and the string `?>`. -A [declaration](@) consists of the -string `<!`, a name consisting of one or more uppercase ASCII letters, -[whitespace], a string of characters not including the -character `>`, and the character `>`. +A [declaration](@) consists of the string `<!`, an ASCII letter, zero or more +characters not including the character `>`, and the character `>`. A [CDATA section](@) consists of the string `<![CDATA[`, a string of characters not including the string @@ -8973,7 +9019,7 @@ Empty elements: ```````````````````````````````` -[Whitespace] is allowed: +Whitespace is allowed: ```````````````````````````````` example <a /><b2 @@ -9031,7 +9077,7 @@ Illegal attribute values: ```````````````````````````````` -Illegal [whitespace]: +Illegal whitespace: ```````````````````````````````` example < a>< @@ -9046,7 +9092,7 @@ bim!bop /></p> ```````````````````````````````` -Missing [whitespace]: +Missing whitespace: ```````````````````````````````` example <a href='bar'title=title> @@ -9158,7 +9204,7 @@ foo <a href="\*"> ## Hard line breaks -A line break (not in a code span or HTML tag) that is preceded +A line ending (not in a code span or HTML tag) that is preceded by two or more spaces and does not occur at the end of a block is parsed as a [hard line break](@) (rendered in HTML as a `<br />` tag): @@ -9173,7 +9219,7 @@ baz</p> For a more visible alternative, a backslash before the -[line ending] may be used instead of two spaces: +[line ending] may be used instead of two or more spaces: ```````````````````````````````` example foo\ @@ -9215,7 +9261,7 @@ bar</p> ```````````````````````````````` -Line breaks can occur inside emphasis, links, and other constructs +Hard line breaks can occur inside emphasis, links, and other constructs that allow inline content: ```````````````````````````````` example @@ -9236,13 +9282,13 @@ bar</em></p> ```````````````````````````````` -Line breaks do not occur inside code spans +Hard line breaks do not occur inside code spans ```````````````````````````````` example -`code +`code span` . -<p><code>code span</code></p> +<p><code>code span</code></p> ```````````````````````````````` @@ -9308,9 +9354,9 @@ foo ## Soft line breaks -A regular line break (not in a code span or HTML tag) that is not +A regular line ending (not in a code span or HTML tag) that is not preceded by two or more spaces or a backslash is parsed as a -[softbreak](@). (A softbreak may be rendered in HTML either as a +[softbreak](@). (A soft line break may be rendered in HTML either as a [line ending] or as a space. The result will be the same in browsers. In the examples here, a [line ending] will be used.) @@ -9336,7 +9382,7 @@ baz</p> A conforming parser may render a soft line break in HTML either as a -line break or as a space. +line ending or as a space. A renderer may also provide an option to render soft line breaks as hard line breaks. @@ -9444,7 +9490,7 @@ blocks. But we cannot close unmatched blocks yet, because we may have a blocks, we look for new block starts (e.g. `>` for a block quote). If we encounter a new block start, we close any blocks unmatched in step 1 before creating the new block as a child of the last -matched block. +matched container block. 3. Finally, we look at the remainder of the line (after block markers like `>`, list markers, and indentation have been consumed). @@ -9660,8 +9706,9 @@ just above `stack_bottom` (or the first element if `stack_bottom` is NULL). We keep track of the `openers_bottom` for each delimiter -type (`*`, `_`) and each length of the closing delimiter run -(modulo 3). Initialize this to `stack_bottom`. +type (`*`, `_`), indexed to the length of the closing delimiter run +(modulo 3) and to whether the closing delimiter can also be an +opener. Initialize this to `stack_bottom`. Then we repeat the following until we run out of potential closers: @@ -9707,4 +9754,3 @@ closers: After we're done, we remove all delimiters above `stack_bottom` from the delimiter stack. - From ac4b3e7777160578c8353012c19a96ba8dbdad75 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 28 Jun 2021 11:23:28 +1000 Subject: [PATCH 503/815] Treat textarea like script, pre and style (spec 0.30) --- .../main/java/org/commonmark/internal/HtmlBlockParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java index fa1befa2a..d62e0f3d5 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -14,8 +14,8 @@ public class HtmlBlockParser extends AbstractBlockParser { private static final Pattern[][] BLOCK_PATTERNS = new Pattern[][]{ {null, null}, // not used (no type 0) { - Pattern.compile("^<(?:script|pre|style)(?:\\s|>|$)", Pattern.CASE_INSENSITIVE), - Pattern.compile("</(?:script|pre|style)>", Pattern.CASE_INSENSITIVE) + Pattern.compile("^<(?:script|pre|style|textarea)(?:\\s|>|$)", Pattern.CASE_INSENSITIVE), + Pattern.compile("</(?:script|pre|style|textarea)>", Pattern.CASE_INSENSITIVE) }, { Pattern.compile("^<!--"), From 2f126d33c589a77a181c0714dbca1fbafcb97a2b Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 29 Jun 2021 15:11:00 +1000 Subject: [PATCH 504/815] Fix case folding for link reference labels special cases (spec 0.30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old implementation wasn't handling "ẞ" (uppercase sharp S) correctly. See https://stackoverflow.com/a/68160923/305973 Also preserve the original label in the LinkReferenceDefinition node. --- .../internal/LinkReferenceDefinitionParser.java | 5 +---- .../java/org/commonmark/internal/util/Escaping.java | 11 +++++++++-- .../internal/LinkReferenceDefinitionParserTest.java | 4 ++-- .../test/LinkReferenceDefinitionNodeTest.java | 10 ++++++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java index 3d8e9ab70..2bceb7549 100644 --- a/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/LinkReferenceDefinitionParser.java @@ -26,7 +26,6 @@ public class LinkReferenceDefinitionParser { private final List<SourceSpan> sourceSpans = new ArrayList<>(); private StringBuilder label; - private String normalizedLabel; private String destination; private char titleDelimiter; private StringBuilder title; @@ -143,7 +142,6 @@ private boolean label(Scanner scanner) { return false; } - this.normalizedLabel = normalizedLabel; state = State.DESTINATION; scanner.whitespace(); @@ -252,14 +250,13 @@ private void finishReference() { String d = Escaping.unescapeString(destination); String t = title != null ? Escaping.unescapeString(title.toString()) : null; - LinkReferenceDefinition definition = new LinkReferenceDefinition(normalizedLabel, d, t); + LinkReferenceDefinition definition = new LinkReferenceDefinition(label.toString(), d, t); definition.setSourceSpans(sourceSpans); sourceSpans.clear(); definitions.add(definition); label = null; referenceValid = false; - normalizedLabel = null; destination = null; title = null; } 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 2b34f6190..ade64d933 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Escaping.java @@ -113,8 +113,15 @@ public static String percentEncodeUrl(String s) { public static String normalizeLabelContent(String input) { String trimmed = input.trim(); - String lowercase = trimmed.toLowerCase(Locale.ROOT); - return WHITESPACE.matcher(lowercase).replaceAll(" "); + + // This is necessary to correctly case fold "ẞ" to "SS": + // "ẞ".toLowerCase(Locale.ROOT) -> "ß" + // "ß".toUpperCase(Locale.ROOT) -> "SS" + // Note that doing upper first (or only upper without lower) wouldn't work because: + // "ẞ".toUpperCase(Locale.ROOT) -> "ẞ" + String caseFolded = trimmed.toLowerCase(Locale.ROOT).toUpperCase(Locale.ROOT); + + return WHITESPACE.matcher(caseFolded).replaceAll(" "); } private static String replaceAll(Pattern p, String s, Replacer replacer) { diff --git a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java index d9a67b6fa..b4f57739b 100644 --- a/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java +++ b/commonmark/src/test/java/org/commonmark/internal/LinkReferenceDefinitionParserTest.java @@ -58,7 +58,7 @@ public void testLabelMultiline() { assertEquals(State.DESTINATION, parser.getState()); parse("/url"); assertEquals(State.START_TITLE, parser.getState()); - assertDef(parser.getDefinitions().get(0), "two lines", "/url", null); + assertDef(parser.getDefinitions().get(0), "two\nlines", "/url", null); } @Test @@ -69,7 +69,7 @@ public void testLabelStartsWithNewline() { assertEquals(State.DESTINATION, parser.getState()); parse("/url"); assertEquals(State.START_TITLE, parser.getState()); - assertDef(parser.getDefinitions().get(0), "weird", "/url", null); + assertDef(parser.getDefinitions().get(0), "\nweird", "/url", null); } @Test diff --git a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java index 37b3d5dcd..bf7bde6ec 100644 --- a/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java +++ b/commonmark/src/test/java/org/commonmark/test/LinkReferenceDefinitionNodeTest.java @@ -105,6 +105,16 @@ public void testDefinitionInListItem2() { assertThat(item2.getFirstChild(), instanceOf(Paragraph.class)); } + @Test + public void testDefinitionLabelCaseIsPreserved() { + Node document = parse("This is a paragraph with a [foo] link.\n\n[fOo]: /url 'title'"); + List<Node> nodes = Nodes.getChildren(document); + + assertThat(nodes.size(), is(2)); + assertThat(nodes.get(0), instanceOf(Paragraph.class)); + assertDef(nodes.get(1), "fOo"); + } + private static Node parse(String input) { Parser parser = Parser.builder().build(); return parser.parse(input); From 4861cfdcb05b5181028e45145a73557742a5e6dd Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 29 Jun 2021 15:35:53 +1000 Subject: [PATCH 505/815] Don't let type 7 HTML blocks interrupt lazy paragraphs either They were already special cased to not interrupt normal paragraphs. This fixes handling of lazy paragraphs, see https://github.com/commonmark/commonmark.js/issues/213 Add updated regression files too. --- .../src/main/resources/cmark-regression.txt | 39 +++++++++++- .../resources/commonmark.js-regression.txt | 61 ++++++++++++++++++- .../commonmark/internal/HtmlBlockParser.java | 6 +- .../commonmark/internal/ListItemParser.java | 1 + .../org/commonmark/test/RegressionTest.java | 2 + .../org/commonmark/test/SpecialInputTest.java | 28 +++++++++ 6 files changed, 132 insertions(+), 5 deletions(-) diff --git a/commonmark-test-util/src/main/resources/cmark-regression.txt b/commonmark-test-util/src/main/resources/cmark-regression.txt index 62b1e7efe..5f1dc5e24 100644 --- a/commonmark-test-util/src/main/resources/cmark-regression.txt +++ b/commonmark-test-util/src/main/resources/cmark-regression.txt @@ -4,7 +4,8 @@ Issue #113: EOL character weirdness on Windows (Important: first line ends with CR + CR + LF) ```````````````````````````````` example -line1 +line1 + line2 . <p>line1</p> @@ -154,3 +155,39 @@ Issue #289. . <p>[a](<b) c></p> ```````````````````````````````` + +Issue #334 - UTF-8 BOM + +```````````````````````````````` example +# Hi +. +<h1>Hi</h1> +```````````````````````````````` + +Issue commonmark.js#213 - type 7 blocks can't interrupt +paragraph + +```````````````````````````````` example +- <script> +- some text +some other text +</script> +. +<ul> +<li> +<script> +</li> +<li>some text +some other text +</script></li> +</ul> +```````````````````````````````` + +Issue #383 - emphasis parsing. + +```````````````````````````````` example +*****Hello*world**** +. +<p>**<em><strong>Hello<em>world</em></strong></em></p> +```````````````````````````````` + diff --git a/commonmark-test-util/src/main/resources/commonmark.js-regression.txt b/commonmark-test-util/src/main/resources/commonmark.js-regression.txt index ec5143eff..16a0e8c35 100644 --- a/commonmark-test-util/src/main/resources/commonmark.js-regression.txt +++ b/commonmark-test-util/src/main/resources/commonmark.js-regression.txt @@ -80,7 +80,7 @@ Issue jgm/CommonMark#468 - backslash at end of link definition <p>[]: test</p> ```````````````````````````````` -Issue jgm/commonmark.js#121 - punctuation set different +Issue commonmark/commonmark.js#121 - punctuation set different ```````````````````````````````` example ^_test_ @@ -122,7 +122,15 @@ Double-encoding. ```````````````````````````````` example [XSS](javascript&colon;alert%28'XSS'%29) . -<p><a href="javascript&colon;alert('XSS')">XSS</a></p> +<p><a href="javascript&colon;alert%28'XSS'%29">XSS</a></p> +```````````````````````````````` + +PR #179 + +```````````````````````````````` example +[link](https://www.example.com/home/%25batty) +. +<p><a href="https://www.example.com/home/%25batty">link</a></p> ```````````````````````````````` Issue commonamrk#517 - script, pre, style close tag without @@ -158,4 +166,53 @@ text text</p> ```````````````````````````````` +Issue #196. + +```````````````````````````````` example +a <? +?> +. +<p>a <? +?></p> +```````````````````````````````` + +Issue #211 + +```````````````````````````````` example +[\ +foo]: /uri + +[\ +foo] +. +<p><a href="/uri"><br /> +foo</a></p> +```````````````````````````````` + +Issue #213 - type 7 blocks can't interrupt +paragraph + +```````````````````````````````` example +- <script> +- some text +some other text +</script> +. +<ul> +<li> +<script> +</li> +<li>some text +some other text +</script></li> +</ul> +```````````````````````````````` + +Issue cmark/#383 - emphasis parsing. + +```````````````````````````````` example +*****Hello*world**** +. +<p>**<em><strong>Hello<em>world</em></strong></em></p> +```````````````````````````````` diff --git a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java index d62e0f3d5..0e2050567 100644 --- a/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/HtmlBlockParser.java @@ -112,8 +112,10 @@ public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockPar if (state.getIndent() < 4 && line.charAt(nextNonSpace) == '<') { for (int blockType = 1; blockType <= 7; blockType++) { - // Type 7 can not interrupt a paragraph - if (blockType == 7 && matchedBlockParser.getMatchedBlockParser().getBlock() instanceof Paragraph) { + // Type 7 can not interrupt a paragraph (not even a lazy one) + if (blockType == 7 && ( + matchedBlockParser.getMatchedBlockParser().getBlock() instanceof Paragraph || + state.getActiveBlockParser().canHaveLazyContinuationLines())) { continue; } Pattern opener = BLOCK_PATTERNS[blockType][0]; diff --git a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java index 96b086dab..6f03770b3 100644 --- a/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/ListItemParser.java @@ -66,6 +66,7 @@ public BlockContinue tryContinue(ParserState state) { if (state.getIndent() >= contentIndent) { return BlockContinue.atColumn(state.getColumn() + contentIndent); } else { + // Note: We'll hit this case for lazy continuation lines, they will get added later. return BlockContinue.none(); } } diff --git a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java index c4a0d3be5..94b3a7439 100644 --- a/commonmark/src/test/java/org/commonmark/test/RegressionTest.java +++ b/commonmark/src/test/java/org/commonmark/test/RegressionTest.java @@ -64,6 +64,8 @@ private static Map<String, String> getOverriddenExamples() { // The only difference is that we don't change `%28` and `%29` to `(` and `)` (percent encoding is preserved) m.put("[XSS](javascript&colon;alert%28'XSS'%29)\n", "<p><a href=\"javascript&colon;alert%28'XSS'%29\">XSS</a></p>\n"); + // Callers should handle BOMs + m.put("\uFEFF# Hi\n", "<p>\uFEFF# Hi</p>\n"); return m; } diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 002b146a9..8eba1dfe3 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -183,4 +183,32 @@ public void unicodePunctuationEmphasis() { // Note that currently the reference implementation doesn't implement this correctly (resulting in no <em>). assertRendering("foo\uD809\uDC70_(bar)_", "<p>foo\uD809\uDC70<em>(bar)</em></p>\n"); } + + @Test + public void htmlBlockInterruptingList() { + assertRendering("- <script>\n" + + "- some text\n" + + "some other text\n" + + "</script>\n", "<ul>\n" + + "<li>\n" + + "<script>\n" + + "</li>\n" + + "<li>some text\n" + + "some other text\n" + + "</script></li>\n" + + "</ul>\n"); + + assertRendering("- <script>\n" + + "- some text\n" + + "some other text\n" + + "\n" + + "</script>\n", "<ul>\n" + + "<li>\n" + + "<script>\n" + + "</li>\n" + + "<li>some text\n" + + "some other text</li>\n" + + "</ul>\n" + + "</script>\n"); + } } From 4ca7a5981a4cb57a5376120d1fa325e312bad81c Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 29 Jun 2021 15:57:01 +1000 Subject: [PATCH 506/815] Allow lowercase ASCII in HTML declaration (spec 0.30) See https://github.com/commonmark/commonmark-spec/issues/620 --- .../commonmark/internal/inline/HtmlInlineParser.java | 10 ++++------ .../java/org/commonmark/test/HtmlInlineParserTest.java | 3 +++ 2 files changed, 7 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 ba992e2fd..605901c22 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -24,8 +24,6 @@ public class HtmlInlineParser implements InlineContentParser { .c('"').c('\'').c('=').c('<').c('>').c('`') .build(); - private static final AsciiMatcher declaration = AsciiMatcher.builder().range('A', 'Z').build(); - @Override public ParsedInline tryParse(InlineParserState inlineParserState) { Scanner scanner = inlineParserState.scanner(); @@ -58,7 +56,7 @@ public ParsedInline tryParse(InlineParserState inlineParserState) { if (tryCdata(scanner)) { return htmlInline(start, scanner); } - } else if (declaration.matches(c)) { + } else if (asciiLetter.matches(c)) { if (tryDeclaration(scanner)) { return htmlInline(start, scanner); } @@ -187,9 +185,9 @@ private static boolean tryCdata(Scanner scanner) { } private static boolean tryDeclaration(Scanner scanner) { - // spec: A declaration consists of the string <!, a name consisting of one or more uppercase ASCII letters, - // whitespace, a string of characters not including the character >, and the character >. - scanner.match(declaration); + // spec: A declaration consists of the string <!, an ASCII letter, zero or more characters not including + // the character >, and the character >. + scanner.match(asciiLetter); if (scanner.whitespace() <= 0) { return false; } diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java index 0a406778b..0172ca430 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java @@ -23,5 +23,8 @@ public void declaration() { assertRendering("inline <!FOO>", "<p>inline <!FOO></p>\n"); assertRendering("inline <!FOO >", "<p>inline <!FOO ></p>\n"); assertRendering("inline <!FOO 'bar'>", "<p>inline <!FOO 'bar'></p>\n"); + + // Lowercase + assertRendering("inline <!foo bar>", "<p>inline <!foo bar></p>\n"); } } From 89ec4e2f7c7da6cba6daaada06697178a94c0ce5 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 30 Jun 2021 13:55:47 +1000 Subject: [PATCH 507/815] Prepare for version 0.18.0 Set the version using `mvn versions:set -DnewVersion=0.18.0-SNAPSHOT`. --- CHANGELOG.md | 14 ++++++++++++++ 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 +++++++++++----------- 13 files changed, 36 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18a4adbdb..bdace42c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ 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.18.0] - 2021-06-30 +### Changed +- Update to CommonMark spec 0.30: + - Add `textarea` to list of literal HTML block tags. + Like `script`, `style`, and `pre`, `textarea` blocks can contain + blank lines without the contents being interpreted as commonmark. + - Fix case folding for link reference labels in some cases + (e.g. `ẞ` and `SS` should match) + - Allow lowercase ASCII in HTML declaration + - Don't let type 7 HTML blocks interrupt lazy paragraphs either +- Preserve the original case for the label of `LinkReferenceDefinition`. + Before, we used to store the normalized version (lowercase, collapsed whitespace). + ## [0.17.2] - 2021-05-14 ### Changed - Pass original instead of normalized label to `InlineParserContext` for lookup (#204). @@ -332,6 +345,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.18.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.2...commonmark-parent-0.18.0 [0.17.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.1...commonmark-parent-0.17.2 [0.17.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.0...commonmark-parent-0.17.1 [0.17.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.16.1...commonmark-parent-0.17.0 diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 6e37671d1..8fcf2e60c 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 632d6b658..79263e772 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 4b5972d14..cdcd58adb 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 1efdf1a36..6ce619a6a 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 7668cfa34..e73d99e69 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 20e5ea657..35b0efd6c 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 9f0cb417d..d3cc20576 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 699b7fc47..9b21607f9 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 9426113a6..671980ad2 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 2f1786617..3ff80e776 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index ea3736c01..061affe05 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 6eb07e941..a88a47914 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.17.3-SNAPSHOT</version> + <version>0.18.0-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> From d92a9f9b7fd8d713b8f296be6c6ed82abfe01985 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 1 Jul 2021 01:00:43 +0000 Subject: [PATCH 508/815] [maven-release-plugin] prepare release commonmark-parent-0.18.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 8fcf2e60c..7cacb0604 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 79263e772..ca79840e4 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index cdcd58adb..f7988bfd8 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 6ce619a6a..129253cf0 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index e73d99e69..729f53d22 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 35b0efd6c..e096c5764 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index d3cc20576..58f8292cb 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 9b21607f9..00811e1d5 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 671980ad2..01c0dbf27 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 3ff80e776..53c27919a 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 061affe05..0b7fa2761 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index a88a47914..42f5546f1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.18.0-SNAPSHOT</version> + <version>0.18.0</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.18.0</tag> </scm> <distributionManagement> From cc1cf2d7832630f08cb6fabb1840ca4f376a4798 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 1 Jul 2021 01:00:48 +0000 Subject: [PATCH 509/815] [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 7cacb0604..800099472 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index ca79840e4..8c46091e6 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index f7988bfd8..a60428067 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 129253cf0..0aabeb5cc 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 729f53d22..6e8a0135e 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index e096c5764..90f9f221c 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 58f8292cb..6d4586b0c 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 00811e1d5..1cf4fc381 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 01c0dbf27..64a5e8d37 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 53c27919a..ee6c4304f 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 0b7fa2761..02d092eaf 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 42f5546f1..ae1067e6d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.18.0</version> + <version>0.18.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.18.0</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From 726d36994386a7fd5205157fc554e82f1f4dfa9e Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 1 Jul 2021 11:27:59 +1000 Subject: [PATCH 510/815] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d94bd0ee6..41fa61cb1 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.17.2</version> + <version>0.18.0</version> </dependency> ``` @@ -233,7 +233,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.16.1</version> + <version>0.18.0</version> </dependency> ``` From cb692b91658248fd09a6289206d79c76405ed584 Mon Sep 17 00:00:00 2001 From: Brent Plump <bplump@atlassian.com> Date: Sat, 4 Sep 2021 15:47:26 +1000 Subject: [PATCH 511/815] Fix tables with leading/trailing header pipes and trailing spaces --- .../gfm/tables/internal/TableBlockParser.java | 14 ++++-- .../commonmark/ext/gfm/tables/TablesTest.java | 43 +++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java b/commonmark-ext-gfm-tables/src/main/java/org/commonmark/ext/gfm/tables/internal/TableBlockParser.java index a8eedb7a6..a203164d3 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 @@ -122,14 +122,22 @@ private TableCell parseCell(SourceLine cell, int column, InlineParser inlinePars private static List<SourceLine> split(SourceLine line) { CharSequence row = line.getContent(); int nonSpace = Parsing.skipSpaceTab(row, 0, row.length()); - int cellStart = row.charAt(nonSpace) == '|' ? nonSpace + 1 : nonSpace; + 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 + 1); + cellEnd = nonSpaceEnd + 1; + } List<SourceLine> cells = new ArrayList<>(); StringBuilder sb = new StringBuilder(); - for (int i = cellStart; i < row.length(); i++) { + for (int i = cellStart; i < cellEnd; i++) { char c = row.charAt(i); switch (c) { case '\\': - if (i + 1 < row.length() && row.charAt(i + 1) == '|') { + if (i + 1 < cellEnd && row.charAt(i + 1) == '|') { // Pipe is special for table parsing. An escaped pipe doesn't result in a new cell, but is // passed down to inline parsing as an unescaped pipe. Note that that applies even for the `\|` // in an input like `\\|` - in other words, table parsing doesn't support escaping backslashes. 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 2ec36cf16..4d5153213 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 @@ -235,6 +235,49 @@ public void pipesOnOutside() { "</table>\n"); } + @Test + public void pipesOnOutsideWhitespaceAfterHeader() { + assertRendering("|Abc|Def| \n|---|---|\n|1|2|", "<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n"); + } + + @Test + public void pipesOnOutsideZeroLengthHeaders() { + // This is literally what someone has done IRL - it helped to expose + // an issue with parsing the last header cell correctly + assertRendering("||center header||\n" + + "-|-------------|-\n" + + "1| 2 |3", + "<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th></th>\n" + + "<th>center header</th>\n" + + "<th></th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "<td>3</td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n"); + } + @Test public void inlineElements() { assertRendering("*Abc*|Def\n---|---\n1|2", "<table>\n" + From 59a9f4f01bbb240750e839a01c12d852893f004f Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 6 Sep 2021 10:52:26 +1000 Subject: [PATCH 512/815] Changelog for 0.18.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdace42c0..c7eec9b50 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. +## [0.18.1] - 2021-09-06 +### Fixed +- Fix tables with leading/trailing header pipes and trailing spaces (#244). + This was a regression in 0.16.1 which is now fixed. + ## [0.18.0] - 2021-06-30 ### Changed - Update to CommonMark spec 0.30: @@ -345,6 +350,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.18.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.0...commonmark-parent-0.18.1 [0.18.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.2...commonmark-parent-0.18.0 [0.17.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.1...commonmark-parent-0.17.2 [0.17.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.0...commonmark-parent-0.17.1 From 607bc35f0686f14f4e9592b1662da36719e9a808 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 6 Sep 2021 15:51:48 +1000 Subject: [PATCH 513/815] Don't release just yet --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7eec9b50..d566ccabb 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. -## [0.18.1] - 2021-09-06 +## Unreleased ### Fixed - Fix tables with leading/trailing header pipes and trailing spaces (#244). This was a regression in 0.16.1 which is now fixed. From 82c200d3e3f9394c7f786e01d2dbe9947f690154 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 29 Nov 2021 12:13:12 +1100 Subject: [PATCH 514/815] Prepare 0.18.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d566ccabb..9cbefdf92 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.18.1] - 2021-11-29 ### Fixed - Fix tables with leading/trailing header pipes and trailing spaces (#244). This was a regression in 0.16.1 which is now fixed. From 957688926be166bdacdee1bbc40fcf7ebc5b263d Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Mon, 29 Nov 2021 01:16:26 +0000 Subject: [PATCH 515/815] [maven-release-plugin] prepare release commonmark-parent-0.18.1 --- 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 800099472..46876e56e 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 8c46091e6..7d0b9df1e 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index a60428067..3108fb2ef 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 0aabeb5cc..c7db00fcb 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 6e8a0135e..be1f2cb2c 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 90f9f221c..b17d3be1f 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 6d4586b0c..acab5d870 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 1cf4fc381..59bb1e2e7 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 64a5e8d37..0881a000a 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index ee6c4304f..4718bccd3 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 02d092eaf..eb237fd07 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index ae1067e6d..30859341b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.18.1-SNAPSHOT</version> + <version>0.18.1</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.18.1</tag> </scm> <distributionManagement> From 3e108fc8d2f2aacbdcbafabaf4131b8fc3495d1c Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Mon, 29 Nov 2021 01:16:27 +0000 Subject: [PATCH 516/815] [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 46876e56e..44f727a84 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 7d0b9df1e..c2064d794 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 3108fb2ef..ca6c0c51f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index c7db00fcb..317f662f0 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index be1f2cb2c..6f7149e4a 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index b17d3be1f..09dcf5413 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index acab5d870..8ea6ca42f 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 59bb1e2e7..06f16c7c6 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 0881a000a..ea0a33a95 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 4718bccd3..f851aa5f0 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index eb237fd07..529d99126 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 30859341b..72dff7d7d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.18.1</version> + <version>0.18.2-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.18.1</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From 889903540b330b595e6fe5eadab80f93307d22f7 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 29 Nov 2021 13:19:58 +1100 Subject: [PATCH 517/815] Bump version in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 41fa61cb1..b94758f40 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.0</version> + <version>0.18.1</version> </dependency> ``` @@ -233,7 +233,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.0</version> + <version>0.18.1</version> </dependency> ``` From 38461d43bf0dd4788768f2753fee4f0abaac62c1 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 29 Nov 2021 13:34:54 +1100 Subject: [PATCH 518/815] Test against Java 17 --- .github/workflows/ci.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19ad24bb0..381d7c73f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [1.8, 11] + java: [1.8, 11, 17] steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/README.md b/README.md index b94758f40..2bb15b16e 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ 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 and Java 9. It should work on Java 7 +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 [commonmark-android-test](commonmark-android-test) directory. From b78607d6b7b2313ed1a48db1e7168608d123117a Mon Sep 17 00:00:00 2001 From: Egor Andreevich <github@egorand.dev> Date: Fri, 21 Jan 2022 16:47:51 -0500 Subject: [PATCH 519/815] Change license URL to "https" Fixes #250 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 72dff7d7d..4cf263e54 100644 --- a/pom.xml +++ b/pom.xml @@ -264,7 +264,7 @@ <licenses> <license> <name>BSD 2-Clause License</name> - <url>http://opensource.org/licenses/BSD-2-Clause</url> + <url>https://opensource.org/licenses/BSD-2-Clause</url> <distribution>repo</distribution> </license> </licenses> From 500cc482c30079a95efa920d5fecda4b3dab96bf Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 24 Feb 2022 11:37:16 +1100 Subject: [PATCH 520/815] Add license to artifacts (fixes #253) Also remove end year from range - apparently it's not necessary, and it wasn't up to date anyway. --- LICENSE.txt | 2 +- .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ .../src/main/resources/META-INF/LICENSE.txt | 23 +++++++++++++++++++ 12 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 commonmark-ext-autolink/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-ext-gfm-strikethrough/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-ext-gfm-tables/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-ext-heading-anchor/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-ext-image-attributes/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-ext-ins/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-ext-task-list-items/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-ext-yaml-front-matter/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-integration-test/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark-test-util/src/main/resources/META-INF/LICENSE.txt create mode 100644 commonmark/src/main/resources/META-INF/LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt index 1e011418e..b09e367ce 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2015-2016, Atlassian Pty Ltd +Copyright (c) 2015, Atlassian Pty Ltd All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/commonmark-ext-autolink/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-autolink/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-autolink/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-gfm-strikethrough/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-gfm-strikethrough/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/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-gfm-tables/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-gfm-tables/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-gfm-tables/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-heading-anchor/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-heading-anchor/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-heading-anchor/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-image-attributes/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-image-attributes/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-image-attributes/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-ins/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-ins/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-ins/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-task-list-items/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-task-list-items/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-task-list-items/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-yaml-front-matter/src/main/resources/META-INF/LICENSE.txt b/commonmark-ext-yaml-front-matter/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-ext-yaml-front-matter/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-integration-test/src/main/resources/META-INF/LICENSE.txt b/commonmark-integration-test/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-integration-test/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-test-util/src/main/resources/META-INF/LICENSE.txt b/commonmark-test-util/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark-test-util/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/src/main/resources/META-INF/LICENSE.txt b/commonmark/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000..b09e367ce --- /dev/null +++ b/commonmark/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. From adae79c1fc6b78d1edd792d27bc8b4a297154c4f Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 24 Feb 2022 11:59:41 +1100 Subject: [PATCH 521/815] Prepare for 0.18.2 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cbefdf92..bc3b7a332 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. +## [0.18.2] - 2022-02-24 +### Changed +- Test against Java 17 +- Bundle LICENSE.txt with artifacts (in addition to Maven metadata) + ## [0.18.1] - 2021-11-29 ### Fixed - Fix tables with leading/trailing header pipes and trailing spaces (#244). @@ -350,6 +355,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.18.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.1...commonmark-parent-0.18.2 [0.18.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.0...commonmark-parent-0.18.1 [0.18.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.2...commonmark-parent-0.18.0 [0.17.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.1...commonmark-parent-0.17.2 From 32f4f8992cb09316eda71e2d9da3f2ac325d4507 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 24 Feb 2022 01:02:08 +0000 Subject: [PATCH 522/815] [maven-release-plugin] prepare release commonmark-parent-0.18.2 --- 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 44f727a84..e0286dbe5 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index c2064d794..70580fda4 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index ca6c0c51f..e87f59e8e 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 317f662f0..7a1689064 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 6f7149e4a..00a202d4d 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 09dcf5413..e9c27c1c9 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 8ea6ca42f..3c9714d83 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 06f16c7c6..b112b1e23 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index ea0a33a95..9bff723d5 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index f851aa5f0..95921bf8e 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 529d99126..afd8c50c7 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 4cf263e54..795175e18 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.18.2-SNAPSHOT</version> + <version>0.18.2</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.18.2</tag> </scm> <distributionManagement> From 77784467b62c48fb291ef65d905fede1c02f89b2 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 24 Feb 2022 01:02:10 +0000 Subject: [PATCH 523/815] [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 e0286dbe5..d0e2fde4b 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 70580fda4..0aa109c76 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index e87f59e8e..2126aefde 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 7a1689064..93ebe809a 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 00a202d4d..ab45201cf 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index e9c27c1c9..5865a1797 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 3c9714d83..1f4085b08 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index b112b1e23..42ac850a6 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 9bff723d5..f0fe2d50b 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 95921bf8e..0c7f33024 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index afd8c50c7..0f6696b7a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 795175e18..e227723c5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.18.2</version> + <version>0.18.3-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.18.2</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From 4785decaba4bc38d8666353464af23ee74e1bf21 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 1 Jun 2022 14:43:15 +1000 Subject: [PATCH 524/815] YAML front matter: Limited support for single/double quoted strings Extend our manual parser to handle string values that use single or double quotes. The support is limited and doesn't implement the full YAML spec (e.g. no support for escapes like `\n`). At some point we should either depend on a real YAML parser or expose the raw YAML source so that users can parse it themselves. Fixes #260. --- CHANGELOG.md | 5 + .../internal/YamlFrontMatterBlockParser.java | 28 ++++- .../ext/front/matter/YamlFrontMatterTest.java | 115 ++++++++---------- 3 files changed, 83 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3b7a332..9761edb2f 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 +### Added +- YAML front matter extension: Limited support for single and double + quoted string values (#260) + ## [0.18.2] - 2022-02-24 ### Changed - Test against Java 17 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 e9d0901b6..2010b4f71 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 @@ -61,10 +61,11 @@ public BlockContinue tryContinue(ParserState parserState) { inLiteral = false; currentKey = matcher.group(1); currentValues = new ArrayList<>(); - if ("|".equals(matcher.group(2))) { + String value = matcher.group(2); + if ("|".equals(value)) { inLiteral = true; - } else if (!"".equals(matcher.group(2))) { - currentValues.add(matcher.group(2)); + } else if (!"".equals(value)) { + currentValues.add(parseString(value)); } return BlockContinue.atIndex(parserState.getIndex()); @@ -81,7 +82,8 @@ public BlockContinue tryContinue(ParserState parserState) { } else { matcher = REGEX_METADATA_LIST.matcher(line); if (matcher.matches()) { - currentValues.add(matcher.group(1)); + String value = matcher.group(1); + currentValues.add(parseString(value)); } } @@ -93,6 +95,24 @@ public BlockContinue tryContinue(ParserState parserState) { public void parseInlines(InlineParser inlineParser) { } + private static String parseString(String s) { + // Limited parsing of https://yaml.org/spec/1.2.2/#73-flow-scalar-styles + // We assume input is well-formed and otherwise treat it as a plain string. In a real + // parser, e.g. `'foo` would be invalid because it's missing a trailing `'`. + if (s.startsWith("'") && s.endsWith("'")) { + String inner = s.substring(1, s.length() - 1); + return inner.replace("''", "'"); + } else if (s.startsWith("\"") && s.endsWith("\"")) { + String inner = s.substring(1, s.length() - 1); + // Only support escaped `\` and `"`, nothing else. + return inner + .replace("\\\"", "\"") + .replace("\\\\", "\\"); + } else { + return s; + } + } + public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { 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 251b1c15f..a5f203b97 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 @@ -30,11 +30,7 @@ public void simpleValue() { "\ngreat"; final String rendered = "<p>great</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertEquals(1, data.size()); assertEquals("hello", data.keySet().iterator().next()); @@ -53,11 +49,7 @@ public void emptyValue() { "\ngreat"; final String rendered = "<p>great</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertEquals(1, data.size()); assertEquals("key", data.keySet().iterator().next()); @@ -77,11 +69,7 @@ public void listValues() { "\ngreat"; final String rendered = "<p>great</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertEquals(1, data.size()); assertTrue(data.containsKey("list")); @@ -103,11 +91,7 @@ public void literalValue1() { "\ngreat"; final String rendered = "<p>great</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertEquals(1, data.size()); assertTrue(data.containsKey("literal")); @@ -127,11 +111,7 @@ public void literalValue2() { "\ngreat"; final String rendered = "<p>great</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertEquals(1, data.size()); assertTrue(data.containsKey("literal")); @@ -156,11 +136,7 @@ public void complexValues() { "\ngreat"; final String rendered = "<p>great</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertEquals(3, data.size()); @@ -187,11 +163,7 @@ public void empty() { "test"; final String rendered = "<p>test</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertTrue(data.isEmpty()); @@ -207,11 +179,7 @@ public void yamlInParagraph() { "\n---"; final String rendered = "<h1>hello</h1>\n<h2>hello markdown world!</h2>\n<h2>hello: world</h2>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertTrue(data.isEmpty()); @@ -226,11 +194,7 @@ public void yamlOnSecondLine() { "\n---"; final String rendered = "<p>hello</p>\n<hr />\n<h2>hello: world</h2>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertTrue(data.isEmpty()); @@ -243,11 +207,7 @@ public void nonMatchedStartTag() { "test"; final String rendered = "<hr />\n<p>test</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertTrue(data.isEmpty()); @@ -261,11 +221,7 @@ public void inList() { "test"; final String rendered = "<ul>\n<li>\n<hr />\n<hr />\n</li>\n</ul>\n<p>test</p>\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertTrue(data.isEmpty()); @@ -310,7 +266,7 @@ public void nodesCanBeModified() { assertTrue(data.containsKey("see")); assertEquals(Collections.singletonList("you"), data.get("see")); } - + @Test public void dotInKeys() { final String input = "---" + @@ -318,11 +274,7 @@ public void dotInKeys() { "\n---" + "\n"; - YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); - Node document = PARSER.parse(input); - document.accept(visitor); - - Map<String, List<String>> data = visitor.getData(); + Map<String, List<String>> data = getFrontMatter(input); assertEquals(1, data.size()); assertEquals("ms.author", data.keySet().iterator().next()); @@ -330,11 +282,52 @@ public void dotInKeys() { assertEquals("author", data.get("ms.author").get(0)); } + @Test + public void singleQuotedLiterals() { + final String input = "---" + + "\nstring: 'It''s me'" + + "\nlist:" + + "\n - 'I''m here'" + + "\n---" + + "\n"; + + Map<String, List<String>> 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)); + } + + @Test + public void doubleQuotedLiteral() { + final String input = "---" + + "\nstring: \"backslash: \\\\ quote: \\\"\"" + + "\nlist:" + + "\n - \"hey\"" + + "\n---" + + "\n"; + + Map<String, List<String>> data = getFrontMatter(input); + + assertEquals(2, data.size()); + assertEquals("backslash: \\ quote: \"", data.get("string").get(0)); + assertEquals("hey", data.get("list").get(0)); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); } + private Map<String, List<String>> getFrontMatter(String input) { + YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor(); + Node document = PARSER.parse(input); + document.accept(visitor); + + Map<String, List<String>> data = visitor.getData(); + return data; + } + // Custom node for tests private static class TestNode extends CustomNode { } From 9f31262540daab3cf78106643b613599c0957fc6 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 2 Jun 2022 15:35:54 +1000 Subject: [PATCH 525/815] Check arg of enabledBlockTypes when building parser instead of NPE later Came up in #258. --- CHANGELOG.md | 2 ++ .../java/org/commonmark/internal/DocumentParser.java | 8 ++++++++ .../src/main/java/org/commonmark/parser/Parser.java | 1 + .../src/test/java/org/commonmark/test/ParserTest.java | 11 +++++++---- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9761edb2f..a6524b713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ with the exception that 0.x versions can break between minor versions. ### Added - YAML front matter extension: Limited support for single and double quoted string values (#260) +### Changed +- Check argument of `enabledBlockTypes` when building parser instead of NPEing later ## [0.18.2] - 2022-02-24 ### Changed diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index ed8ae7412..086c3dbc0 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -98,6 +98,14 @@ public static List<BlockParserFactory> calculateBlockParserFactories(List<BlockP return list; } + public static void checkEnabledBlockTypes(Set<Class<? extends Block>> enabledBlockTypes) { + for (Class<? extends Block> enabledBlockType : enabledBlockTypes) { + if (!NODES_TO_CORE_FACTORIES.containsKey(enabledBlockType)) { + throw new IllegalArgumentException("Can't enable block type " + enabledBlockType + ", possible options are: " + NODES_TO_CORE_FACTORIES.keySet()); + } + } + } + /** * The main parsing function. Returns a parsed document AST. */ diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index 63cebb2eb..89cdd584c 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -176,6 +176,7 @@ public Builder enabledBlockTypes(Set<Class<? extends Block>> enabledBlockTypes) if (enabledBlockTypes == null) { throw new NullPointerException("enabledBlockTypes must not be null"); } + DocumentParser.checkEnabledBlockTypes(enabledBlockTypes); this.enabledBlockTypes = enabledBlockTypes; return this; } diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index e486750aa..9a91aa40a 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -11,10 +11,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -75,6 +72,12 @@ public void enabledBlockTypes() { assertThat(document.getFirstChild(), not(instanceOf(Heading.class))); } + @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(); + } + @Test public void indentation() { String given = " - 1 space\n - 3 spaces\n - 5 spaces\n\t - tab + space"; From a5d5a03724804ef203a6e081c832d5435cec9b76 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 2 Jun 2022 15:51:22 +1000 Subject: [PATCH 526/815] Prepare for version 0.19.0 Set the version using `mvn versions:set -DnewVersion=0.19.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 d0e2fde4b..bc77e11ea 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 0aa109c76..27671a2b5 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 2126aefde..ffe6a52fd 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 93ebe809a..ed92654b2 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index ab45201cf..53a1cda32 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 5865a1797..9c88bbb16 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 1f4085b08..4023c6ce2 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 42ac850a6..118c53411 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index f0fe2d50b..01909f351 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 0c7f33024..22ad7098f 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 0f6696b7a..5c380c059 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index e227723c5..ceb351dd2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.18.3-SNAPSHOT</version> + <version>0.19.0-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> From ca53e5a41f699e0760b7202c215346229d777f80 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 2 Jun 2022 15:52:30 +1000 Subject: [PATCH 527/815] CHANGELOG for 0.19.0 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6524b713..cdfb65043 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.19.0] - 2022-06-02 ### Added - YAML front matter extension: Limited support for single and double quoted string values (#260) @@ -362,6 +362,7 @@ Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.19.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.2...commonmark-parent-0.19.0 [0.18.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.1...commonmark-parent-0.18.2 [0.18.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.0...commonmark-parent-0.18.1 [0.18.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.17.2...commonmark-parent-0.18.0 From 5103c0f22cb027c8fdda2a672bc5120428379d9a Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 2 Jun 2022 05:56:31 +0000 Subject: [PATCH 528/815] [maven-release-plugin] prepare release commonmark-parent-0.19.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 bc77e11ea..6085d83d9 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 27671a2b5..fa7fede1e 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index ffe6a52fd..98b5a6ebb 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index ed92654b2..0e885c2ad 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 53a1cda32..f21c14f98 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 9c88bbb16..5f5b57d40 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 4023c6ce2..427f00a89 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 118c53411..616c425c0 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 01909f351..ebb01fc45 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 22ad7098f..f167c79af 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 5c380c059..0eebfc42d 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index ceb351dd2..d75b53dd0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.19.0-SNAPSHOT</version> + <version>0.19.0</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.19.0</tag> </scm> <distributionManagement> From f64be6ee6fab6b6b0363cbe92135d8b3b3f80924 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 2 Jun 2022 05:56:32 +0000 Subject: [PATCH 529/815] [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 6085d83d9..0daa389b4 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index fa7fede1e..bd97e0704 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 98b5a6ebb..52e75ac90 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 0e885c2ad..c1785404a 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index f21c14f98..adf29d35b 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 5f5b57d40..279eef082 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 427f00a89..72f9dfa71 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 616c425c0..a88af6a46 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index ebb01fc45..4bbd14f58 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index f167c79af..543d701c1 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 0eebfc42d..36c3c2459 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index d75b53dd0..10328eef9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.19.0</version> + <version>0.19.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.19.0</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From bce67594596200dc4f7dc482effd1290db6575e2 Mon Sep 17 00:00:00 2001 From: Taeik Lim <sibera21@gmail.com> Date: Wed, 10 Aug 2022 22:40:16 +0900 Subject: [PATCH 530/815] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2bb15b16e..3e7546be2 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.18.1</version> + <version>0.19.0</version> </dependency> ``` @@ -233,7 +233,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.18.1</version> + <version>0.19.0</version> </dependency> ``` From 7584f008900752cd9a539d2ac0d216b848eb2a4b Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Tue, 18 Oct 2022 18:29:18 +1100 Subject: [PATCH 531/815] Fix GFM table parser to handle dangling pipe correctly (fixes #255) --- CHANGELOG.md | 6 ++ .../gfm/tables/internal/TableBlockParser.java | 18 ++++- .../commonmark/ext/gfm/tables/TablesTest.java | 73 +++++++++++++++---- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdfb65043..9a8f1b2ca 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 +- A single pipe (optional whitespace) now ends a table instead of crashing or + being treated as an empty row, for consistency with GitHub (#255). + ## [0.19.0] - 2022-06-02 ### Added - YAML front matter extension: Limited support for single and double @@ -362,6 +367,7 @@ 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.19.0...HEAD [0.19.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.2...commonmark-parent-0.19.0 [0.18.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.1...commonmark-parent-0.18.2 [0.18.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.0...commonmark-parent-0.18.1 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 a203164d3..b7cea14db 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 @@ -19,6 +19,8 @@ public class TableBlockParser extends AbstractBlockParser { private final List<SourceLine> rowLines = new ArrayList<>(); private final List<TableCell.Alignment> columns; + private boolean canHaveLazyContinuationLines = true; + private TableBlockParser(List<TableCell.Alignment> columns, SourceLine headerLine) { this.columns = columns; this.rowLines.add(headerLine); @@ -26,7 +28,7 @@ private TableBlockParser(List<TableCell.Alignment> columns, SourceLine headerLin @Override public boolean canHaveLazyContinuationLines() { - return true; + return canHaveLazyContinuationLines; } @Override @@ -36,7 +38,17 @@ public Block getBlock() { @Override public BlockContinue tryContinue(ParserState state) { - if (Parsing.find('|', state.getLine().getContent(), 0) != -1) { + CharSequence content = state.getLine().getContent(); + int pipe = Parsing.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()) { + // We also don't want the pipe to be added via lazy continuation. + canHaveLazyContinuationLines = false; + return BlockContinue.none(); + } + } return BlockContinue.atIndex(state.getIndex()); } else { return BlockContinue.none(); @@ -128,7 +140,7 @@ private static List<SourceLine> split(SourceLine line) { // 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 + 1); + int nonSpaceEnd = Parsing.skipSpaceTabBackwards(row, row.length() - 1, cellStart); cellEnd = nonSpaceEnd + 1; } List<SourceLine> cells = new ArrayList<>(); 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 4d5153213..bef3b8b6c 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 @@ -261,21 +261,21 @@ public void pipesOnOutsideZeroLengthHeaders() { "-|-------------|-\n" + "1| 2 |3", "<table>\n" + - "<thead>\n" + - "<tr>\n" + - "<th></th>\n" + - "<th>center header</th>\n" + - "<th></th>\n" + - "</tr>\n" + - "</thead>\n" + - "<tbody>\n" + - "<tr>\n" + - "<td>1</td>\n" + - "<td>2</td>\n" + - "<td>3</td>\n" + - "</tr>\n" + - "</tbody>\n" + - "</table>\n"); + "<thead>\n" + + "<tr>\n" + + "<th></th>\n" + + "<th>center header</th>\n" + + "<th></th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "<td>3</td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n"); } @Test @@ -664,6 +664,47 @@ public void issue142() { "</table>\n"); } + @Test + public void danglingPipe() { + assertRendering("Abc|Def\n" + + "---|---\n" + + "1|2\n" + + "|", "<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n" + + "<p>|</p>\n"); + + assertRendering("Abc|Def\n" + + "---|---\n" + + "1|2\n" + + " | ", "<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th>Abc</th>\n" + + "<th>Def</th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n" + + "<p>|</p>\n"); + } + @Test public void attributeProviderIsApplied() { AttributeProviderFactory factory = new AttributeProviderFactory() { @@ -718,7 +759,7 @@ public void sourceSpans() { TableBlock block = (TableBlock) document.getFirstChild(); assertEquals(Arrays.asList(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)), + SourceSpan.of(2, 0, 4), SourceSpan.of(3, 0, 8), SourceSpan.of(4, 0, 3)), block.getSourceSpans()); TableHead head = (TableHead) block.getFirstChild(); From afb31c80deb5fc0fbf15b62ef2093ad12e165c41 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 19 Oct 2022 15:38:23 +1100 Subject: [PATCH 532/815] Move GFM spec out of tables ext, test strikethrough as well All tests passing already (there's not many). --- .../strikethrough/StrikethroughSpecTest.java | 46 +++++++++++++++++++ .../ext/gfm/tables/TablesSpecTest.java | 9 +--- .../commonmark/testutil/TestResources.java | 4 ++ .../testutil/example/ExampleReader.java | 11 +++++ .../src/main}/resources/gfm-spec.txt | 3 +- etc/update-spec.sh | 2 +- 6 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java rename {commonmark-ext-gfm-tables/src/test => commonmark-test-util/src/main}/resources/gfm-spec.txt (99%) 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 new file mode 100644 index 000000000..4b907cf41 --- /dev/null +++ b/commonmark-ext-gfm-strikethrough/src/test/java/org/commonmark/ext/gfm/strikethrough/StrikethroughSpecTest.java @@ -0,0 +1,46 @@ +package org.commonmark.ext.gfm.strikethrough; + +import org.commonmark.Extension; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.testutil.RenderingTestCase; +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 java.util.Collections; +import java.util.List; +import java.util.Set; + +@RunWith(Parameterized.class) +public class StrikethroughSpecTest extends RenderingTestCase { + + private static final Set<Extension> EXTENSIONS = Collections.singleton(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; + + public StrikethroughSpecTest(Example example) { + this.example = example; + } + + @Parameters(name = "{0}") + public static List<Object[]> data() { + return ExampleReader.readExampleObjects(TestResources.getGfmSpec(), "strikethrough"); + } + + @Test + public void testHtmlRendering() { + assertRendering(example.getSource(), example.getHtml()); + } + + @Override + protected String render(String source) { + return RENDERER.render(PARSER.parse(source)); + } +} 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 12c806e32..00fc61401 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 @@ -32,14 +32,7 @@ public TablesSpecTest(Example example) { @Parameters(name = "{0}") public static List<Object[]> data() { - List<Example> examples = ExampleReader.readExamples(TestResources.class.getResource("/gfm-spec.txt")); - List<Object[]> data = new ArrayList<>(); - for (Example example : examples) { - if (example.getInfo().contains("table")) { - data.add(new Object[]{example}); - } - } - return data; + return ExampleReader.readExampleObjects(TestResources.getGfmSpec(), "table"); } @Test 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 8f6f5c071..ac4d3f1c2 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 @@ -14,6 +14,10 @@ public static URL getSpec() { return TestResources.class.getResource("/spec.txt"); } + public static URL getGfmSpec() { + return TestResources.class.getResource("/gfm-spec.txt"); + } + public static List<URL> getRegressions() { return Arrays.asList( TestResources.class.getResource("/cmark-regression.txt"), 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 0972a227f..e93d2e07c 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 @@ -42,6 +42,17 @@ public static List<Example> readExamples(URL url) { } } + public static List<Object[]> readExampleObjects(URL url, String info) { + List<Example> examples = readExamples(url); + List<Object[]> data = new ArrayList<>(); + for (Example example : examples) { + if (example.getInfo().contains(info)) { + data.add(new Object[]{example}); + } + } + return data; + } + public static List<String> readExampleSources(URL url) { List<Example> examples = ExampleReader.readExamples(url); List<String> result = new ArrayList<>(); diff --git a/commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt b/commonmark-test-util/src/main/resources/gfm-spec.txt similarity index 99% rename from commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt rename to commonmark-test-util/src/main/resources/gfm-spec.txt index 582131d70..170276156 100644 --- a/commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt +++ b/commonmark-test-util/src/main/resources/gfm-spec.txt @@ -2077,7 +2077,7 @@ followed by one of the strings (case-insensitive) `address`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`, `html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, -`section`, `source`, `summary`, `table`, `tbody`, `td`, +`section`, `summary`, `table`, `tbody`, `td`, `tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed by [whitespace], the end of the line, the string `>`, or the string `/>`.\ @@ -10224,4 +10224,3 @@ closers: After we're done, we remove all delimiters above `stack_bottom` from the delimiter stack. - diff --git a/etc/update-spec.sh b/etc/update-spec.sh index d4930438e..0f9def8b3 100755 --- a/etc/update-spec.sh +++ b/etc/update-spec.sh @@ -7,7 +7,7 @@ fi version=$1 curl -L "https://raw.githubusercontent.com/commonmark/commonmark-spec/$version/spec.txt" -o commonmark-test-util/src/main/resources/spec.txt -curl -L "https://raw.githubusercontent.com/github/cmark-gfm/master/test/spec.txt" -o commonmark-ext-gfm-tables/src/test/resources/gfm-spec.txt +curl -L "https://raw.githubusercontent.com/github/cmark-gfm/master/test/spec.txt" -o commonmark-test-util/src/main/resources/gfm-spec.txt echo "Check cmark and commonmark.js regression.txt:" echo "https://github.com/commonmark/cmark/blob/master/test/regression.txt" From 4ad649fcbff254a59ac511c5607c317770e25274 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 19 Oct 2022 15:43:01 +1100 Subject: [PATCH 533/815] Adjust GFM strikethrough parsing (fixes #267) We used to accept 2 or more tildes for strikethrough. But GitHub actually accepts a single tilde as well, but rejects more than 2 tildes. This brings our implementation more in line with GitHub's. --- CHANGELOG.md | 8 ++++++-- .../StrikethroughDelimiterProcessor.java | 12 ++++++------ .../gfm/strikethrough/StrikethroughTest.java | 19 +++++++++---------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8f1b2ca..a95d92e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,12 @@ with the exception that 0.x versions can break between minor versions. ## [Unreleased] ### Fixed -- A single pipe (optional whitespace) now ends a table instead of crashing or - being treated as an empty row, for consistency with GitHub (#255). +- GitHub tables: A single pipe (optional whitespace) now ends a table + instead of crashing or being treated as an empty row, for consistency + with GitHub (#255). +- GitHub strikethrough: A single tilde now also works, and more than two + tildes are not accepted anymore. This brings us in line with what + GitHub actually does, which is a bit underspecified (#267) ## [0.19.0] - 2022-06-02 ### Added 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 7d54eedf2..a26953d28 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 @@ -22,13 +22,13 @@ public char getClosingCharacter() { @Override public int getMinLength() { - return 2; + return 1; } @Override public int process(DelimiterRun openingRun, DelimiterRun closingRun) { - if (openingRun.length() >= 2 && closingRun.length() >= 2) { - // Use exactly two delimiters even if we have more, and don't care about internal openers/closers. + if (openingRun.length() == closingRun.length() && openingRun.length() <= 2) { + // GitHub only accepts either one or two delimiters, but not a mix or more than that. Text opener = openingRun.getOpener(); @@ -36,19 +36,19 @@ public int process(DelimiterRun openingRun, DelimiterRun closingRun) { Node strikethrough = new Strikethrough(); SourceSpans sourceSpans = new SourceSpans(); - sourceSpans.addAllFrom(openingRun.getOpeners(2)); + sourceSpans.addAllFrom(openingRun.getOpeners(openingRun.length())); for (Node node : Nodes.between(opener, closingRun.getCloser())) { strikethrough.appendChild(node); sourceSpans.addAll(node.getSourceSpans()); } - sourceSpans.addAllFrom(closingRun.getClosers(2)); + sourceSpans.addAllFrom(closingRun.getClosers(closingRun.length())); strikethrough.setSourceSpans(sourceSpans.getSourceSpans()); opener.insertAfter(strikethrough); - return 2; + return openingRun.length(); } else { return 0; } 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 e2e3b95c4..76cf929e9 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 @@ -26,12 +26,12 @@ public class StrikethroughTest extends RenderingTestCase { .extensions(EXTENSIONS).build(); @Test - public void oneTildeIsNotEnough() { - assertRendering("~foo~", "<p>~foo~</p>\n"); + public void oneTildeIsEnough() { + assertRendering("~foo~", "<p><del>foo</del></p>\n"); } @Test - public void twoTildesYay() { + public void twoTildesWorksToo() { assertRendering("~~foo~~", "<p><del>foo</del></p>\n"); } @@ -48,23 +48,22 @@ public void unmatched() { @Test public void threeInnerThree() { - assertRendering("a ~~~foo~~~", "<p>a ~<del>foo</del>~</p>\n"); + assertRendering("a ~~~foo~~~", "<p>a ~~~foo~~~</p>\n"); } @Test public void twoInnerThree() { - assertRendering("~~foo~~~", "<p><del>foo</del>~</p>\n"); + assertRendering("~~foo~~~", "<p>~~foo~~~</p>\n"); } @Test public void tildesInside() { assertRendering("~~foo~bar~~", "<p><del>foo~bar</del></p>\n"); assertRendering("~~foo~~bar~~", "<p><del>foo</del>bar~~</p>\n"); - assertRendering("~~foo~~~bar~~", "<p><del>foo</del>~bar~~</p>\n"); - assertRendering("~~foo~~~~bar~~", "<p><del>foo</del><del>bar</del></p>\n"); - assertRendering("~~foo~~~~~bar~~", "<p><del>foo</del>~<del>bar</del></p>\n"); - assertRendering("~~foo~~~~~~bar~~", "<p><del>foo</del>~~<del>bar</del></p>\n"); - assertRendering("~~foo~~~~~~~bar~~", "<p><del>foo</del>~~~<del>bar</del></p>\n"); + assertRendering("~~foo~~~bar~~", "<p><del>foo~~~bar</del></p>\n"); + assertRendering("~~foo~~~~bar~~", "<p><del>foo~~~~bar</del></p>\n"); + assertRendering("~~foo~~~~~bar~~", "<p><del>foo~~~~~bar</del></p>\n"); + assertRendering("~~foo~~~~~~bar~~", "<p><del>foo~~~~~~bar</del></p>\n"); } @Test From 2e03eb57fe89898492721d87f1db906cc44410df Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Wed, 19 Oct 2022 22:42:10 +1100 Subject: [PATCH 534/815] Fix autolink extension to handle source spans correctly (fixes #209) It didn't set any source spans on the Link nodes before, and even worse, dropped source spans of the existing Text node (even if there were no links). --- CHANGELOG.md | 1 + .../internal/AutolinkPostProcessor.java | 50 +++++++++++----- .../commonmark/ext/autolink/AutolinkTest.java | 60 +++++++++++++++++++ 3 files changed, 96 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a95d92e5d..76d74b8b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ with the exception that 0.x versions can break between minor versions. - GitHub strikethrough: A single tilde now also works, and more than two tildes are not accepted anymore. This brings us in line with what GitHub actually does, which is a bit underspecified (#267) +- The autolink extension now handles source spans correctly (#209) ## [0.19.0] - 2022-06-02 ### Added 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 0f94d5902..e00692158 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,16 +1,13 @@ package org.commonmark.ext.autolink.internal; -import org.commonmark.node.AbstractVisitor; -import org.commonmark.node.Link; -import org.commonmark.node.Node; -import org.commonmark.node.Text; +import org.commonmark.node.*; import org.commonmark.parser.PostProcessor; import org.nibor.autolink.LinkExtractor; import org.nibor.autolink.LinkSpan; import org.nibor.autolink.LinkType; import org.nibor.autolink.Span; -import java.util.EnumSet; +import java.util.*; public class AutolinkPostProcessor implements PostProcessor { @@ -25,26 +22,49 @@ public Node process(Node node) { return node; } - private void linkify(Text textNode) { - String literal = textNode.getLiteral(); + private void linkify(Text originalTextNode) { + String literal = originalTextNode.getLiteral(); - Node lastNode = textNode; + Node lastNode = originalTextNode; + List<SourceSpan> sourceSpans = originalTextNode.getSourceSpans(); + SourceSpan sourceSpan = sourceSpans.size() == 1 ? sourceSpans.get(0) : null; - for (Span span : linkExtractor.extractSpans(literal)) { - String text = literal.substring(span.getBeginIndex(), span.getEndIndex()); + Iterator<Span> spans = linkExtractor.extractSpans(literal).iterator(); + while (spans.hasNext()) { + Span span = spans.next(); + + if (lastNode == originalTextNode && !spans.hasNext() && !(span instanceof LinkSpan)) { + // Didn't find any links, don't bother changing existing node. + return; + } + + Text textNode = createTextNode(literal, span, sourceSpan); if (span instanceof LinkSpan) { - String destination = getDestination((LinkSpan) span, text); - Text contentNode = new Text(text); + String destination = getDestination((LinkSpan) span, textNode.getLiteral()); + Link linkNode = new Link(destination, null); - linkNode.appendChild(contentNode); + linkNode.appendChild(textNode); + linkNode.setSourceSpans(textNode.getSourceSpans()); lastNode = insertNode(linkNode, lastNode); } else { - lastNode = insertNode(new Text(text), lastNode); + lastNode = insertNode(textNode, lastNode); } } // Original node no longer needed - textNode.unlink(); + originalTextNode.unlink(); + } + + private static Text createTextNode(String literal, Span span, SourceSpan sourceSpan) { + int beginIndex = span.getBeginIndex(); + int endIndex = span.getEndIndex(); + 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)); + } + return textNode; } private static String getDestination(LinkSpan linkSpan, String 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 ae586b6f0..2a9fd3b69 100644 --- a/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java +++ b/commonmark-ext-autolink/src/test/java/org/commonmark/ext/autolink/AutolinkTest.java @@ -1,14 +1,21 @@ package org.commonmark.ext.autolink; import org.commonmark.Extension; +import org.commonmark.node.*; +import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; +import java.util.Arrays; import java.util.Collections; 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<Extension> EXTENSIONS = Collections.singleton(AutolinkExtension.create()); @@ -53,6 +60,59 @@ public void dontLinkTextWithinLinks() { "<p><a href=\"http://example.com\">http://example.com</a></p>\n"); } + @Test + public void sourceSpans() { + Parser parser = Parser.builder() + .extensions(EXTENSIONS) + .includeSourceSpans(IncludeSourceSpans.BLOCKS_AND_INLINES) + .build(); + Node document = parser.parse("abc\n" + + "http://example.com/one\n" + + "def http://example.com/two\n" + + "ghi http://example.com/three jkl"); + + Paragraph paragraph = (Paragraph) document.getFirstChild(); + Text abc = (Text) paragraph.getFirstChild(); + assertEquals(Arrays.asList(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)), + 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)), + def.getSourceSpans()); + + Link two = (Link) def.getNext(); + assertEquals("http://example.com/two", two.getDestination()); + assertEquals(Arrays.asList(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)), + ghi.getSourceSpans()); + + Link three = (Link) ghi.getNext(); + assertEquals("http://example.com/three", three.getDestination()); + assertEquals(Arrays.asList(SourceSpan.of(3, 4, 24)), + three.getSourceSpans()); + + Text jkl = (Text) three.getNext(); + assertEquals(" jkl", jkl.getLiteral()); + assertEquals(Arrays.asList(SourceSpan.of(3, 28, 4)), + jkl.getSourceSpans()); + } + @Override protected String render(String source) { return RENDERER.render(PARSER.parse(source)); From a0196a57c476d7f2a1716dc8717e187ada20275a Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 20 Oct 2022 12:12:01 +1100 Subject: [PATCH 535/815] Prepare for version 0.20.0 Set the version using `mvn versions:set -DnewVersion=0.20.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 0daa389b4..dc6868e50 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index bd97e0704..8c82d2fa5 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 52e75ac90..f7970b13d 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index c1785404a..be8ac82f5 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index adf29d35b..88911eaad 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 279eef082..bccf5e14e 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 72f9dfa71..ae9f130e4 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index a88af6a46..9eab0f4e2 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 4bbd14f58..2ca29bac9 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 543d701c1..1b8e100a9 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 36c3c2459..b934fdcdd 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 10328eef9..b6214e7db 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.19.1-SNAPSHOT</version> + <version>0.20.0-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> From fcad995248e1b4c6e607656daaf2d6263270e9e7 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 20 Oct 2022 01:17:06 +0000 Subject: [PATCH 536/815] [maven-release-plugin] prepare release commonmark-parent-0.20.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 dc6868e50..0a986f7bc 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 8c82d2fa5..7f8fb9116 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index f7970b13d..54f53025f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index be8ac82f5..948e7db46 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 88911eaad..e6236eda9 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index bccf5e14e..5f601087a 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index ae9f130e4..f083ae8c7 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 9eab0f4e2..071e14afd 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 2ca29bac9..b98e26d9f 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 1b8e100a9..ee01b2b54 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index b934fdcdd..76e803e11 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index b6214e7db..f754d9099 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.20.0-SNAPSHOT</version> + <version>0.20.0</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.20.0</tag> </scm> <distributionManagement> From af9d026e5b374748b82f504db59740a8660bc9b8 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 20 Oct 2022 01:17:08 +0000 Subject: [PATCH 537/815] [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 0a986f7bc..43ebf31b1 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 7f8fb9116..3212541d0 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 54f53025f..e017ab05d 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 948e7db46..350fed676 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index e6236eda9..b18b9d5ab 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 5f601087a..edacbd87c 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index f083ae8c7..cff7f974c 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 071e14afd..8337da4f5 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index b98e26d9f..a208f4de6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index ee01b2b54..716d5243e 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 76e803e11..dc35608e4 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index f754d9099..265b5f2d4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.20.0</version> + <version>0.20.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.20.0</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From ba673a9782c06e1b21c1e489592c58f789115d48 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 20 Oct 2022 13:26:10 +1100 Subject: [PATCH 538/815] Changelog for 0.20.0, bump version --- CHANGELOG.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76d74b8b2..cf2ef7b40 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.20.0] - 2022-10-20 ### Fixed - GitHub tables: A single pipe (optional whitespace) now ends a table instead of crashing or being treated as an empty row, for consistency @@ -372,7 +372,7 @@ 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.19.0...HEAD +[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 [0.18.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.1...commonmark-parent-0.18.2 [0.18.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.0...commonmark-parent-0.18.1 diff --git a/README.md b/README.md index 3e7546be2..31a7780da 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Coordinates for core library (see all on [Maven Central]): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.19.0</version> + <version>0.20.0</version> </dependency> ``` @@ -233,7 +233,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.19.0</version> + <version>0.20.0</version> </dependency> ``` From 6db29901d6251c8d30c2db78187c48ed2eb6a0e4 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 24 Oct 2022 17:08:47 +1100 Subject: [PATCH 539/815] Add `requireTwoTildes` for `StrikethroughExtension` (fixes #271) With the previous version we adjusted the extension to also accept the single tilde syntax. But if you use another extension that uses the single tilde syntax, you will get a conflict, see issue. To avoid that, `StrikethroughExtension` can now be configured to require two tildes like before, see Javadoc. --- CHANGELOG.md | 10 +++- .../strikethrough/StrikethroughExtension.java | 60 +++++++++++++++++-- .../StrikethroughDelimiterProcessor.java | 12 +++- .../gfm/strikethrough/StrikethroughTest.java | 45 +++++++++++++- .../internal/StaggeredDelimiterProcessor.java | 2 +- .../test/DelimiterProcessorTest.java | 2 +- 6 files changed, 120 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf2ef7b40..49ebab528 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] +### Added +- GitHub strikethrough: With the previous version we adjusted the + extension to also accept the single tilde syntax. But if you use + another extension that uses the single tilde syntax, you will get a + conflict. To avoid that, `StrikethroughExtension` can now be + configured to require two tildes like before, see Javadoc. + ## [0.20.0] - 2022-10-20 ### Fixed - GitHub tables: A single pipe (optional whitespace) now ends a table @@ -371,7 +379,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.20.0...HEAD [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 [0.18.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.1...commonmark-parent-0.18.2 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 3d0839f11..7d277fd73 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 @@ -14,29 +14,57 @@ import org.commonmark.renderer.NodeRenderer; /** - * Extension for GFM strikethrough using ~~ (GitHub Flavored Markdown). + * Extension for GFM strikethrough using {@code ~} or {@code ~~} (GitHub Flavored Markdown). + * <p>Example input:</p> + * <pre>{@code ~foo~ or ~~bar~~}</pre> + * <p>Example output (HTML):</p> + * <pre>{@code <del>foo</del> or <del>bar</del>}</pre> * <p> - * Create it with {@link #create()} and then configure it on the builders + * Create the extension with {@link #create()} and then add it to the parser and renderer builders * ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)}, * {@link HtmlRenderer.Builder#extensions(Iterable)}). * </p> * <p> * The parsed strikethrough text regions are turned into {@link Strikethrough} nodes. * </p> + * <p> + * If you have another extension that only uses a single tilde ({@code ~}) syntax, you will have to configure this + * {@link StrikethroughExtension} to only accept the double tilde syntax, like this: + * <pre> + * {@code + * StrikethroughExtension.builder().requireTwoTildes(true).build(); + * } + * </pre> + * If you don't do that, there's a conflict between the two extensions and you will get an + * {@link IllegalArgumentException} when constructing the parser. + * </p> */ public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, TextContentRenderer.TextContentRendererExtension { - private StrikethroughExtension() { + private final boolean requireTwoTildes; + + private StrikethroughExtension(Builder builder) { + this.requireTwoTildes = builder.requireTwoTildes; } + /** + * @return the extension with default options + */ public static Extension create() { - return new StrikethroughExtension(); + 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.customDelimiterProcessor(new StrikethroughDelimiterProcessor()); + parserBuilder.customDelimiterProcessor(new StrikethroughDelimiterProcessor(requireTwoTildes)); } @Override @@ -58,4 +86,26 @@ public NodeRenderer create(TextContentNodeRendererContext 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. + * @return {@code this} + */ + public Builder requireTwoTildes(boolean requireTwoTildes) { + this.requireTwoTildes = requireTwoTildes; + return this; + } + + /** + * @return a configured extension + */ + public Extension build() { + return new StrikethroughExtension(this); + } + } } 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 a26953d28..3dedff1b9 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 @@ -10,6 +10,16 @@ public class StrikethroughDelimiterProcessor implements DelimiterProcessor { + private final boolean requireTwoTildes; + + public StrikethroughDelimiterProcessor() { + this(false); + } + + public StrikethroughDelimiterProcessor(boolean requireTwoTildes) { + this.requireTwoTildes = requireTwoTildes; + } + @Override public char getOpeningCharacter() { return '~'; @@ -22,7 +32,7 @@ public char getClosingCharacter() { @Override public int getMinLength() { - return 1; + return requireTwoTildes ? 2 : 1; } @Override 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 76cf929e9..d8a754c72 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 @@ -4,22 +4,25 @@ import org.commonmark.node.Node; import org.commonmark.node.Paragraph; import org.commonmark.node.SourceSpan; +import org.commonmark.node.Text; import org.commonmark.parser.IncludeSourceSpans; import org.commonmark.parser.Parser; +import org.commonmark.parser.delimiter.DelimiterProcessor; +import org.commonmark.parser.delimiter.DelimiterRun; import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.testutil.RenderingTestCase; import org.junit.Test; import java.util.Arrays; -import java.util.Collections; 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<Extension> EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); + private static final Set<Extension> EXTENSIONS = singleton(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() @@ -92,6 +95,19 @@ public void textContentRenderer() { assertEquals("/foo/", CONTENT_RENDERER.render(document)); } + @Test + public void requireTwoTildesOption() { + Parser parser = Parser.builder() + .extensions(singleton(StrikethroughExtension.builder() + .requireTwoTildes(true) + .build())) + .customDelimiterProcessor(new SubscriptDelimiterProcessor()) + .build(); + + Node document = parser.parse("~foo~ ~~bar~~"); + assertEquals("(sub)foo(/sub) /bar/", CONTENT_RENDERER.render(document)); + } + @Test public void sourceSpans() { Parser parser = Parser.builder() @@ -110,4 +126,29 @@ public void sourceSpans() { protected String render(String source) { return HTML_RENDERER.render(PARSER.parse(source)); } + + private static class SubscriptDelimiterProcessor implements DelimiterProcessor { + + @Override + public char getOpeningCharacter() { + return '~'; + } + + @Override + public char getClosingCharacter() { + return '~'; + } + + @Override + public int getMinLength() { + return 1; + } + + @Override + public int process(DelimiterRun openingRun, DelimiterRun closingRun) { + openingRun.getOpener().insertAfter(new Text("(sub)")); + closingRun.getCloser().insertBefore(new Text("(/sub)")); + return 1; + } + } } diff --git a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java index 0fe8065bb..2836e346a 100644 --- a/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java +++ b/commonmark/src/main/java/org/commonmark/internal/StaggeredDelimiterProcessor.java @@ -51,7 +51,7 @@ void add(DelimiterProcessor dp) { added = true; break; } else if (len == pLen) { - throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len); + throw new IllegalArgumentException("Cannot add two delimiter processors for char '" + delim + "' and minimum length " + len + "; conflicting processors: " + p + ", " + dp); } } if (!added) { diff --git a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java index 173d711d3..d2e20a64f 100644 --- a/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/DelimiterProcessorTest.java @@ -60,7 +60,7 @@ public void multipleDelimitersWithDifferentLengths() { } @Test(expected = IllegalArgumentException.class) - public void multipleDelimitersWithSameLength() { + public void multipleDelimitersWithSameLengthConflict() { Parser.builder() .customDelimiterProcessor(new OneDelimiterProcessor()) .customDelimiterProcessor(new OneDelimiterProcessor()) From de1c59eccb38e6e2359a023dcffbe6507e0380a4 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Mon, 24 Oct 2022 17:13:01 +1100 Subject: [PATCH 540/815] Tweak Javadoc (meh) --- .../ext/gfm/strikethrough/StrikethroughExtension.java | 2 ++ 1 file changed, 2 insertions(+) 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 7d277fd73..4f0228a1c 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 @@ -30,11 +30,13 @@ * <p> * If you have another extension that only uses a single tilde ({@code ~}) syntax, you will have to configure this * {@link StrikethroughExtension} to only accept the double tilde syntax, like this: + * </p> * <pre> * {@code * StrikethroughExtension.builder().requireTwoTildes(true).build(); * } * </pre> + * <p> * If you don't do that, there's a conflict between the two extensions and you will get an * {@link IllegalArgumentException} when constructing the parser. * </p> From 77c58536ab780a8ce582136206672f480e72c57a Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 3 Nov 2022 13:59:33 +1100 Subject: [PATCH 541/815] Add DingusApp for quickly testing out renderings Named Dingus because of https://spec.commonmark.org/dingus/ --- .../java/org/commonmark/ui/DingusApp.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java diff --git a/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java new file mode 100644 index 000000000..0e98386bb --- /dev/null +++ b/commonmark-integration-test/src/test/java/org/commonmark/ui/DingusApp.java @@ -0,0 +1,114 @@ +package org.commonmark.ui; + +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.commonmark.renderer.text.TextContentRenderer; + +import java.awt.*; +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** + * Simple UI to quickly test out different rendering of CommonMark inputs. + * Similar to <a href="https://spec.commonmark.org/dingus/">commonmark.js dingus</a>. + **/ +public class DingusApp { + + private final Parser parser = Parser.builder().build(); + private final TextContentRenderer textRenderer = TextContentRenderer.builder().build(); + private final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build(); + + private final JTabbedPane tabbedPane; + private final JEditorPane htmlVisualRendererOutput; + private final JTextArea htmlSourceRendererOutput; + private final JTextArea textRendererOutput; + + public static void main(String[] args) { + new DingusApp().run(); + } + + private DingusApp() { + tabbedPane = new JTabbedPane(); + + htmlVisualRendererOutput = new JEditorPane(); + htmlVisualRendererOutput.setEnabled(false); + htmlVisualRendererOutput.setContentType("text/html"); + + htmlSourceRendererOutput = new JTextArea(); + htmlSourceRendererOutput.setEnabled(false); + htmlSourceRendererOutput.setLineWrap(true); + htmlSourceRendererOutput.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); + + textRendererOutput = new JTextArea(); + textRendererOutput.setEnabled(false); + textRendererOutput.setLineWrap(true); + textRendererOutput.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); + } + + private void run() { + JFrame frame = new JFrame("commonmark-java dingus"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setMinimumSize(new Dimension(400, 300)); + frame.setSize(new Dimension(1200, 675)); + + final JTextArea input = new JTextArea(); + input.setBorder(BorderFactory.createTitledBorder("Input")); + input.setLineWrap(true); + input.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); + + input.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + updateOutput(input.getText()); + } + + @Override + public void removeUpdate(DocumentEvent e) { + updateOutput(input.getText()); + } + + @Override + public void changedUpdate(DocumentEvent e) { + } + }); + + tabbedPane.addTab("HTML rendered", htmlVisualRendererOutput); + tabbedPane.addTab("HTML source", htmlSourceRendererOutput); + tabbedPane.addTab("Plain text", textRendererOutput); + + tabbedPane.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + updateOutput(input.getText()); + } + }); + + input.setText("# Example\n" + + "Enter text *here* and see how it renders on the right.\n\n" + + "* Try\n* this\n\n" + + "```\nor this\n```"); + updateOutput(input.getText()); + + frame.setLayout(new GridLayout()); + frame.add(input); + frame.add(tabbedPane); + + frame.setVisible(true); + } + + private void updateOutput(String inputText) { + if (tabbedPane.getSelectedComponent() == htmlVisualRendererOutput) { + String rendered = htmlRenderer.render(parser.parse(inputText)); + htmlVisualRendererOutput.setText(rendered); + } else if (tabbedPane.getSelectedComponent() == htmlSourceRendererOutput) { + String rendered = htmlRenderer.render(parser.parse(inputText)); + htmlSourceRendererOutput.setText(rendered); + } else if (tabbedPane.getSelectedComponent() == textRendererOutput) { + String rendered = textRenderer.render(parser.parse(inputText)); + textRendererOutput.setText(rendered); + } + } +} From 8636cf7f398c8a80c595037f44efa84c6e86758e Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 3 Nov 2022 14:35:28 +1100 Subject: [PATCH 542/815] Mention DingusApp --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31a7780da..bcf552c5b 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ be followed. See the [spec.txt](commonmark-test-util/src/main/resources/spec.txt) file if you're wondering which version of the spec is currently implemented. Also check out the [CommonMark dingus] for getting familiar -with the syntax or trying out edge cases. +with the syntax or trying out edge cases. If you clone the repository, +you can also use the `DingusApp` class to try out things interactively. Usage From 6bf916e1fa00ae7be9a692c5821bc91f0eba8957 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 10 Nov 2022 21:05:10 +1100 Subject: [PATCH 543/815] Bump maven plugins --- pom.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 265b5f2d4..3cf35692e 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.8.1</version> + <version>3.10.1</version> <configuration> <source>7</source> <target>7</target> @@ -47,17 +47,17 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.2.0</version> + <version>3.3.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-install-plugin</artifactId> - <version>3.0.0-M1</version> + <version>3.0.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>3.2.0</version> + <version>3.4.1</version> <configuration> <excludePackageNames>*.internal,*.internal.*</excludePackageNames> <!-- The offline links make links from extensions to core work. --> @@ -74,7 +74,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>2.22.1</version> + <version>2.22.2</version> </plugin> </plugins> </pluginManagement> @@ -83,7 +83,7 @@ <plugin> <groupId>org.sonatype.plugins</groupId> <artifactId>nexus-staging-maven-plugin</artifactId> - <version>1.6.8</version> + <version>1.6.13</version> <extensions>true</extensions> <configuration> <serverId>ossrh</serverId> @@ -95,7 +95,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> - <version>3.0.0-M1</version> + <version>3.0.0-M7</version> <configuration> <autoVersionSubmodules>true</autoVersionSubmodules> <useReleaseProfile>false</useReleaseProfile> @@ -212,7 +212,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-gpg-plugin</artifactId> - <version>1.6</version> + <version>3.0.1</version> <executions> <execution> <id>sign-artifacts</id> @@ -239,7 +239,7 @@ <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> - <version>0.7.9</version> + <version>0.8.8</version> <configuration> <excludes> <!-- Classes from test-util --> From e146e4203324908de1b3797191b790d82b1ee679 Mon Sep 17 00:00:00 2001 From: Robin Stocker <rstocker@atlassian.com> Date: Thu, 17 Nov 2022 13:33:03 +1100 Subject: [PATCH 544/815] Prepare for version 0.21.0 Set the version using `mvn versions:set -DnewVersion=0.21.0-SNAPSHOT` --- CHANGELOG.md | 4 ++-- 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 +++++++++++----------- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49ebab528..f558a3601 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.21.0] - 2022-11-17 ### Added - GitHub strikethrough: With the previous version we adjusted the extension to also accept the single tilde syntax. But if you use @@ -379,7 +379,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.20.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 [0.18.2]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.18.1...commonmark-parent-0.18.2 diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 43ebf31b1..c95d71383 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 3212541d0..41e358b08 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index e017ab05d..940e491ee 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 350fed676..40c8c91a0 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index b18b9d5ab..58ff6af45 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index edacbd87c..475398615 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index cff7f974c..c56b00dbb 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 8337da4f5..57aa1fbd0 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index a208f4de6..0745fe2d9 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 716d5243e..a9700153e 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index dc35608e4..a4b3368e2 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 3cf35692e..1b8c1e82e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.20.1-SNAPSHOT</version> + <version>0.21.0-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> From db8469c78cdbec6a5cb0375e5fce4844d9ad57d7 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 17 Nov 2022 02:37:39 +0000 Subject: [PATCH 545/815] [maven-release-plugin] prepare release commonmark-parent-0.21.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 c95d71383..3558b2231 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 41e358b08..a1eb8f466 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 940e491ee..adbc14e94 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 40c8c91a0..33dfa985c 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 58ff6af45..307597da9 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 475398615..f9e095ea7 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index c56b00dbb..772088d4a 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 57aa1fbd0..9fd018c9a 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 0745fe2d9..2646e7456 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index a9700153e..6c43eb2ab 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index a4b3368e2..5b69ddcf0 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 1b8c1e82e..f44082810 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.21.0-SNAPSHOT</version> + <version>0.21.0</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.21.0</tag> </scm> <distributionManagement> From dfd95d08a547d91bf10a6dea3d030906a5f976b7 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Thu, 17 Nov 2022 02:37:40 +0000 Subject: [PATCH 546/815] [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 3558b2231..c82ecdae8 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index a1eb8f466..878a1e586 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index adbc14e94..c6448889f 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 33dfa985c..49bc4a032 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 307597da9..959b1406c 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index f9e095ea7..708532472 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 772088d4a..ec3ac1a1f 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 9fd018c9a..e5e120caa 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 2646e7456..da5146a15 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 6c43eb2ab..c61000412 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 5b69ddcf0..64c456192 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index f44082810..f5c7749ef 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -112,52 +112,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.21.0</version> + <version>0.21.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -282,7 +282,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.21.0</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From fbf87b8495d00723e8a62ba0824fd7df7fdf0ce5 Mon Sep 17 00:00:00 2001 From: Dan Wyand <dwyand@palantir.com> Date: Fri, 14 Jul 2023 14:34:44 -0400 Subject: [PATCH 547/815] 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<SourceLine> rowLines = new ArrayList<>(); - private final List<TableCell.Alignment> columns; + private final List<TableCellInfo> columns; private boolean canHaveLazyContinuationLines = true; - private TableBlockParser(List<TableCell.Alignment> columns, SourceLine headerLine) { + private TableBlockParser(List<TableCellInfo> 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<SourceLine> split(SourceLine line) { // -|- // |-|-| // --- | --- - private static List<TableCell.Alignment> parseSeparator(CharSequence s) { - List<TableCell.Alignment> columns = new ArrayList<>(); + private static List<TableCellInfo> parseSeparator(CharSequence s) { + List<TableCellInfo> 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<TableCell.Alignment> 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<TableCell.Alignment> 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<TableCell.Alignment> columns = parseSeparator(separatorLine.getContent()); + List<TableCellInfo> columns = parseSeparator(separatorLine.getContent()); if (columns != null && !columns.isEmpty()) { SourceLine paragraph = paragraphLines.get(0); List<SourceLine> 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<String, String> attribu "</table>\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<String, String> 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("<table>\n" + + "<thead>\n" + + "<tr>\n" + + "<th width=\"5em\">Abc</th>\n" + + "<th width=\"3em\">Def</th>\n" + + "</tr>\n" + + "</thead>\n" + + "<tbody>\n" + + "<tr>\n" + + "<td>1</td>\n" + + "<td>2</td>\n" + + "</tr>\n" + + "</tbody>\n" + + "</table>\n")); + } + @Test public void sourceSpans() { Parser parser = Parser.builder() From 654203286f8e6b1900a4e564613ed361e8a36449 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Tue, 9 Jan 2024 16:48:57 +1100 Subject: [PATCH 548/815] 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 <robin@nibor.org> Date: Tue, 9 Jan 2024 16:50:07 +1100 Subject: [PATCH 549/815] 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 14:36:09 +1100 Subject: [PATCH 550/815] 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<Object[]> 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 <robin@nibor.org> Date: Tue, 16 Jan 2024 22:30:52 +1100 Subject: [PATCH 551/815] 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). + * <p> + * Some examples and their marker indent: + * <pre>- Foo</pre> + * Marker indent: 0 + * <pre> - Foo</pre> + * Marker indent: 1 + * <pre> 1. Foo</pre> + * 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. + * <p> + * Some examples and their content indent: + * <pre>- Foo</pre> + * Content indent: 2 + * <pre> - Foo</pre> + * Content indent: 3 + * <pre> 1. Foo</pre> + * Content indent: 5 + * <p> + * 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<Node> 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> T find(Node parent, Class<T> 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 <robin@nibor.org> Date: Tue, 9 Jan 2024 16:53:44 +1100 Subject: [PATCH 552/815] 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<Class<? extends Node>> 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. + * <p> + * Note that it does not currently attempt to preserve the exact syntax of the original input Markdown (if any): + * <ul> + * <li>Headings are always output as ATX headings for simplicity</li> + * </ul> + */ +public class MarkdownRenderer implements Renderer { + + private final List<MarkdownNodeRendererFactory> 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<MarkdownNodeRendererFactory> 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. + * <p> + * 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<? extends Extension> 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 12:04:12 +1100 Subject: [PATCH 553/815] 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 15:43:49 +1100 Subject: [PATCH 554/815] 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: + * <ol> + * <li>Parses the source to an AST and then renders it back to Markdown</li> + * <li>Parses that to an AST and then renders it to HTML</li> + * <li>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</li> + * </ol> + */ +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<Example> 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 15:57:58 +1100 Subject: [PATCH 555/815] 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<Example> examples = ExampleReader.readExamples(TestResources.getSpec()); - int passed = 0; + List<Example> passes = new ArrayList<>(); + List<Example> 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<Example> examples) { + Map<String, Integer> 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<String, Integer> 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 16:21:44 +1100 Subject: [PATCH 556/815] 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 16:27:36 +1100 Subject: [PATCH 557/815] 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 17:37:45 +1100 Subject: [PATCH 558/815] 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): * <ul> * <li>Headings are always output as ATX headings for simplicity</li> + * <li>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</li> * </ul> */ 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](</my uri>)\n"); + assertRoundTrip("[a](<b)c>)\n"); + assertRoundTrip("[a](<b(c>)\n"); + assertRoundTrip("[a](<b\\>c>)\n"); + assertRoundTrip("[a](<b\\\\\\>c>)\n"); + assertRoundTrip("[a](/uri \"foo \\\" bar\")\n"); + } + + @Test + public void testImages() { + assertRoundTrip("![link](/uri)\n"); + assertRoundTrip("![link](/uri \"title\")\n"); + assertRoundTrip("![link](</my uri>)\n"); + assertRoundTrip("![a](<b)c>)\n"); + assertRoundTrip("![a](<b(c>)\n"); + assertRoundTrip("![a](<b\\>c>)\n"); + assertRoundTrip("![a](<b\\\\\\>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<Example> examples) { bySection.put(example.getSection(), count + 1); } for (Map.Entry<String, Integer> 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 17:44:11 +1100 Subject: [PATCH 559/815] 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("<div>test</div>\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("<del>*foo*</del>\n"); + } + @Test public void testHardLineBreaks() { assertRoundTrip("foo \nbar\n"); From e4ced9b071a32c2c14c614a0cbcd3c2ae4b4c9e6 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Wed, 10 Jan 2024 18:15:53 +1100 Subject: [PATCH 560/815] 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<String> 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 <robin@nibor.org> Date: Wed, 10 Jan 2024 21:17:00 +1100 Subject: [PATCH 561/815] 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("<div>test</div>\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 <robin@nibor.org> Date: Wed, 10 Jan 2024 21:29:03 +1100 Subject: [PATCH 562/815] 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("<div>test</div>\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 <robin@nibor.org> Date: Thu, 11 Jan 2024 16:08:42 +1100 Subject: [PATCH 563/815] 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 <robin@nibor.org> Date: Thu, 11 Jan 2024 16:24:17 +1100 Subject: [PATCH 564/815] 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 <robin@nibor.org> Date: Thu, 11 Jan 2024 16:44:00 +1100 Subject: [PATCH 565/815] 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 <robin@nibor.org> Date: Thu, 11 Jan 2024 16:48:32 +1100 Subject: [PATCH 566/815] 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 <robin@nibor.org> Date: Thu, 11 Jan 2024 17:03:45 +1100 Subject: [PATCH 567/815] 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 <robin@nibor.org> Date: Thu, 11 Jan 2024 17:13:28 +1100 Subject: [PATCH 568/815] 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<String> 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 <robin@nibor.org> Date: Wed, 17 Jan 2024 16:58:26 +1100 Subject: [PATCH 569/815] 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 <robin@nibor.org> Date: Wed, 17 Jan 2024 17:01:49 +1100 Subject: [PATCH 570/815] 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 <robin@nibor.org> Date: Wed, 17 Jan 2024 22:43:30 +1100 Subject: [PATCH 571/815] 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<String> 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. + * <p> + * 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 <robin@nibor.org> Date: Wed, 17 Jan 2024 22:54:06 +1100 Subject: [PATCH 572/815] 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 <robin@nibor.org> Date: Mon, 22 Jan 2024 22:09:49 +1100 Subject: [PATCH 573/815] 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<String> 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<String> 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<String> 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 <robin@nibor.org> Date: Mon, 22 Jan 2024 22:46:33 +1100 Subject: [PATCH 574/815] 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). + * <p> + * 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 @@ * <p> * Note that it does not currently attempt to preserve the exact syntax of the original input Markdown (if any): * <ul> - * <li>Headings are always output as ATX headings for simplicity</li> + * <li>Headings are output as ATX headings if possible (multi-line headings need Setext headings)</li> * <li>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</li> * </ul> 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 <robin@nibor.org> Date: Mon, 22 Jan 2024 23:02:59 +1100 Subject: [PATCH 575/815] 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 <robin@nibor.org> Date: Mon, 22 Jan 2024 23:10:31 +1100 Subject: [PATCH 576/815] 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<String> 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("<div>test</div>\n"); + assertRoundTrip("> <div>\n> test\n> </div>\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 <robin@nibor.org> Date: Tue, 23 Jan 2024 22:43:20 +1100 Subject: [PATCH 577/815] 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<String> 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 <robin@nibor.org> Date: Tue, 23 Jan 2024 22:45:29 +1100 Subject: [PATCH 578/815] 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 <robin@nibor.org> Date: Tue, 23 Jan 2024 23:35:48 +1100 Subject: [PATCH 579/815] 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<String> 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<String> 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 <robin@nibor.org> Date: Wed, 24 Jan 2024 14:53:33 +1100 Subject: [PATCH 580/815] 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<Example> examples) { From 37e507ab08912d3fd8e9753f86ed1dd7e84f9b69 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Wed, 24 Jan 2024 21:57:17 +1100 Subject: [PATCH 581/815] 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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 <robin@nibor.org> Date: Mon, 29 Jan 2024 17:30:36 +1100 Subject: [PATCH 582/815] 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<Class<? extends Node>> 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 <robin@nibor.org> Date: Mon, 29 Jan 2024 17:39:58 +1100 Subject: [PATCH 583/815] 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<String> 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<String> 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<String> 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<String> 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. + * <p> + * 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. - * <p> - * 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 <robin@nibor.org> Date: Tue, 6 Feb 2024 12:44:26 +1100 Subject: [PATCH 584/815] 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 <a href="https://github.github.com/gfm/#tables-extension-">Tables (extension) in GitHub Flavored Markdown Spec</a> */ 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<TableCell.Alignment> 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<Extension> 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|<span>Foo\\|bar</span>|\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<String> prefixes = new LinkedList<>(); + private final LinkedList<CharMatcher> 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](<b\\>c>)\n"); assertRoundTrip("[a](<b\\\\\\>c>)\n"); assertRoundTrip("[a](/uri \"foo \\\" bar\")\n"); + assertRoundTrip("[link](/uri \"tes\\\\\")\n"); + assertRoundTrip("[link](/url \"test \")\n"); + assertRoundTrip("[link](</url >)\n"); } @Test From d726f72916bcdcdc3b5fb8d49d97beb6aaf784c2 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Tue, 6 Feb 2024 12:52:43 +1100 Subject: [PATCH 585/815] 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<String> prefixes = new LinkedList<>(); + private final LinkedList<Boolean> tight = new LinkedList<>(); private final LinkedList<CharMatcher> 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. + * <p> + * 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. - * <p> - * 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 <robin@nibor.org> Date: Tue, 6 Feb 2024 12:58:30 +1100 Subject: [PATCH 586/815] 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 <robin@nibor.org> Date: Tue, 6 Feb 2024 22:04:50 +1100 Subject: [PATCH 587/815] 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<Extension> 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 <robin@nibor.org> Date: Tue, 6 Feb 2024 22:31:13 +1100 Subject: [PATCH 588/815] 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 @@ * </p> */ 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<Extension> 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 <robin@nibor.org> Date: Tue, 6 Feb 2024 23:04:37 +1100 Subject: [PATCH 589/815] 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). * <p>Example input:</p> @@ -100,6 +103,11 @@ public void extend(MarkdownRenderer.Builder rendererBuilder) { public NodeRenderer create(MarkdownNodeRendererContext context) { return new StrikethroughMarkdownNodeRenderer(context); } + + @Override + public Set<Character> 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). * <p> @@ -72,6 +75,11 @@ public void extend(MarkdownRenderer.Builder rendererBuilder) { public NodeRenderer create(MarkdownNodeRendererContext context) { return new TableMarkdownNodeRenderer(context); } + + @Override + public Set<Character> 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<Character> 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<Character> 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<Character> 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<Character> 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<Character> additionalTextEscapes; private RendererContext(MarkdownWriter writer) { + // Set fields that are used by interface this.writer = writer; + Set<Character> escapes = new HashSet<Character>(); + 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<Character> getSpecialCharacters() { + return additionalTextEscapes; + } } } From 64eafc413dfc5bc936dd1ae7dbf189d46a8652fa Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Tue, 6 Feb 2024 23:06:42 +1100 Subject: [PATCH 590/815] 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<Character> 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|<span>Foo\\|bar</span>|\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 <robin@nibor.org> Date: Wed, 7 Feb 2024 19:13:55 +1100 Subject: [PATCH 591/815] 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 ++ * <p> @@ -24,9 +31,7 @@ * The parsed ins text regions are turned into {@link Ins} nodes. * </p> */ -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<Character> 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<Extension> 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 <robin@nibor.org> Date: Thu, 8 Feb 2024 23:26:03 +1100 Subject: [PATCH 592/815] 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 <robin@nibor.org> Date: Thu, 8 Feb 2024 23:26:32 +1100 Subject: [PATCH 593/815] 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<Extension> 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 <robin@nibor.org> Date: Thu, 8 Feb 2024 23:31:37 +1100 Subject: [PATCH 594/815] 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 <robin@nibor.org> Date: Sat, 10 Feb 2024 11:54:30 +1100 Subject: [PATCH 595/815] 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]): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.20.0</version> + <version>0.21.0</version> </dependency> ``` @@ -234,7 +234,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.20.0</version> + <version>0.21.0</version> </dependency> ``` From 2cfe732fc8c67ab04350c1b54326e46ce4039d60 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Mon, 2 Oct 2023 19:26:48 +1100 Subject: [PATCH 596/815] 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 <robin@nibor.org> Date: Sat, 10 Feb 2024 11:56:38 +1100 Subject: [PATCH 597/815] 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 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.10.1</version> + <version>3.12.1</version> <configuration> - <source>7</source> - <target>7</target> + <release>11</release> </configuration> </plugin> <plugin> From 01fd7732e55b3aaa98a18c2d54b2d5dbc5dac4fa Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 10 Feb 2024 12:38:49 +1100 Subject: [PATCH 598/815] 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 <robin@nibor.org> Date: Thu, 15 Feb 2024 18:19:04 +1100 Subject: [PATCH 599/815] 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); // "<p>This is <em>Sparta</em></p>\n" +renderer.render(document); // "<p>This is <em>Markdown</em></p>\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 @@ <artifactId>commonmark</artifactId> <name>commonmark-java core</name> - <description>Core of commonmark-java (implementation of CommonMark for parsing markdown and rendering to HTML)</description> + <description>Core of commonmark-java (a library for parsing Markdown to an AST, modifying the AST and rendering it to HTML or Markdown)</description> <dependencies> <dependency> 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 @@ * <li>{@link org.commonmark.parser} for parsing input text to AST nodes</li> * <li>{@link org.commonmark.node} for AST node types and visitors</li> * <li>{@link org.commonmark.renderer.html} for HTML rendering</li> + * <li>{@link org.commonmark.renderer.markdown} for Markdown rendering</li> * </ul> */ 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. * <p> - * 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): * <ul> * <li>Headings are output as ATX headings if possible (multi-line headings need Setext headings)</li> * <li>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</li> + * <li>Leading whitespace in paragraphs is not preserved</li> * </ul> + * 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<MarkdownNodeRendererFactory> nodeRendererFactories = new ArrayList<>(); + private final List<MarkdownNodeRendererFactory> nodeRendererFactories = new ArrayList<>(); /** * @return the configured {@link MarkdownRenderer} @@ -106,9 +109,15 @@ public Builder extensions(Iterable<? extends Extension> 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("<p>This is <em>Sparta</em></p>\n", renderer.render(document)); + assertEquals("<p>This is <em>Markdown</em></p>\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 <robin@nibor.org> Date: Sat, 24 Feb 2024 11:23:27 +1100 Subject: [PATCH 600/815] 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 <robin@nibor.org> Date: Sat, 24 Feb 2024 14:36:34 +1100 Subject: [PATCH 601/815] 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 @@ <description>commonmark-java extension for turning plain URLs and email addresses into links</description> <properties> - <autolink.version>0.10.0</autolink.version> + <autolink.version>0.11.0</autolink.version> </properties> <dependencies> @@ -33,20 +33,4 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.autolink</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.gfm.strikethrough</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.gfm.tables</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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<SourceLine> 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<SourceLine> cells = new ArrayList<>(); @@ -267,7 +267,7 @@ public static class Factory extends AbstractBlockParserFactory { @Override public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) { List<SourceLine> 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<TableCell.Alignment> 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<TableCell.Alignment> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.heading.anchor</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.image.attributes</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.ins</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.task.list.items</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.ext.front.matter</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark.testutil</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - </project> 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 @@ </dependency> </dependencies> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-jar-plugin</artifactId> - <configuration> - <archive> - <manifestEntries> - <Automatic-Module-Name>org.commonmark</Automatic-Module-Name> - </manifestEntries> - </archive> - </configuration> - </plugin> - </plugins> - </build> - <profiles> <profile> <id>benchmark</id> 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 (<code>[</code>) or images (<code>![</code>). 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<Class<? extends Block>> 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 = "</" + TAGNAME + "\\s*[>]"; + 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 = "</" + TAGNAME + "\\s*[>]"; - 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. + * <p> + * 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 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>3.4.1</version> + <version>3.5.0</version> <configuration> <excludePackageNames>*.internal,*.internal.*</excludePackageNames> <!-- The offline links make links from extensions to core work. --> From a3d31d99fff86be3e6561e23ee66fd0310ed2adb Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 2 Mar 2024 11:37:13 +1100 Subject: [PATCH 602/815] 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<String> 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 <a href="https://spec.commonmark.org/0.31.2/#blank-line">blank line</a> + */ 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 <a href="https://spec.commonmark.org/0.29/#punctuation-character">punctuation character</a> + */ 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 <a href="https://spec.commonmark.org/0.31.2/#unicode-whitespace-character">Unicode whitespace character</a> + */ 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), - "<p>" + repeat("<em>a <strong>a ", x) + "b" + - repeat(" a</strong> a</em>", x) + "</p>\n"); + "*a **a ".repeat(x) + "b" + " a** a*".repeat(x), + "<p>" + "<em>a <strong>a ".repeat(x) + "b" + + " a</strong> a</em>".repeat(x) + "</p>\n"); } @Test public void emphasisClosersWithNoOpeners() { assertRendering( - repeat("a_ ", x), - "<p>" + repeat("a_ ", x - 1) + "a_</p>\n"); + "a_ ".repeat(x), + "<p>" + "a_ ".repeat(x - 1) + "a_</p>\n"); } @Test public void emphasisOpenersWithNoClosers() { assertRendering( - repeat("_a ", x), - "<p>" + repeat("_a ", x - 1) + "_a</p>\n"); + "_a ".repeat(x), + "<p>" + "_a ".repeat(x - 1) + "_a</p>\n"); } @Test public void linkClosersWithNoOpeners() { assertRendering( - repeat("a] ", x), - "<p>" + repeat("a] ", x - 1) + "a]</p>\n"); + "a] ".repeat(x), + "<p>" + "a] ".repeat(x - 1) + "a]</p>\n"); } @Test public void linkOpenersWithNoClosers() { assertRendering( - repeat("[a ", x), - "<p>" + repeat("[a ", x - 1) + "[a</p>\n"); + "[a ".repeat(x), + "<p>" + "[a ".repeat(x - 1) + "[a</p>\n"); } @Test public void linkOpenersAndEmphasisClosers() { assertRendering( - repeat("[ a_ ", x), - "<p>" + repeat("[ a_ ", x - 1) + "[ a_</p>\n"); + "[ a_ ".repeat(x), + "<p>" + "[ a_ ".repeat(x - 1) + "[ a_</p>\n"); } @Test public void mismatchedOpenersAndClosers() { assertRendering( - repeat("*a_ ", x), - "<p>" + repeat("*a_ ", x - 1) + "*a_</p>\n"); + "*a_ ".repeat(x), + "<p>" + "*a_ ".repeat(x - 1) + "*a_</p>\n"); } @Test public void nestedBrackets() { assertRendering( - repeat("[", x) + "a" + repeat("]", x), - "<p>" + repeat("[", x) + "a" + repeat("]", x) + "</p>\n"); + "[".repeat(x) + "a" + "]".repeat(x), + "<p>" + "[".repeat(x) + "a" + "]".repeat(x) + "</p>\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("<blockquote>\n", x) + "<p>a</p>\n" + - repeat("</blockquote>\n", x)); + "> ".repeat(x) + "a\n", + "<blockquote>\n".repeat(x) + "<p>a</p>\n" + + "</blockquote>\n".repeat(x)); } @Test public void hugeHorizontalRule() { assertRendering( - repeat("*", 10000) + "\n", + "*".repeat(10000) + "\n", "<hr />\n"); } @Test public void backslashInLink() { // See https://github.com/commonmark/commonmark.js/issues/157 - assertRendering("[" + repeat("\\", x) + "\n", - "<p>" + "[" + repeat("\\", x / 2) + "</p>\n"); + assertRendering("[" + "\\".repeat(x) + "\n", + "<p>" + "[" + "\\".repeat(x / 2) + "</p>\n"); } @Test public void unclosedInlineLinks() { // See https://github.com/commonmark/commonmark.js/issues/129 - assertRendering(repeat("[](", x) + "\n", - "<p>" + repeat("[](", x) + "</p>\n"); + assertRendering("[](".repeat(x) + "\n", + "<p>" + "[](".repeat(x) + "</p>\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 + "]: /", "<p><a href=\"/\">foo</a></p>\n"); assertRendering("[foo][x" + label1 + "]\n\n[x" + label1 + "]: /", "<p>[foo][x" + label1 + "]</p>\n<p>[x" + label1 + "]: /</p>\n"); assertRendering("[foo][\n" + label1 + "]\n\n[\n" + label1 + "]: /", "<p>[foo][\n" + label1 + "]</p>\n<p>[\n" + label1 + "]: /</p>\n"); - String label2 = Strings.repeat("a\n", 499); + String label2 = "a\n".repeat(499); assertRendering("[foo][" + label2 + "]\n\n[" + label2 + "]: /", "<p><a href=\"/\">foo</a></p>\n"); assertRendering("[foo][12" + label2 + "]\n\n[12" + label2 + "]: /", "<p>[foo][12" + label2 + "]</p>\n<p>[12" + label2 + "]: /</p>\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 <robin@nibor.org> Date: Sat, 2 Mar 2024 11:45:52 +1100 Subject: [PATCH 603/815] 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 <robin@nibor.org> Date: Sat, 2 Mar 2024 12:03:11 +1100 Subject: [PATCH 604/815] 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 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 literal 58695 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 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 <robin@nibor.org> Date: Sat, 2 Mar 2024 12:27:07 +1100 Subject: [PATCH 605/815] 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 <robin@nibor.org> Date: Sat, 2 Mar 2024 16:15:25 +1100 Subject: [PATCH 606/815] 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 @@ -<manifest package="org.commonmark.android.test"> +<manifest> <application /> </manifest> From 33a589951141e6514f9ebc012f9600fc67def1cc Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 2 Mar 2024 18:56:26 +1100 Subject: [PATCH 607/815] 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 <robin@nibor.org> Date: Sat, 2 Mar 2024 19:00:28 +1100 Subject: [PATCH 608/815] 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<String> 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 <robin@nibor.org> Date: Sat, 2 Mar 2024 19:17:15 +1100 Subject: [PATCH 609/815] 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 <robin@nibor.org> Date: Sat, 2 Mar 2024 19:22:49 +1100 Subject: [PATCH 610/815] 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.\ `<![CDATA[`.\ **End condition:** line contains the string `]]>`. -6. **Start condition:** line begins the string `<` or `</` +6. **Start condition:** line begins with the string `<` or `</` followed by one of the strings (case-insensitive) `address`, `article`, `aside`, `base`, `basefont`, `blockquote`, `body`, `caption`, `center`, `col`, `colgroup`, `dd`, `details`, `dialog`, @@ -5279,7 +5276,7 @@ well. ([reStructuredText](http://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, @@ -6929,7 +6926,7 @@ foo__bar__ ```````````````````````````````` example __foo, __bar__, baz__ . -<p><strong>foo, <strong>bar</strong>, baz</strong></p> +<p><strong>foo, bar, baz</strong></p> ```````````````````````````````` @@ -7200,7 +7197,7 @@ foo***bar***baz ```````````````````````````````` example foo******bar*********baz . -<p>foo<strong><strong><strong>bar</strong></strong></strong>***baz</p> +<p>foo<strong>bar</strong>***baz</p> ```````````````````````````````` @@ -7271,21 +7268,21 @@ __foo _bar_ baz__ ```````````````````````````````` example __foo __bar__ baz__ . -<p><strong>foo <strong>bar</strong> baz</strong></p> +<p><strong>foo bar baz</strong></p> ```````````````````````````````` ```````````````````````````````` example ____foo__ bar__ . -<p><strong><strong>foo</strong> bar</strong></p> +<p><strong>foo bar</strong></p> ```````````````````````````````` ```````````````````````````````` example **foo **bar**** . -<p><strong>foo <strong>bar</strong></strong></p> +<p><strong>foo bar</strong></p> ```````````````````````````````` @@ -7570,14 +7567,14 @@ switching delimiters: ```````````````````````````````` example ****foo**** . -<p><strong><strong>foo</strong></strong></p> +<p><strong>foo</strong></p> ```````````````````````````````` ```````````````````````````````` example ____foo____ . -<p><strong><strong>foo</strong></strong></p> +<p><strong>foo</strong></p> ```````````````````````````````` @@ -7588,7 +7585,7 @@ delimiters: ```````````````````````````````` example ******foo****** . -<p><strong><strong><strong>foo</strong></strong></strong></p> +<p><strong>foo</strong></p> ```````````````````````````````` @@ -7604,7 +7601,7 @@ Rule 14: ```````````````````````````````` example _____foo_____ . -<p><em><strong><strong>foo</strong></strong></em></p> +<p><em><strong>foo</strong></em></p> ```````````````````````````````` @@ -9410,10 +9407,9 @@ character, and a `>` character. A [closing tag](@) consists of the string `</`, a [tag name], optional [whitespace], and the character `>`. -An [HTML comment](@) consists of `<!--` + *text* + `-->`, -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 `<!--`, a string of +characters not including the string `-->`, and `-->` (see the +[HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). A [processing instruction](@) consists of the string `<?`, a string @@ -9554,30 +9550,20 @@ Illegal attributes in closing tag: Comments: ```````````````````````````````` example -foo <!-- this is a -comment - with hyphen --> +foo <!-- this is a -- +comment - with hyphens --> . -<p>foo <!-- this is a -comment - with hyphen --></p> +<p>foo <!-- this is a -- +comment - with hyphens --></p> ```````````````````````````````` - -```````````````````````````````` example -foo <!-- not a comment -- two hyphens --> -. -<p>foo <!-- not a comment -- two hyphens --></p> -```````````````````````````````` - - -Not comments: - ```````````````````````````````` example foo <!--> foo --> -foo <!-- foo---> +foo <!---> foo --> . -<p>foo <!--> foo --></p> -<p>foo <!-- foo---></p> +<p>foo <!--> foo --></p> +<p>foo <!---> foo --></p> ```````````````````````````````` 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. -> (<http://daringfireball.net/projects/markdown/>) +> (<https://daringfireball.net/projects/markdown/>) 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 -<http://example.com?find=\*> +<https://example.com?find=\*> . -<p><a href="http://example.com?find=%5C*">http://example.com?find=\*</a></p> +<p><a href="https://example.com?find=%5C*">https://example.com?find=\*</a></p> ```````````````````````````````` @@ -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.\ `<![CDATA[`.\ **End condition:** line contains the string `]]>`. -6. **Start condition:** line begins the string `<` or `</` +6. **Start condition:** line begins with the string `<` or `</` followed by one of the strings (case-insensitive) `address`, `article`, `aside`, `base`, `basefont`, `blockquote`, `body`, `caption`, `center`, `col`, `colgroup`, `dd`, `details`, `dialog`, @@ -2406,7 +2402,7 @@ followed by one of the strings (case-insensitive) `address`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`, `html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`, `nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`, -`section`, `source`, `summary`, `table`, `tbody`, `td`, +`search`, `section`, `summary`, `table`, `tbody`, `td`, `tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed by a space, a tab, the end of the line, the string `>`, 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>` . -<p><code><http://foo.bar.</code>baz>`</p> +<p><code><https://foo.bar.</code>baz>`</p> ```````````````````````````````` But this is an autolink: ```````````````````````````````` example -<http://foo.bar.`baz>` +<https://foo.bar.`baz>` . -<p><a href="http://foo.bar.%60baz">http://foo.bar.`baz</a>`</p> +<p><a href="https://foo.bar.%60baz">https://foo.bar.`baz</a>`</p> ```````````````````````````````` @@ -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. +. +<p>*$*alpha.</p> +<p>*£*bravo.</p> +<p>*€*charlie.</p> +```````````````````````````````` + + Intraword emphasis with `*` is permitted: ```````````````````````````````` example @@ -7431,16 +7442,16 @@ _a `_`_ ```````````````````````````````` example -**a<http://foo.bar/?q=**> +**a<https://foo.bar/?q=**> . -<p>**a<a href="http://foo.bar/?q=**">http://foo.bar/?q=**</a></p> +<p>**a<a href="https://foo.bar/?q=**">https://foo.bar/?q=**</a></p> ```````````````````````````````` ```````````````````````````````` example -__a<http://foo.bar/?q=__> +__a<https://foo.bar/?q=__> . -<p>__a<a href="http://foo.bar/?q=__">http://foo.bar/?q=__</a></p> +<p>__a<a href="https://foo.bar/?q=__">https://foo.bar/?q=__</a></p> ```````````````````````````````` @@ -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) . <p><a href="#fragment">link</a></p> -<p><a href="http://example.com#fragment">link</a></p> -<p><a href="http://example.com?foo=3#frag">link</a></p> +<p><a href="https://example.com#fragment">link</a></p> +<p><a href="https://example.com?foo=3#frag">link</a></p> ```````````````````````````````` @@ -7938,9 +7949,9 @@ and autolinks over link grouping: ```````````````````````````````` example -[foo<http://example.com/?search=](uri)> +[foo<https://example.com/?search=](uri)> . -<p>[foo<a href="http://example.com/?search=%5D(uri)">http://example.com/?search=](uri)</a></p> +<p>[foo<a href="https://example.com/?search=%5D(uri)">https://example.com/?search=](uri)</a></p> ```````````````````````````````` @@ -8094,11 +8105,11 @@ and autolinks over link grouping: ```````````````````````````````` example -[foo<http://example.com/?search=][ref]> +[foo<https://example.com/?search=][ref]> [ref]: /uri . -<p>[foo<a href="http://example.com/?search=%5D%5Bref%5D">http://example.com/?search=][ref]</a></p> +<p>[foo<a href="https://example.com/?search=%5D%5Bref%5D">https://example.com/?search=][ref]</a></p> ```````````````````````````````` @@ -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> . -<p><a href="http://foo.bar.baz/test?q=hello&id=22&boolean">http://foo.bar.baz/test?q=hello&id=22&boolean</a></p> +<p><a href="https://foo.bar.baz/test?q=hello&id=22&boolean">https://foo.bar.baz/test?q=hello&id=22&boolean</a></p> ```````````````````````````````` @@ -8816,9 +8827,9 @@ with their syntax: ```````````````````````````````` example -<http://../> +<https://../> . -<p><a href="http://../">http://../</a></p> +<p><a href="https://../">https://../</a></p> ```````````````````````````````` @@ -8832,18 +8843,18 @@ with their syntax: Spaces are not allowed in autolinks: ```````````````````````````````` example -<http://foo.bar/baz bim> +<https://foo.bar/baz bim> . -<p><http://foo.bar/baz bim></p> +<p><https://foo.bar/baz bim></p> ```````````````````````````````` Backslash-escapes do not work inside autolinks: ```````````````````````````````` example -<http://example.com/\[\> +<https://example.com/\[\> . -<p><a href="http://example.com/%5C%5B%5C">http://example.com/\[\</a></p> +<p><a href="https://example.com/%5C%5B%5C">https://example.com/\[\</a></p> ```````````````````````````````` @@ -8895,9 +8906,9 @@ These are not autolinks: ```````````````````````````````` example -< http://foo.bar > +< https://foo.bar > . -<p>< http://foo.bar ></p> +<p>< https://foo.bar ></p> ```````````````````````````````` @@ -8916,9 +8927,9 @@ These are not autolinks: ```````````````````````````````` example -http://example.com +https://example.com . -<p>http://example.com</p> +<p>https://example.com</p> ```````````````````````````````` @@ -8980,10 +8991,9 @@ A [closing tag](@) consists of the string `</`, a [tag name], optional spaces, tabs, and up to one line ending, and the character `>`. -An [HTML comment](@) consists of `<!--` + *text* + `-->`, -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 `<!--`, a string of +characters not including the string `-->`, and `-->` (see the +[HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). A [processing instruction](@) consists of the string `<?`, a string @@ -9122,30 +9132,20 @@ Illegal attributes in closing tag: Comments: ```````````````````````````````` example -foo <!-- this is a -comment - with hyphen --> +foo <!-- this is a -- +comment - with hyphens --> . -<p>foo <!-- this is a -comment - with hyphen --></p> +<p>foo <!-- this is a -- +comment - with hyphens --></p> ```````````````````````````````` - -```````````````````````````````` example -foo <!-- not a comment -- two hyphens --> -. -<p>foo <!-- not a comment -- two hyphens --></p> -```````````````````````````````` - - -Not comments: - ```````````````````````````````` example foo <!--> foo --> -foo <!-- foo---> +foo <!---> foo --> . -<p>foo <!--> foo --></p> -<p>foo <!-- foo---></p> +<p>foo <!--> foo --></p> +<p>foo <!---> foo --></p> ```````````````````````````````` @@ -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 <robin@nibor.org> Date: Sat, 2 Mar 2024 21:03:01 +1100 Subject: [PATCH 611/815] 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 <a href="https://spec.commonmark.org/0.29/#punctuation-character">punctuation character</a> + * @see <a href="https://spec.commonmark.org/0.31.2/#unicode-punctuation-character">Unicode punctuation character</a> */ 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 <robin@nibor.org> Date: Sat, 2 Mar 2024 21:05:47 +1100 Subject: [PATCH 612/815] 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 <robin@nibor.org> Date: Sat, 2 Mar 2024 21:26:42 +1100 Subject: [PATCH 613/815] 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 <!-- + text + -->, 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 `<!--`, a string of + // characters not including the string `-->`, and `-->` (see the + // [HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). // Skip first `-` scanner.next(); @@ -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 <!---->", "<p>inline <!----></p>\n"); assertRendering("inline <!-- -> -->", "<p>inline <!-- -> --></p>\n"); - assertRendering("inline <!--->-->", "<p>inline <!--->--></p>\n"); + assertRendering("inline <!-- -- -->", "<p>inline <!-- -- --></p>\n"); + assertRendering("inline <!-- --->", "<p>inline <!-- ---></p>\n"); + assertRendering("inline <!-- ---->", "<p>inline <!-- ----></p>\n"); + assertRendering("inline <!-->-->", "<p>inline <!-->--></p>\n"); + assertRendering("inline <!--->-->", "<p>inline <!--->--></p>\n"); } @Test From a8af670b8ac5212b9a658b14c6a2a0d1170f83b7 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 2 Mar 2024 22:34:31 +1100 Subject: [PATCH 614/815] 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<Extension> 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}", "<p><img src=\"/url.png\" alt=\"text\" height=\"5\" width=\"6\" /></p>\n"); + } + + @Test + public void testTaskListItems() { + assertRendering("- [ ] task to do\n- [x] task done\n", + "<ul>\n<li><input type=\"checkbox\" disabled=\"\"> task to do</li>\n" + + "<li><input type=\"checkbox\" disabled=\"\" checked=\"\"> task done</li>\n</ul>\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<Extension> 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<String, String> OVERRIDDEN_EXAMPLES = getOverriddenExamples(); public SpecIntegrationTest(Example example) { @@ -59,7 +42,7 @@ private static Map<String, String> getOverriddenExamples() { Map<String, String> m = new HashMap<>(); // Not a spec autolink because of space, but the resulting text contains a valid URL - m.put("<http://foo.bar/baz bim>\n", "<p><<a href=\"http://foo.bar/baz\">http://foo.bar/baz</a> bim></p>\n"); + m.put("<https://foo.bar/baz bim>\n", "<p><<a href=\"https://foo.bar/baz\">https://foo.bar/baz</a> bim></p>\n"); // Not a spec autolink, but the resulting text contains a valid email m.put("<foo\\+@bar.example.com>\n", "<p><<a href=\"mailto:foo+@bar.example.com\">foo+@bar.example.com</a>></p>\n"); @@ -68,10 +51,10 @@ private static Map<String, String> getOverriddenExamples() { m.put("<heck://bing.bong>\n", "<p><<a href=\"heck://bing.bong%3E\">heck://bing.bong></a></p>\n"); // Not a spec autolink because of spaces, but autolink extension doesn't limit schemes - m.put("< http://foo.bar >\n", "<p>< <a href=\"http://foo.bar\">http://foo.bar</a> ></p>\n"); + m.put("< https://foo.bar >\n", "<p>< <a href=\"https://foo.bar\">https://foo.bar</a> ></p>\n"); // Plain autolink - m.put("http://example.com\n", "<p><a href=\"http://example.com\">http://example.com</a></p>\n"); + m.put("https://example.com\n", "<p><a href=\"https://example.com\">https://example.com</a></p>\n"); // Plain autolink m.put("foo@bar.example.com\n", "<p><a href=\"mailto:foo@bar.example.com\">foo@bar.example.com</a></p>\n"); @@ -80,13 +63,6 @@ private static Map<String, String> getOverriddenExamples() { m.put("---\nFoo\n---\nBar\n---\nBaz\n", "<h2>Bar</h2>\n<p>Baz</p>\n"); m.put("---\n---\n", ""); - // Image attributes - m.put("![text](/url.png){height=5 width=6}", "<img src=\"/url.png\" alt=\"text\" height=\"5\" width=\"6\" />"); - - // Task list items - m.put("- [ ] task to do\n- [x] task done\n", "<ul>\n<li><input type=\"checkbox\" disabled=\"\"> task to do</li>\n" + - "<li><input type=\"checkbox\" disabled=\"\" checked=\"\"> task done</li>\n</ul>\n"); - return m; } From b13438921bf18c79e2cff3cf6c38a3aef0b73a8e Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 9 Mar 2024 11:49:44 +1100 Subject: [PATCH 615/815] 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 <robin@nibor.org> Date: Tue, 12 Mar 2024 15:15:39 +1100 Subject: [PATCH 616/815] 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 <robin@nibor.org> Date: Tue, 12 Mar 2024 16:27:19 +1100 Subject: [PATCH 617/815] 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<Node> 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> T find(Node parent, Class<T> nodeClass) { + public static <T> T tryFind(Node parent, Class<T> nodeClass) { Node node = parent.getFirstChild(); while (node != null) { Node next = node.getNext(); @@ -29,7 +29,7 @@ public static <T> T find(Node parent, Class<T> 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> T find(Node parent, Class<T> 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> T find(Node parent, Class<T> 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 <robin@nibor.org> Date: Tue, 12 Mar 2024 16:28:13 +1100 Subject: [PATCH 618/815] 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 <robin@nibor.org> Date: Thu, 14 Mar 2024 11:43:04 +1100 Subject: [PATCH 619/815] 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. * <p> * Some examples and their marker indent: * <pre>- Foo</pre> @@ -21,17 +22,17 @@ public void accept(Visitor visitor) { * <pre> 1. Foo</pre> * 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. * <p> * Some examples and their content indent: * <pre>- Foo</pre> @@ -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<String, String> 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 <robin@nibor.org> Date: Thu, 14 Mar 2024 17:02:12 +1100 Subject: [PATCH 620/815] 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 <robin@nibor.org> Date: Fri, 15 Mar 2024 16:47:34 +1100 Subject: [PATCH 621/815] 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): * <ul> * <li>Headings are output as ATX headings if possible (multi-line headings need Setext headings)</li> + * <li>Links are always rendered as inline links (no support for reference links yet)</li> * <li>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</li> * <li>Leading whitespace in paragraphs is not preserved</li> From bd4012e857618f929efaff5b715038e9fcbd5c0c Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Fri, 15 Mar 2024 16:52:10 +1100 Subject: [PATCH 622/815] 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> 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 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 4eac3b7b2..2def77372 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.21.1-SNAPSHOT</version> + <version>0.22.0-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> 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 623/815] [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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-ext-ins</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> 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 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-integration-test</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark-test-util</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 2def77372..693760923 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.22.0-SNAPSHOT</version> + <version>0.22.0</version> </dependency> <!-- Common test dependencies --> @@ -281,7 +281,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>HEAD</tag> + <tag>commonmark-parent-0.22.0</tag> </scm> <distributionManagement> 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 624/815] [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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-autolink</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-gfm-tables</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-heading-anchor</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-image-attributes</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-ins</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-task-list-items</artifactId> 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 @@ <parent> <artifactId>commonmark-parent</artifactId> <groupId>org.commonmark</groupId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-ext-yaml-front-matter</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-integration-test</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark-test-util</artifactId> 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 @@ <parent> <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </parent> <artifactId>commonmark</artifactId> diff --git a/pom.xml b/pom.xml index 693760923..d4e49cd80 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>org.commonmark</groupId> <artifactId>commonmark-parent</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> <name>commonmark-java parent</name> <description> Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -111,52 +111,52 @@ <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-autolink</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-image-attributes</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-ins</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-strikethrough</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-heading-anchor</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-task-list-items</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-yaml-front-matter</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-test-util</artifactId> - <version>0.22.0</version> + <version>0.22.1-SNAPSHOT</version> </dependency> <!-- Common test dependencies --> @@ -281,7 +281,7 @@ <connection>scm:git:https://github.com/commonmark/commonmark-java</connection> <developerConnection>scm:git:https://github.com/commonmark/commonmark-java</developerConnection> <url>https://github.com/commonmark/commonmark-java</url> - <tag>commonmark-parent-0.22.0</tag> + <tag>HEAD</tag> </scm> <distributionManagement> From b570e1bda7732c37b748155fb5dc2716c0517a20 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 6 Apr 2024 12:06:32 +0700 Subject: [PATCH 625/815] 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 <a href="https://spec.commonmark.org/0.29/#link-reference-definition">Link reference definitions</a> + * @see <a href="https://spec.commonmark.org/0.31.2/#link-reference-definitions">Link reference definitions</a> */ 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 <robin@nibor.org> Date: Tue, 16 Apr 2024 00:02:37 +1000 Subject: [PATCH 626/815] 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<Character, DelimiterProcessor> delimiterProcessors; private final InlineParserContext context; private final Map<Character, List<InlineContentParser>> inlineParsers; + private final Map<Character, DelimiterProcessor> 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.<InlineContentParser>singletonList(new BackslashInlineParser())); - this.inlineParsers.put('`', Collections.<InlineContentParser>singletonList(new BackticksInlineParser())); - this.inlineParsers.put('&', Collections.<InlineContentParser>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<Character> delimiterCharacters, Set<Character> characters) { - BitSet bitSet = new BitSet(); - for (Character c : delimiterCharacters) { - bitSet.set(c); + private static Map<Character, List<InlineContentParser>> calculateInlineContentParsers() { + var map = new HashMap<Character, List<InlineContentParser>>(); + 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<Character, DelimiterProcessor> calculateDelimiterProcessors(List<DelimiterProcessor> delimiterProcessors) { - Map<Character, DelimiterProcessor> map = new HashMap<>(); - addDelimiterProcessors(Arrays.<DelimiterProcessor>asList(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map); + private static Map<Character, DelimiterProcessor> calculateDelimiterProcessors(List<DelimiterProcessor> delimiterProcessors) { + var map = new HashMap<Character, DelimiterProcessor>(); + addDelimiterProcessors(List.of(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map); addDelimiterProcessors(delimiterProcessors, map); return map; } - @Override - public Scanner scanner() { - return scanner; - } - private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> 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<Character> delimiterCharacters, Set<Character> 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). * <p> * 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 <robin@nibor.org> Date: Tue, 16 Apr 2024 00:14:46 +1000 Subject: [PATCH 627/815] 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<? extends Node> 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<? extends Node> 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<InlineContentParser> inlineParsers = this.inlineParsers.get(c); @@ -186,7 +185,7 @@ private List<? extends Node> 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<? extends Node> 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 <robin@nibor.org> Date: Tue, 16 Apr 2024 22:23:58 +1000 Subject: [PATCH 628/815] 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<BlockParserFactory> blockParserFactories; private final InlineParserFactory inlineParserFactory; + private final List<InlineContentParser> inlineContentParsers; private final List<DelimiterProcessor> delimiterProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; @@ -75,9 +77,11 @@ public class DocumentParser implements ParserState { private final List<BlockParser> allBlockParsers = new ArrayList<>(); public DocumentParser(List<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory, - List<DelimiterProcessor> delimiterProcessors, IncludeSourceSpans includeSourceSpans) { + List<InlineContentParser> inlineContentParsers, List<DelimiterProcessor> 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<InlineContentParser> inlineContentParsers; private final List<DelimiterProcessor> delimiterProcessors; private final LinkReferenceDefinitions linkReferenceDefinitions; - public InlineParserContextImpl(List<DelimiterProcessor> delimiterProcessors, + public InlineParserContextImpl(List<InlineContentParser> inlineContentParsers, + List<DelimiterProcessor> delimiterProcessors, LinkReferenceDefinitions linkReferenceDefinitions) { + this.inlineContentParsers = inlineContentParsers; this.delimiterProcessors = delimiterProcessors; this.linkReferenceDefinitions = linkReferenceDefinitions; } + @Override + public List<InlineContentParser> getCustomInlineContentParsers() { + return inlineContentParsers; + } + @Override public List<DelimiterProcessor> 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<Character, List<InlineContentParser>> calculateInlineContentParsers() { + private static Map<Character, List<InlineContentParser>> calculateInlineContentParsers(List<InlineContentParser> inlineContentParsers) { var map = new HashMap<Character, List<InlineContentParser>>(); + // 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<InlineContentParser> 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<BlockParserFactory> blockParserFactories; + private final List<InlineContentParser> inlineContentParsers; private final List<DelimiterProcessor> delimiterProcessors; private final InlineParserFactory inlineParserFactory; private final List<PostProcessor> 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<BlockParserFactory> blockParserFactories = new ArrayList<>(); + private final List<InlineContentParser> inlineContentParsers = new ArrayList<>(); private final List<DelimiterProcessor> delimiterProcessors = new ArrayList<>(); private final List<PostProcessor> postProcessors = new ArrayList<>(); private Set<Class<? extends Block>> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); @@ -169,7 +174,7 @@ public Builder extensions(Iterable<? extends Extension> extensions) { * </pre> * * @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<Class<? extends Block>> enabledBlockTypes) { @@ -196,7 +201,7 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { } /** - * Adds a custom block parser factory. + * Add a custom block parser factory. * <p> * Note that custom factories are applied <em>before</em> 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. + * <p> + * 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. * <p> * 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<InlineContentParser> getCustomInlineContentParsers() { + return inlineParserContext.getCustomInlineContentParsers(); + } + @Override public List<DelimiterProcessor> getCustomDelimiterProcessors() { return inlineParserContext.getCustomDelimiterProcessors(); From 07735411f6654700c46c1b6f6d46176f0fe66dc0 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Mon, 22 Apr 2024 09:36:38 +1000 Subject: [PATCH 629/815] 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<BlockParserFactory> blockParserFactories; private final InlineParserFactory inlineParserFactory; - private final List<InlineContentParser> inlineContentParsers; + private final List<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; @@ -77,11 +77,11 @@ public class DocumentParser implements ParserState { private final List<BlockParser> allBlockParsers = new ArrayList<>(); public DocumentParser(List<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory, - List<InlineContentParser> inlineContentParsers, List<DelimiterProcessor> delimiterProcessors, + List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> 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<InlineContentParser> inlineContentParsers; + private final List<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; private final LinkReferenceDefinitions linkReferenceDefinitions; - public InlineParserContextImpl(List<InlineContentParser> inlineContentParsers, + public InlineParserContextImpl(List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, LinkReferenceDefinitions linkReferenceDefinitions) { - this.inlineContentParsers = inlineContentParsers; + this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; this.linkReferenceDefinitions = linkReferenceDefinitions; } @Override - public List<InlineContentParser> getCustomInlineContentParsers() { - return inlineContentParsers; + public List<InlineContentParserFactory> 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<Character, List<InlineContentParser>> inlineParsers; + private final List<InlineContentParserFactory> inlineContentParserFactories; private final Map<Character, DelimiterProcessor> delimiterProcessors; private final BitSet specialCharacters; + private Map<Character, List<InlineContentParser>> 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<Character, List<InlineContentParser>> calculateInlineContentParsers(List<InlineContentParser> inlineContentParsers) { - var map = new HashMap<Character, List<InlineContentParser>>(); + private List<InlineContentParserFactory> calculateInlineContentParserFactories(List<InlineContentParserFactory> 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<Character, DelimiterProcessor> calculateDelimiterProcessors(List<DelimiterProcessor> delimiterProcessors) { @@ -96,13 +95,14 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr } } - private static BitSet calculateSpecialCharacters(Set<Character> delimiterCharacters, Set<Character> characters) { + private static BitSet calculateSpecialCharacters(Set<Character> delimiterCharacters, + List<InlineContentParserFactory> 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<Character> delimiterCharact return bitSet; } + private Map<Character, List<InlineContentParser>> createInlineContentParsers() { + var map = new HashMap<Character, List<InlineContentParser>>(); + 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. + * <p> + * 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<InlineContentParser> getCustomInlineContentParsers(); + List<InlineContentParserFactory> 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<BlockParserFactory> blockParserFactories; - private final List<InlineContentParser> inlineContentParsers; + private final List<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; private final InlineParserFactory inlineParserFactory; private final List<PostProcessor> 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<BlockParserFactory> blockParserFactories = new ArrayList<>(); - private final List<InlineContentParser> inlineContentParsers = new ArrayList<>(); + private final List<InlineContentParserFactory> inlineContentParserFactories = new ArrayList<>(); private final List<DelimiterProcessor> delimiterProcessors = new ArrayList<>(); private final List<PostProcessor> postProcessors = new ArrayList<>(); private Set<Class<? extends Block>> 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. * <p> * 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<InlineContentParser> getCustomInlineContentParsers() { - return inlineParserContext.getCustomInlineContentParsers(); + public List<InlineContentParserFactory> getCustomInlineContentParserFactories() { + return inlineParserContext.getCustomInlineContentParserFactories(); } @Override From e7d7bcd258d7bb4cd83c2c8c7d2d6c4b81f9b0dc Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Thu, 25 Apr 2024 23:03:48 +1000 Subject: [PATCH 630/815] 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<Character> 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<Character> delimiterCharact private Map<Character, List<InlineContentParser>> createInlineContentParsers() { var map = new HashMap<Character, List<InlineContentParser>>(); 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<Character> 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<Character> 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<Character> 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<Character> 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<Character> 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. * <p> * 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<Character> 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. * <p> * 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<Character> getTriggerCharacters() { + return Set.of('$'); } @Override From eeb077623e47047646130005151faa9376ea8ab4 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Fri, 26 Apr 2024 17:44:19 +1000 Subject: [PATCH 631/815] 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. + * <p> + * <em>This interface is not intended to be implemented by clients.</em> + */ +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 <robin@nibor.org> Date: Fri, 26 Apr 2024 17:47:43 +1000 Subject: [PATCH 632/815] 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<InlineContentParserFactory> 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<DelimiterProcessor> 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 <robin@nibor.org> Date: Fri, 26 Apr 2024 17:53:18 +1000 Subject: [PATCH 633/815] 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 <robin@nibor.org> Date: Fri, 26 Apr 2024 19:02:49 +1000 Subject: [PATCH 634/815] 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. * <p> * 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. * <p> * 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. + * <p> + * 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. + * <p> + * 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 *}. * <p> * 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 <robin@nibor.org> Date: Fri, 26 Apr 2024 19:03:18 +1000 Subject: [PATCH 635/815] 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 <robin@nibor.org> Date: Fri, 26 Apr 2024 22:36:18 +1000 Subject: [PATCH 636/815] 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 <a href="https://spec.commonmark.org/0.29/#link-reference-definition">Link reference definitions</a> */ -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 <robin@nibor.org> Date: Fri, 26 Apr 2024 23:03:26 +1000 Subject: [PATCH 637/815] 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<Class<? extends Node>> getNodeTypes() { // Return the node types we want to use this renderer for. - return Collections.<Class<? extends Node>>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<Extension> extensions = Arrays.asList(TablesExtension.create()); +List<Extension> 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<Extension> EXTENSIONS = Collections.singleton(AutolinkExtension.create()); + private static final Set<Extension> 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<Character> 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<String, String> attributes = context.extendAttributes(node, "del", Collections.<String, String>emptyMap()); + Map<String, String> 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<Class<? extends Node>> getNodeTypes() { - return Collections.<Class<? extends Node>>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<Extension> EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); + private static final Set<Extension> 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<Extension> EXTENSIONS = Collections.singleton(StrikethroughExtension.create()); + private static final Set<Extension> 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<Extension> EXTENSIONS = singleton(StrikethroughExtension.create()); + private static final Set<Extension> 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<Character> 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<String, String> getAttributes(Node node, String tagName) { - return context.extendAttributes(node, tagName, Collections.<String, String>emptyMap()); + return context.extendAttributes(node, tagName, Map.of()); } private Map<String, String> 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.<String, String>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<Class<? extends Node>> 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<Extension> EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set<Extension> 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<Extension> EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set<Extension> 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<Extension> EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set<Extension> 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<Extension> EXTENSIONS = Collections.singleton(TablesExtension.create()); + private static final Set<Extension> 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("<h1 id=\"id\"></h1>\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<Extension> EXTENSIONS = Collections.singleton(HeadingAnchorExtension.create()); + private static final Set<Extension> 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<String> SUPPORTED_ATTRIBUTES = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList("width", "height"))); + private static final Set<String> 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<Extension> EXTENSIONS = Collections.singleton(ImageAttributesExtension.create()); + private static final Set<Extension> 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<Character> 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<String, String> attributes = context.extendAttributes(node, "ins", Collections.<String, String>emptyMap()); + Map<String, String> 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<Class<? extends Node>> getNodeTypes() { - return Collections.<Class<? extends Node>>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<Extension> EXTENSIONS = Collections.singleton(InsExtension.create()); + private static final Set<Extension> 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<Extension> EXTENSIONS = Collections.singleton(InsExtension.create()); + private static final Set<Extension> 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<Class<? extends Node>> getNodeTypes() { - return Collections.<Class<? extends Node>>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<Extension> EXTENSIONS = Collections.singleton(TaskListItemsExtension.create()); + private static final Set<Extension> EXTENSIONS = Set.of(TaskListItemsExtension.create()); private static final String HTML_CHECKED = "<input type=\"checkbox\" disabled=\"\" checked=\"\">"; private static final String HTML_UNCHECKED = "<input type=\"checkbox\" disabled=\"\">"; 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<Extension> EXTENSIONS = Collections.singleton(YamlFrontMatterExtension.create()); + private static final Set<Extension> 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<String, List<String>> 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<String, List<String>> 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<Extension> ALL_EXTENSIONS = Arrays.asList( + static final List<Extension> 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<Extension> EXTENSIONS = Arrays.asList( + private static final List<Extension> 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<URL> 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<Class<? extends Block>> CORE_FACTORY_TYPES = new LinkedHashSet<>(Arrays.asList( + private static final Set<Class<? extends Block>> 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<SourceSpan> getSourceSpans() { - return sourceSpans != null ? Collections.unmodifiableList(sourceSpans) : Collections.<SourceSpan>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<SourceSpan> getSourceSpans() { - return sourceSpans != null ? sourceSpans : Collections.<SourceSpan>emptyList(); + return sourceSpans != null ? sourceSpans : List.of(); } public void addAllFrom(Iterable<? extends Node> 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<? extends Extension> extensions) { * E.g., to only parse headings and lists: * <pre> * {@code - * Parser.builder().enabledBlockTypes(new HashSet<>(Arrays.asList(Heading.class, ListBlock.class))); + * Parser.builder().enabledBlockTypes(Set.of(Heading.class, ListBlock.class)); * } * </pre> * 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<Class<? extends Node>> getNodeTypes() { - return new HashSet<>(Arrays.asList( + return Set.of( Document.class, Heading.class, Paragraph.class, @@ -41,7 +43,7 @@ public Set<Class<? extends Node>> 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.<String, String>emptyMap()); + renderCodeBlock(indentedCodeBlock.getLiteral(), indentedCodeBlock, Map.of()); } @Override @@ -287,7 +289,7 @@ private boolean isInTightList(Paragraph paragraph) { } private Map<String, String> getAttrs(Node node, String tagName) { - return getAttrs(node, tagName, Collections.<String, String>emptyMap()); + return getAttrs(node, tagName, Map.of()); } private Map<String, String> getAttrs(Node node, String tagName, Map<String, String> 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<String> protocols; public DefaultUrlSanitizer() { - this(Arrays.asList("http", "https", "mailto")); + this(List.of("http", "https", "mailto")); } public DefaultUrlSanitizer(Collection<String> 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<String, String> NO_ATTRIBUTES = Collections.emptyMap(); + private static final Map<String, String> 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<Class<? extends Node>> getNodeTypes() { - return new HashSet<>(Arrays.asList( + return Set.of( BlockQuote.class, BulletList.class, Code.class, @@ -72,7 +70,7 @@ public Set<Class<? extends Node>> getNodeTypes() { StrongEmphasis.class, Text.class, ThematicBreak.class - )); + ); } @Override @@ -470,9 +468,9 @@ private static List<String> 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<Character> 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<Class<? extends Node>> getNodeTypes() { - return new HashSet<>(Arrays.asList( + return Set.of( Document.class, Heading.class, Paragraph.class, @@ -48,7 +46,7 @@ public Set<Class<? extends Node>> 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<Node> nodes = parse(Collections.singletonList(SPEC)); + List<Node> 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<BlockParserFactory> CORE_FACTORIES = Arrays.<BlockParserFactory>asList( + private static final List<BlockParserFactory> 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<BlockParserFactory> customParserFactories = Collections.emptyList(); - Set<Class<? extends Block>> nodes = new HashSet<>(Arrays.asList(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class)); + List<BlockParserFactory> customParserFactories = List.of(); + var enabledBlockTypes = Set.of(BlockQuote.class, Heading.class, FencedCodeBlock.class, HtmlBlock.class, ThematicBreak.class, ListBlock.class, IndentedCodeBlock.class); - List<BlockParserFactory> blockParserFactories = DocumentParser.calculateBlockParserFactories(customParserFactories, nodes); + List<BlockParserFactory> 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<BlockParserFactory> customParserFactories = Collections.emptyList(); + List<BlockParserFactory> customParserFactories = List.of(); Set<Class<? extends Block>> 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<Class<? extends Node>> getNodeTypes() { - return Collections.<Class<? extends Node>>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<Class<? extends Node>> getNodeTypes() { - return Collections.<Class<? extends Node>>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("<p><a href=\"/url\">link with special label</a></p>\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<? extends Node> nodeCl private static void assertSpans(Node rootNode, Class<? extends Node> 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<? extends Node> 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<Class<? extends Node>> getNodeTypes() { // Return the node types we want to use this renderer for. - return Collections.<Class<? extends Node>>singleton(IndentedCodeBlock.class); + return Set.of(IndentedCodeBlock.class); } @Override From cf6e03a5abac5d55f64a63bdb21c37e1f4775fba Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Fri, 26 Apr 2024 23:05:53 +1000 Subject: [PATCH 638/815] 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<URL> 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<Example> 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 <robin@nibor.org> Date: Fri, 26 Apr 2024 23:11:22 +1000 Subject: [PATCH 639/815] 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<? extends Extension> 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<? extends Extension> extensions) { * @return {@code this} */ public Builder enabledBlockTypes(Set<Class<? extends Block>> 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<? extends Extension> 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 <robin@nibor.org> Date: Sat, 27 Apr 2024 18:43:24 +1000 Subject: [PATCH 640/815] 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 @@ <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> - <version>1.17.5</version> + <version>1.37</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> - <version>1.17.5</version> + <version>1.37</version> </dependency> </dependencies> </dependencyManagement> From 80d7929cb8560dc8c77de027290d43d31054468e Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 27 Apr 2024 18:44:01 +1000 Subject: [PATCH 641/815] 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 <robin@nibor.org> Date: Sat, 27 Apr 2024 19:10:09 +1000 Subject: [PATCH 642/815] 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 <robin@nibor.org> Date: Sat, 27 Apr 2024 19:12:25 +1000 Subject: [PATCH 643/815] 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 <robin@nibor.org> Date: Sat, 27 Apr 2024 21:22:33 +1000 Subject: [PATCH 644/815] 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 <robin@nibor.org> Date: Sat, 27 Apr 2024 21:29:03 +1000 Subject: [PATCH 645/815] 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 <robin@nibor.org> Date: Sat, 27 Apr 2024 21:42:54 +1000 Subject: [PATCH 646/815] 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 @@ <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> - <version>1.5.0</version> + <version>3.2.0</version> <configuration> <executable>java</executable> <classpathScope>test</classpathScope> diff --git a/pom.xml b/pom.xml index 3e17f85ae..465c3b8e4 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.12.1</version> + <version>3.13.0</version> <configuration> <release>11</release> </configuration> @@ -46,17 +46,17 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.3.0</version> + <version>3.4.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-install-plugin</artifactId> - <version>3.0.1</version> + <version>3.1.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> - <version>3.5.0</version> + <version>3.6.3</version> <configuration> <excludePackageNames>*.internal,*.internal.*</excludePackageNames> <!-- The offline links make links from extensions to core work. --> @@ -73,7 +73,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>2.22.2</version> + <version>3.2.5</version> </plugin> </plugins> </pluginManagement> @@ -94,7 +94,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> - <version>3.0.0-M7</version> + <version>3.0.1</version> <configuration> <autoVersionSubmodules>true</autoVersionSubmodules> <useReleaseProfile>false</useReleaseProfile> @@ -186,7 +186,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> - <version>3.2.1</version> + <version>3.3.1</version> <executions> <execution> <id>attach-sources</id> @@ -211,7 +211,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-gpg-plugin</artifactId> - <version>3.0.1</version> + <version>3.2.4</version> <executions> <execution> <id>sign-artifacts</id> @@ -238,7 +238,7 @@ <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> - <version>0.8.8</version> + <version>0.8.12</version> <configuration> <excludes> <!-- Classes from test-util --> @@ -271,9 +271,6 @@ <developers> <developer> <name>Robin Stocker</name> - <email>rstocker@atlassian.com</email> - <organization>Atlassian</organization> - <organizationUrl>https://www.atlassian.com/</organizationUrl> </developer> </developers> From f14989bcdda958ae7249ffb3effe0883159ee2a3 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sun, 28 Apr 2024 11:57:20 +1000 Subject: [PATCH 647/815] 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 <robin@nibor.org> Date: Sat, 6 Apr 2024 18:08:08 +0700 Subject: [PATCH 648/815] 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 <robin@nibor.org> Date: Sun, 28 Apr 2024 12:26:53 +1000 Subject: [PATCH 649/815] 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.commonmark</groupId> + <artifactId>commonmark-parent</artifactId> + <version>0.22.1-SNAPSHOT</version> + </parent> + + <artifactId>commonmark-ext-footnotes</artifactId> + <name>commonmark-java extension for footnotes</name> + <description>commonmark-java extension for footnotes using [^1] syntax</description> + + <dependencies> + <dependency> + <groupId>org.commonmark</groupId> + <artifactId>commonmark</artifactId> + </dependency> + + <dependency> + <groupId>org.commonmark</groupId> + <artifactId>commonmark-test-util</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> 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 @@ +<html> +<body> +<b>Extension for footnotes using [^1] syntax</b> +<p>See {@link org.commonmark.ext.footnotes.FootnotesExtension}</p> +</body> +</html> 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<Extension> 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> T find(Node parent, Class<T> nodeClass) { + return Objects.requireNonNull(tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); + } + + private static <T> T tryFind(Node parent, Class<T> nodeClass) { + return findAll(parent, nodeClass).stream().findFirst().orElse(null); + } + + private static <T> List<T> findAll(Node parent, Class<T> nodeClass) { + var nodes = new ArrayList<T>(); + 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> T tryFind(Node parent, Class<T> nodeClass) { * could not be found. */ public static <T> T find(Node parent, Class<T> 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 @@ <modules> <module>commonmark</module> <module>commonmark-ext-autolink</module> + <module>commonmark-ext-footnotes</module> <module>commonmark-ext-gfm-strikethrough</module> <module>commonmark-ext-gfm-tables</module> <module>commonmark-ext-heading-anchor</module> @@ -63,7 +64,7 @@ <detectOfflineLinks>false</detectOfflineLinks> <offlineLinks> <offlineLink> - <url>http://static.javadoc.io/org.commonmark/commonmark/${project.version}/ + <url>https://static.javadoc.io/org.commonmark/commonmark/${project.version}/ </url> <location>${commonmark.javadoc.location}</location> </offlineLink> From 0c17ae84c059a4f8e95329e351e0a53d06c69ced Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 4 May 2024 15:36:45 +1000 Subject: [PATCH 650/815] 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 651/815] 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]): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark</artifactId> - <version>0.21.0</version> + <version>0.22.0</version> </dependency> ``` @@ -265,7 +265,7 @@ First, add an additional dependency (see [Maven Central] for others): <dependency> <groupId>org.commonmark</groupId> <artifactId>commonmark-ext-gfm-tables</artifactId> - <version>0.21.0</version> + <version>0.22.0</version> </dependency> ``` 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 652/815] 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<String> protocols; public DefaultUrlSanitizer() { - this(List.of("http", "https", "mailto")); + this(List.of("http", "https", "mailto", "data")); } public DefaultUrlSanitizer(Collection<String> protocols) { From 1fa49f469ef7113ad4e0ee40bafed01eb32c4dd9 Mon Sep 17 00:00:00 2001 From: kmh916 <kmh102808@gmail.com> Date: Wed, 15 May 2024 14:20:51 +0900 Subject: [PATCH 653/815] 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 <a href="http://spec.commonmark.org/0.18/#info-string">CommonMark spec</a> + * @see <a href="http://spec.commonmark.org/0.31.2/#info-string">CommonMark spec</a> */ 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 <a href="http://spec.commonmark.org/0.18/#html-blocks">CommonMark Spec</a> + * @see <a href="http://spec.commonmark.org/0.31.2/#html-blocks">CommonMark Spec</a> */ 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 <a href="http://spec.commonmark.org/0.24/#raw-html">CommonMark Spec</a> + * @see <a href="http://spec.commonmark.org/0.31.2/#raw-html">CommonMark Spec</a> */ 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 <a href="http://spec.commonmark.org/0.26/#links">CommonMark Spec for links</a> + * @see <a href="http://spec.commonmark.org/0.31.2/#links">CommonMark Spec for links</a> */ 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 <code>[foo]</code>. The definitions * themselves are usually not rendered in the final output. * - * @see <a href="https://spec.commonmark.org/0.29/#link-reference-definition">Link reference definitions</a> + * @see <a href="https://spec.commonmark.org/0.31.2/#link-reference-definition">Link reference definitions</a> */ 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 <a href="https://spec.commonmark.org/0.28/#tight">CommonMark Spec for tight lists</a> + * @see <a href="https://spec.commonmark.org/0.31.2/#tight">CommonMark Spec for tight lists</a> */ public boolean isTight() { return tight; From 92ef1d0a9f72ee24741b1da72c0ab07c06db7ad2 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 18 May 2024 11:35:09 +1000 Subject: [PATCH 654/815] 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 <robin@nibor.org> Date: Sat, 18 May 2024 15:31:38 +1000 Subject: [PATCH 655/815] 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> T find(Node parent, Class<T> 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; + private final List<BracketProcessor> 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<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory, List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, - IncludeSourceSpans includeSourceSpans) { + List<BracketProcessor> 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; + private final List<BracketProcessor> bracketProcessors; private final LinkReferenceDefinitions linkReferenceDefinitions; public InlineParserContextImpl(List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, + List<BracketProcessor> bracketProcessors, LinkReferenceDefinitions linkReferenceDefinitions) { this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; + this.bracketProcessors = bracketProcessors; this.linkReferenceDefinitions = linkReferenceDefinitions; } @@ -31,6 +35,11 @@ public List<DelimiterProcessor> getCustomDelimiterProcessors() { return delimiterProcessors; } + @Override + public List<BracketProcessor> 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<InlineContentParserFactory> inlineContentParserFactories; private final Map<Character, DelimiterProcessor> delimiterProcessors; + private final List<BracketProcessor> bracketProcessors; private final BitSet specialCharacters; private Map<Character, List<InlineContentParser>> 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<InlineContentParserFactory> calculateInlineContentParserFactories(L return list; } + private List<BracketProcessor> calculateBracketProcessors(List<BracketProcessor> 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<Character, DelimiterProcessor> calculateDelimiterProcessors(List<DelimiterProcessor> delimiterProcessors) { var map = new HashMap<Character, DelimiterProcessor>(); 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<DelimiterProcessor> getCustomDelimiterProcessors(); + /** + * TODO + */ + List<BracketProcessor> getCustomBracketProcessors(); + /** * Look up a {@link LinkReferenceDefinition} for a given label. * <p> 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<BlockParserFactory> blockParserFactories; private final List<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; + private final List<BracketProcessor> bracketProcessors; private final InlineParserFactory inlineParserFactory; private final List<PostProcessor> 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<BlockParserFactory> blockParserFactories = new ArrayList<>(); private final List<InlineContentParserFactory> inlineContentParserFactories = new ArrayList<>(); private final List<DelimiterProcessor> delimiterProcessors = new ArrayList<>(); + private final List<BracketProcessor> bracketProcessors = new ArrayList<>(); private final List<PostProcessor> postProcessors = new ArrayList<>(); private Set<Class<? extends Block>> 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<DelimiterProcessor> getCustomDelimiterProcessors() { return inlineParserContext.getCustomDelimiterProcessors(); } + @Override + public List<BracketProcessor> 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 <robin@nibor.org> Date: Sat, 18 May 2024 16:01:38 +1000 Subject: [PATCH 656/815] 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 <robin@nibor.org> Date: Sun, 19 May 2024 22:13:20 +1000 Subject: [PATCH 657/815] 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<BracketProcessor> bracketProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; - private final LinkReferenceDefinitions definitions = new LinkReferenceDefinitions(); + private final DefinitionMap<LinkReferenceDefinition> linkReferenceDefinitions = new DefinitionMap<>(); private final List<OpenBlockParser> openBlockParsers = new ArrayList<>(); private final List<BlockParser> 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; private final List<BracketProcessor> bracketProcessors; - private final LinkReferenceDefinitions linkReferenceDefinitions; + private final DefinitionMap<LinkReferenceDefinition> linkReferenceDefinitions; public InlineParserContextImpl(List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, List<BracketProcessor> bracketProcessors, - LinkReferenceDefinitions linkReferenceDefinitions) { + DefinitionMap<LinkReferenceDefinition> 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<String, LinkReferenceDefinition> 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 <V> the type of value + */ +public class DefinitionMap<V> { + + // LinkedHashMap for determinism and to preserve document order + private final Map<String, V> 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 <robin@nibor.org> Date: Sat, 25 May 2024 22:39:51 +1000 Subject: [PATCH 658/815] 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<DefinitionMap<?>> 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<Class<?>, DefinitionMap<?>> definitionsByType = new HashMap<>(); + + public <D> void addDefinitions(DefinitionMap<D> definitionMap) { + var existingMap = getMap(definitionMap.getType()); + if (existingMap == null) { + definitionsByType.put(definitionMap.getType(), definitionMap); + } else { + existingMap.addAll(definitionMap); + } + } + + public <V> V getDefinition(Class<V> type, String label) { + var definitionMap = getMap(type); + if (definitionMap == null) { + return null; + } + return definitionMap.get(label); + } + + private <V> DefinitionMap<V> getMap(Class<V> type) { + //noinspection unchecked + return (DefinitionMap<V>) 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<BracketProcessor> bracketProcessors; private final IncludeSourceSpans includeSourceSpans; private final DocumentBlockParser documentBlockParser; - private final DefinitionMap<LinkReferenceDefinition> linkReferenceDefinitions = new DefinitionMap<>(); + private final Definitions definitions = new Definitions(); private final List<OpenBlockParser> openBlockParsers = new ArrayList<>(); private final List<BlockParser> 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; private final List<BracketProcessor> bracketProcessors; - private final DefinitionMap<LinkReferenceDefinition> linkReferenceDefinitions; + private final Definitions definitions; public InlineParserContextImpl(List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, List<BracketProcessor> bracketProcessors, - DefinitionMap<LinkReferenceDefinition> 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<BracketProcessor> getCustomBracketProcessors() { @Override public LinkReferenceDefinition getLinkReferenceDefinition(String label) { - return linkReferenceDefinitions.get(label); + return definitions.getDefinition(LinkReferenceDefinition.class, label); + } + + @Override + public <D> D getDefinition(Class<D> 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<DefinitionMap<?>> 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<LinkReferenceDefinition> 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 <V> the type of value + * @param <D> the type of value */ -public class DefinitionMap<V> { +public class DefinitionMap<D> { + private final Class<D> type; // LinkedHashMap for determinism and to preserve document order - private final Map<String, V> definitions = new LinkedHashMap<>(); + private final Map<String, D> definitions = new LinkedHashMap<>(); + + public DefinitionMap(Class<D> type) { + this.type = type; + } + + public Class<D> getType() { + return type; + } + + public void addAll(DefinitionMap<D> 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<D> 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. * <p> - * 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. + * <p> + * 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> D getDefinition(Class<D> 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<DefinitionMap<?>> 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. * <p> @@ -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<DefinitionMap<?>> 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<BracketProcessor> getCustomBracketProcessors() { @Override public LinkReferenceDefinition getLinkReferenceDefinition(String label) { + return getDefinition(LinkReferenceDefinition.class, label); + } + + @Override + public <D> D getDefinition(Class<D> 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 <robin@nibor.org> Date: Sun, 26 May 2024 22:02:24 +1000 Subject: [PATCH 659/815] 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 <robin@nibor.org> Date: Sat, 1 Jun 2024 11:44:35 +1000 Subject: [PATCH 660/815] 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 (<code>[</code> or <code>![</code>) + * 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<? extends Node> 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<? extends Node> 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 <robin@nibor.org> Date: Sat, 1 Jun 2024 16:41:35 +1000 Subject: [PATCH 661/815] 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> T find(Node parent, Class<T> nodeClass) { return Objects.requireNonNull(tryFind(parent, nodeClass), "Could not find a " + nodeClass.getSimpleName() + " node in " + parent); } @@ -212,4 +212,9 @@ private static <T> List<T> findAll(Node parent, Class<T> 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 <robin@nibor.org> Date: Sat, 1 Jun 2024 17:02:16 +1000 Subject: [PATCH 662/815] 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> T find(Node parent, Class<T> 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 <robin@nibor.org> Date: Sat, 15 Jun 2024 00:41:41 +1000 Subject: [PATCH 663/815] 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<Character> 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. + * <p> + * Aims to match the rendering of cmark-gfm (which is slightly different from GitHub's when it comes to class + * attributes, not sure why). + * <p> + * Some notes on how rendering works: + * <ul> + * <li>Footnotes are numbered according to the order of references, starting at 1</li> + * <li>Definitions are rendered at the end of the document, regardless of where the definition was in the source</li> + * <li>Definitions are ordered by number</li> + * <li>Definitions have links back to their references (one or more)</li> + * </ul> + */ +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<FootnoteDefinition> 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<FootnoteDefinition, List<String>> references = new LinkedHashMap<>(); + /** + * The number of the definition (i.e. 1, 2). + */ + private final Map<FootnoteDefinition, Integer> definitionNumbers = new HashMap<>(); + + public FootnoteHtmlNodeRenderer(HtmlNodeRendererContext context) { + this.html = context.getWriter(); + this.context = context; + } + + @Override + public Set<Class<? extends Node>> 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<String, String>(); + 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<String, String>(); + 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<String> references) { + // <ol> etc + var id = definitionId(def.getLabel()); + var attrs = new LinkedHashMap<String, String>(); + 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 <p> ourselves here instead of delegating the rendering. + // What if the rendering was overwritten to not use <p>, 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<String> 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<String, String>(); + 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<FootnoteDefinition> 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<Class<? extends Node>> 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<Extension> 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", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-foo\">\n" + + "<p>note <a href=\"#fnref-foo\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn-BAR\" id=\"fnref-BAR\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-BAR\">\n" + + "<p>note <a href=\"#fnref-BAR\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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", + "<p>First <sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo\" data-footnote-ref>1</a></sup></p>\n" + + "<p>Then <sup class=\"footnote-ref\"><a href=\"#fn-bar\" id=\"fnref-bar\" data-footnote-ref>2</a></sup></p>\n" + + "<p>Then <sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo-2\" data-footnote-ref>1</a></sup> again</p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-foo\">\n" + + "<p>f <a href=\"#fnref-foo\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a> <a href=\"#fnref-foo-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1-2\" aria-label=\"Back to reference 1-2\"><sup class=\"footnote-ref\">2</sup>↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn-bar\">\n" + + "<p>b <a href=\"#fnref-bar\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\n"); + } + + @Test + public void testDefinitionWithTwoParagraphs() { + // With two paragraphs, the backref <a> should be added to the second one + assertRendering("Test [^foo]\n\n[^foo]: one\n \n two\n", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-foo\">\n" + + "<p>one</p>\n" + + "<p>two <a href=\"#fnref-foo\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\n"); + } + + @Test + public void testDefinitionWithList() { + assertRendering("Test [^foo]\n\n[^foo]:\n - one\n - two\n", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-foo\">\n" + + "<ul>\n" + + "<li>one</li>\n" + + "<li>two</li>\n" + + "</ul>\n" + + "<a href=\"#fnref-foo\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></li>\n" + + "</ol>\n" + + "</section>\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 = "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-foo\">\n" + + "<p>note! <a href=\"#fnref-foo\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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<Extension> 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<NodeRenderer> nodeRenderers = new ArrayList<>(); private final Map<Class<? extends Node>, NodeRenderer> renderers = new HashMap<>(32); public void add(NodeRenderer nodeRenderer) { - for (Class<? extends Node> 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<D> 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<String, String> 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<String, String> attrs, boolean voidElement) { append("<"); append(name); if (attrs != null && !attrs.isEmpty()) { - for (Map.Entry<String, String> 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 <robin@nibor.org> Date: Sat, 15 Jun 2024 16:13:48 +1000 Subject: [PATCH 664/815] 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<FootnoteDefinition> definitions; + private DefinitionMap<FootnoteDefinition> 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<FootnoteDefinition, List<String>> references = new LinkedHashMap<>(); - /** - * The number of the definition (i.e. 1, 2). - */ - private final Map<FootnoteDefinition, Integer> definitionNumbers = new HashMap<>(); + private final Map<FootnoteDefinition, ReferencedDefinition> 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<String, String>(); 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<String> references) { + private void renderDefinition(FootnoteDefinition def, ReferencedDefinition referencedDefinition) { // <ol> etc var id = definitionId(def.getLabel()); var attrs = new LinkedHashMap<String, String>(); @@ -147,24 +138,25 @@ private void renderDefinition(FootnoteDefinition def, int defNumber, List<String html.tag("p", context.extendAttributes(lastParagraph, "p", Map.of())); renderChildren(lastParagraph, null); html.raw(" "); - renderBackrefs(def, defNumber, references); + renderBackrefs(def, referencedDefinition); html.tag("/p"); html.line(); } else { renderChildren(def, null); html.line(); - renderBackrefs(def, defNumber, references); + renderBackrefs(def, referencedDefinition); } html.tag("/li"); html.line(); } - private void renderBackrefs(FootnoteDefinition def, int defNumber, List<String> 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<String, String>(); 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<String> references = new ArrayList<>(); + + ReferencedDefinition(int definitionNumber) { + this.definitionNumber = definitionNumber; + } + } } From b10bd57bbdaf363ec19cab8f76d4a60bdb6bc847 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 15 Jun 2024 16:21:03 +1000 Subject: [PATCH 665/815] 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 <p> 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 <p> ourselves here instead of delegating the rendering. - // What if the rendering was overwritten to not use <p>, 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 <robin@nibor.org> Date: Thu, 20 Jun 2024 22:07:45 +1000 Subject: [PATCH 666/815] 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 <robin@nibor.org> Date: Thu, 20 Jun 2024 22:13:37 +1000 Subject: [PATCH 667/815] 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 <robin@nibor.org> Date: Thu, 20 Jun 2024 22:22:50 +1000 Subject: [PATCH 668/815] 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 <robin@nibor.org> Date: Sat, 22 Jun 2024 21:46:46 +1000 Subject: [PATCH 669/815] 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 <robin@nibor.org> Date: Sat, 22 Jun 2024 22:15:20 +1000 Subject: [PATCH 670/815] 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> T find(Node parent, Class<T> 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 <robin@nibor.org> Date: Sat, 29 Jun 2024 11:31:43 +1000 Subject: [PATCH 671/815] 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 <robin@nibor.org> Date: Sat, 29 Jun 2024 11:45:47 +1000 Subject: [PATCH 672/815] 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; - private final List<BracketProcessor> bracketProcessors; + private final List<LinkProcessor> 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<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory, List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, - List<BracketProcessor> bracketProcessors, IncludeSourceSpans includeSourceSpans) { + List<LinkProcessor> 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; - private final List<BracketProcessor> bracketProcessors; + private final List<LinkProcessor> linkProcessors; private final Definitions definitions; public InlineParserContextImpl(List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, - List<BracketProcessor> bracketProcessors, + List<LinkProcessor> linkProcessors, Definitions definitions) { this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; - this.bracketProcessors = bracketProcessors; + this.linkProcessors = linkProcessors; this.definitions = definitions; } @@ -36,8 +36,8 @@ public List<DelimiterProcessor> getCustomDelimiterProcessors() { } @Override - public List<BracketProcessor> getCustomBracketProcessors() { - return bracketProcessors; + public List<LinkProcessor> 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<InlineContentParserFactory> inlineContentParserFactories; private final Map<Character, DelimiterProcessor> delimiterProcessors; - private final List<BracketProcessor> bracketProcessors; + private final List<LinkProcessor> linkProcessors; private final BitSet specialCharacters; private Map<Character, List<InlineContentParser>> 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<InlineContentParserFactory> calculateInlineContentParserFactories(L return list; } - private List<BracketProcessor> calculateBracketProcessors(List<BracketProcessor> 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<LinkProcessor> calculateLinkProcessors(List<LinkProcessor> 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<BracketProcessor> getCustomBracketProcessors(); + List<LinkProcessor> 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<BlockParserFactory> blockParserFactories; private final List<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; - private final List<BracketProcessor> bracketProcessors; + private final List<LinkProcessor> linkProcessors; private final InlineParserFactory inlineParserFactory; private final List<PostProcessor> 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<BlockParserFactory> blockParserFactories = new ArrayList<>(); private final List<InlineContentParserFactory> inlineContentParserFactories = new ArrayList<>(); private final List<DelimiterProcessor> delimiterProcessors = new ArrayList<>(); - private final List<BracketProcessor> bracketProcessors = new ArrayList<>(); + private final List<LinkProcessor> linkProcessors = new ArrayList<>(); private final List<PostProcessor> postProcessors = new ArrayList<>(); private Set<Class<? extends Block>> 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<DelimiterProcessor> getCustomDelimiterProcessors() { } @Override - public List<BracketProcessor> getCustomBracketProcessors() { - return inlineParserContext.getCustomBracketProcessors(); + public List<LinkProcessor> getCustomLinkProcessors() { + return inlineParserContext.getCustomLinkProcessors(); } @Override From a2258faba36836c97c862e96988ad9d2f7fe9cab Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 29 Jun 2024 15:03:24 +1000 Subject: [PATCH 673/815] 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. + * <p> + * Inline links: + * <pre> + * [text](destination) + * [text](destination "title") + * </pre> + * <p> + * Reference links, which have different subtypes. Full:: + * <pre> + * [text][label] + * </pre> + * Collapsed (label is ""): + * <pre> + * [text][] + * </pre> + * Shortcut (label is null): + * <pre> + * [text] + * </pre> + */ 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.: + * <pre> + * [foo][bar] + * ^ + * </pre> + */ Position afterTextBracket(); } From f028716e716ebecad518a3c14db3f0ecdd58e6af Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sun, 30 Jun 2024 21:54:14 +1000 Subject: [PATCH 674/815] 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 675/815] 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("<p><a rel=\"nofollow\" href=\"https://google.com\"></a></p>\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("<p><a rel=\"nofollow\" href=\"http://google.com\"></a></p>\n", sanitizeUrlsRenderer().render(paragraph)); + + paragraph = new Paragraph(); + link = new Link(); + link.setDestination("https://google.com"); + paragraph.appendChild(link); + assertEquals("<p><a rel=\"nofollow\" href=\"https://google.com\"></a></p>\n", sanitizeUrlsRenderer().render(paragraph)); + + paragraph = new Paragraph(); + link = new Link(); + link.setDestination("mailto:foo@bar.example.com"); + paragraph.appendChild(link); + assertEquals("<p><a rel=\"nofollow\" href=\"mailto:foo@bar.example.com\"></a></p>\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("<p><a rel=\"nofollow\" href=\"" + image + "\"></a></p>\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("<p><a rel=\"nofollow\" href=\"\"></a></p>\n", sanitizeUrlsRenderer().render(paragraph)); + + paragraph = new Paragraph(); + link = new Link(); + link.setDestination("ftp://google.com"); + paragraph.appendChild(link); + assertEquals("<p><a rel=\"nofollow\" href=\"\"></a></p>\n", sanitizeUrlsRenderer().render(paragraph)); } @Test From 3388891e196bcecab8cff943c7c8b33790f0e5f0 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Wed, 3 Jul 2024 22:18:16 +1000 Subject: [PATCH 676/815] 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<FootnoteDefinition, ReferencedDefinition> referencedDefinitions = new LinkedHashMap<>(); + /** + * Information about references that should be rendered as footnotes. + */ + private final Map<FootnoteReference, ReferenceInfo> references = new HashMap<>(); + public FootnoteHtmlNodeRenderer(HtmlNodeRendererContext context) { this.html = context.getWriter(); this.context = context; @@ -51,23 +57,69 @@ public Set<Class<? extends Node>> 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<String, String>(); + 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<String, String>(); 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<String, String>(); - 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) { // <ol> 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 </p>. 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<FootnoteDefinition> 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<FootnoteReference> consumer; + + private ReferenceVisitor(boolean inspectDefinitions, Consumer<FootnoteReference> 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() { "</section>\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", "<p><sup class=\"footnote-ref\"><a href=\"#fn-foo1\" id=\"fnref-foo1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-foo1\">\n" + + "<p>one <sup class=\"footnote-ref\"><a href=\"#fn-foo2\" id=\"fnref-foo2\" data-footnote-ref>2</a></sup> <a href=\"#fnref-foo1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn-foo2\">\n" + + "<p>two <a href=\"#fnref-foo2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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", "<p><sup class=\"footnote-ref\"><a href=\"#fn-foo\" id=\"fnref-foo\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-foo\">\n" + + "<p>foo <sup class=\"footnote-ref\"><a href=\"#fn-bar\" id=\"fnref-bar\" data-footnote-ref>2</a></sup> <a href=\"#fnref-foo\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn-bar\">\n" + + "<p>bar <a href=\"#fnref-bar\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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", "<p><sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-1\">\n" + + "<p>one <sup class=\"footnote-ref\"><a href=\"#fn-2\" id=\"fnref-2\" data-footnote-ref>2</a></sup><sup class=\"footnote-ref\"><a href=\"#fn-3\" id=\"fnref-3\" data-footnote-ref>3</a></sup> <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn-2\">\n" + + "<p>two <sup class=\"footnote-ref\"><a href=\"#fn-4\" id=\"fnref-4\" data-footnote-ref>4</a></sup> <a href=\"#fnref-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn-3\">\n" + + "<p>three <sup class=\"footnote-ref\"><a href=\"#fn-4\" id=\"fnref-4-2\" data-footnote-ref>4</a></sup> <a href=\"#fnref-3\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"3\" aria-label=\"Back to reference 3\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn-4\">\n" + + "<p>four <a href=\"#fnref-4\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"4\" aria-label=\"Back to reference 4\">↩</a> <a href=\"#fnref-4-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"4-2\" aria-label=\"Back to reference 4-2\"><sup class=\"footnote-ref\">2</sup>↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\n"); + } + + @Test + public void testNestedFootnotesCycle() { + // Footnotes can contain cycles, lol. + assertRendering("[^foo1]\n" + + "\n" + + "[^foo1]: one [^foo2]\n" + + "[^foo2]: two [^foo1]\n", "<p><sup class=\"footnote-ref\"><a href=\"#fn-foo1\" id=\"fnref-foo1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-foo1\">\n" + + "<p>one <sup class=\"footnote-ref\"><a href=\"#fn-foo2\" id=\"fnref-foo2\" data-footnote-ref>2</a></sup> <a href=\"#fnref-foo1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a> <a href=\"#fnref-foo1-2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1-2\" aria-label=\"Back to reference 1-2\"><sup class=\"footnote-ref\">2</sup>↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn-foo2\">\n" + + "<p>two <sup class=\"footnote-ref\"><a href=\"#fn-foo1\" id=\"fnref-foo1-2\" data-footnote-ref>1</a></sup> <a href=\"#fnref-foo2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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", "<p><sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-1\">\n" + + "<p>one <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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 <robin@nibor.org> Date: Fri, 5 Jul 2024 22:36:56 +1000 Subject: [PATCH 677/815] 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 @@ * <li>Definitions are ordered by number</li> * <li>Definitions have links back to their references (one or more)</li> * </ul> + * + * <h4>Nested footnotes</h4> + * 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). + * <p> + * In other words, footnotes form a directed graph. Footnotes can reference each other so cycles are possible too. + * <p> + * 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. + * <p> + * The way we implement it: + * <ol> + * <li>Start with the references in the main text; we can render them as we go</li> + * <li>After the main text is rendered, we have the referenced definitions, but there might be more from definition text</li> + * <li>To find the remaining definitions, we visit the definitions from before to look at references</li> + * <li>Repeat (breadth-first search) until we've found all definitions (note that we can't render before that's done because of backrefs)</li> + * <li>Now render the definitions (and any references inside)</li> + * </ol> + * 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<FootnoteDefinition, ReferencedDefinition> 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<FootnoteReference, ReferenceInfo> references = new HashMap<>(); @@ -56,21 +80,20 @@ public Set<Class<? extends Node>> 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<FootnoteReference> consumer; - private ReferenceVisitor(boolean inspectDefinitions, Consumer<FootnoteReference> consumer) { - this.inspectDefinitions = inspectDefinitions; + private ReferenceVisitor(Consumer<FootnoteReference> 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() { "</section>\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 <robin@nibor.org> Date: Sat, 6 Jul 2024 21:56:07 +1000 Subject: [PATCH 678/815] 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 <robin@nibor.org> Date: Sat, 6 Jul 2024 22:04:29 +1000 Subject: [PATCH 679/815] 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.: + * <pre><code> + * [^foo]: This is the footnote text + * </code></pre> + * 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. + * <p> + * 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. <code>[^foo]</code> in <code>Some text with a footnote[^foo]</code> + * <p> + * 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: + * <pre><code> + * Some text with a footnote[^1]. + * + * [^1]: The text of the footnote. + * </code></pre> + * The <code>[^1]</code> is a {@link FootnoteReference}, with "1" being the label. + * <p> + * The line with <code>[^1]: ...</code> is a {@link FootnoteDefinition}, with the contents as child nodes (can be a + * paragraph like in the example, or other blocks like lists). + * <p> + * 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 <a href="https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes">GitHub docs for footnotes</a> */ 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. <code>[^foo]</code> 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 <robin@nibor.org> Date: Sat, 6 Jul 2024 23:05:17 +1000 Subject: [PATCH 680/815] 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<DelimiterProcessor> getCustomDelimiterProcessors(); /** - * TODO + * @return custom link processors that have been configured with {@link Parser.Builder#linkProcessor}. */ List<LinkProcessor> 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. + * <p> + * 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 @@ * <pre> * [text] * </pre> + * 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: + * <ul> + * <li>{@link OpenerType#LINK} for links like {@code [text...}</li> + * <li>{@link OpenerType#IMAGE} for images like {@code ![text...}</li> + * </ul> + */ 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. + * <p> + * 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: + * <p> + * <pre><code> + * [text](destination) + * [text] + * [text][] + * [text][label] + * </pre> + * And images: + * <pre><code> + * ![text](destination) + * ![text] + * ![text][] + * ![text][label] + * </pre> + * See {@link LinkInfo} for accessing various parts of the parsed link/image. + * <p> + * 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: + * <pre><code> + * [my *text*](destination) + * </code></pre> + * 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: + * <pre><code> + * [^foo] + * </code></pre> + * 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 <robin@nibor.org> Date: Sat, 6 Jul 2024 23:15:54 +1000 Subject: [PATCH 681/815] 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 <D> the type of value @@ -28,13 +29,16 @@ public Class<D> getType() { public void addAll(DefinitionMap<D> 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. + * <p> + * 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<String> keySet() { + return definitions.keySet(); + } + public Collection<D> values() { return definitions.values(); } From ee7b710649717a93ab02dd98b0db147ed44eb717 Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sun, 7 Jul 2024 22:41:59 +1000 Subject: [PATCH 682/815] 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] - * </pre> + * </code></pre> * And images: * <pre><code> * ![text](destination) * ![text] * ![text][] * ![text][label] - * </pre> + * </code></pre> * See {@link LinkInfo} for accessing various parts of the parsed link/image. * <p> * 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 <dev.ymalcev@gmail.com> Date: Mon, 8 Jul 2024 10:52:49 +0200 Subject: [PATCH 683/815] 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 <lo.zampino@gmail.com> Date: Tue, 27 Aug 2024 15:55:24 +0200 Subject: [PATCH 684/815] 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 <lo.zampino@gmail.com> Date: Wed, 28 Aug 2024 10:05:02 +0200 Subject: [PATCH 685/815] 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-<version>,maven-mvnd-<version>-<platform>}/<hash> +[ -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 <robin@nibor.org> Date: Sat, 13 Jul 2024 21:21:42 +1000 Subject: [PATCH 686/815] 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). + * <p> + * There is also optional support for inline footnotes, use {@link #builder()} and then set {@link Builder#inlineFootnotes}. * * @see <a href="https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes">GitHub docs for footnotes</a> */ @@ -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<Character> getSpecialCharacters() { } }); } + + public static class Builder { + + private boolean inlineFootnotes = false; + + /** + * Enable support for inline footnotes without definitions, e.g.: + * <pre> + * Some text^[this is an inline footnote] + * </pre> + */ + 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. <code>[^foo]</code> into a {@link FootnoteReference}. + * For turning e.g. <code>[^foo]</code> into a {@link FootnoteReference}, + * and <code>^[foo]</code> 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<Character> 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. * <p> @@ -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.: * <pre> * [foo][bar] * ^ From be9a27fa431372ad94b73097c130c0257a0c219c Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Sat, 13 Jul 2024 23:34:45 +1000 Subject: [PATCH 687/815] 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<Character> 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 (<code>[</code>) or images (<code>![</code>). + * 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; private final List<LinkProcessor> linkProcessors; + private final Set<Character> 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<BlockParserFactory> blockParserFactories, InlineParserFactory inlineParserFactory, List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, - List<LinkProcessor> linkProcessors, IncludeSourceSpans includeSourceSpans) { + List<LinkProcessor> linkProcessors, Set<Character> 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; private final List<LinkProcessor> linkProcessors; + private final Set<Character> linkMarkers; private final Definitions definitions; public InlineParserContextImpl(List<InlineContentParserFactory> inlineContentParserFactories, List<DelimiterProcessor> delimiterProcessors, List<LinkProcessor> linkProcessors, + Set<Character> linkMarkers, Definitions definitions) { this.inlineContentParserFactories = inlineContentParserFactories; this.delimiterProcessors = delimiterProcessors; this.linkProcessors = linkProcessors; + this.linkMarkers = linkMarkers; this.definitions = definitions; } @@ -40,6 +44,11 @@ public List<LinkProcessor> getCustomLinkProcessors() { return linkProcessors; } + @Override + public Set<Character> 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<Character, DelimiterProcessor> delimiterProcessors; private final List<LinkProcessor> linkProcessors; private final BitSet specialCharacters; + private final BitSet linkMarkers; private Map<Character, List<InlineContentParser>> 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<InlineContentParserFactory> calculateInlineContentParserFactories(List<InlineContentParserFactory> customFactories) { @@ -104,9 +106,19 @@ private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterPr } } - private static BitSet calculateSpecialCharacters(Set<Character> delimiterCharacters, + private static BitSet calculateLinkMarkers(Set<Character> linkMarkers) { + var bitSet = new BitSet(); + for (var c : linkMarkers) { + bitSet.set(c); + } + bitSet.set('!'); + return bitSet; + } + + private static BitSet calculateSpecialCharacters(BitSet linkMarkers, + Set<Character> delimiterCharacters, List<InlineContentParserFactory> inlineContentParserFactories) { - BitSet bitSet = new BitSet(); + BitSet bitSet = (BitSet) linkMarkers.clone(); for (Character c : delimiterCharacters) { bitSet.set(c); } @@ -185,8 +197,6 @@ private List<? extends Node> parseInline() { switch (c) { case '[': return List.of(parseOpenBracket()); - case '!': - return parseBang(); case ']': return List.of(parseCloseBracket()); case '\n': @@ -195,6 +205,16 @@ private List<? extends Node> 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<? extends Node> parseBang() { - var bangPosition = scanner.position(); + private List<? extends Node> 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<LinkProcessor> getCustomLinkProcessors(); + /** + * @return custom link markers that have been configured with {@link Parser.Builder#linkMarker}. + */ + Set<Character> getCustomLinkMarkers(); + /** * Look up a {@link LinkReferenceDefinition} for a given label. * <p> 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<InlineContentParserFactory> inlineContentParserFactories; private final List<DelimiterProcessor> delimiterProcessors; private final List<LinkProcessor> linkProcessors; + private final Set<Character> linkMarkers; private final InlineParserFactory inlineParserFactory; private final List<PostProcessor> 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<DelimiterProcessor> delimiterProcessors = new ArrayList<>(); private final List<LinkProcessor> linkProcessors = new ArrayList<>(); private final List<PostProcessor> postProcessors = new ArrayList<>(); + private final Set<Character> linkMarkers = new HashSet<>(); private Set<Class<? extends Block>> 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. + * <p> + * 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. * <p> * Inline links: * <pre> @@ -24,28 +23,15 @@ * <pre> * [text] * </pre> - * 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: - * <ul> - * <li>{@link OpenerType#LINK} for links like {@code [text...}</li> - * <li>{@link OpenerType#IMAGE} for images like {@code ![text...}</li> - * </ul> + * 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<Character> 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<LinkProcessor> getCustomLinkProcessors() { return inlineParserContext.getCustomLinkProcessors(); } + @Override + public Set<Character> 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 <robin@nibor.org> Date: Sat, 7 Sep 2024 17:39:02 +1000 Subject: [PATCH 688/815] 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<FootnoteDefinition, ReferencedDefinition> referencedDefinitions = new LinkedHashMap<>(); + private final Map<Node, ReferencedDefinition> 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<Class<? extends Node>> 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<String, String>(); 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) { - // <ol> etc - var id = definitionId(def.getLabel()); + private void renderDefinition(Node def, ReferencedDefinition referencedDefinition) { var attrs = new LinkedHashMap<String, String>(); - 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<String> 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() { "</section>\n"); } + @Test + public void testInlineFootnotes() { + assertRenderingInline("Test ^[inline *footnote*]", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn1\">\n" + + "<p>inline <em>footnote</em> <a href=\"#fnref1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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 <robin@nibor.org> Date: Wed, 11 Sep 2024 23:39:17 +1000 Subject: [PATCH 689/815] 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<FootnoteReference, ReferenceInfo> references = new HashMap<>(); + private final Map<Node, ReferenceInfo> 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<FootnoteReference> 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<Node> consumer; - private ReferenceVisitor(Consumer<FootnoteReference> consumer) { + private ShallowReferenceVisitor(Node parent, Consumer<Node> 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() { "</section>\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]]", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn1\">\n" + + "<p>inline <sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\" data-footnote-ref>2</a></sup> <a href=\"#fnref1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn2\">\n" + + "<p>nested <a href=\"#fnref2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn1\">\n" + + "<p>inline <sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>2</a></sup> <a href=\"#fnref1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn-1\">\n" + + "<p>normal <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\n"); + } + + @Test + public void testInlineFootnoteInsideDefinition() { + assertRenderingInline("Test [^1]\n" + + "\n" + + "[^1]: Definition ^[inline]\n", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-1\">\n" + + "<p>Definition <sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\" data-footnote-ref>2</a></sup> <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn2\">\n" + + "<p>inline <a href=\"#fnref2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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", + "<p>Test <sup class=\"footnote-ref\"><a href=\"#fn-1\" id=\"fnref-1\" data-footnote-ref>1</a></sup></p>\n" + + "<section class=\"footnotes\" data-footnotes>\n" + + "<ol>\n" + + "<li id=\"fn-1\">\n" + + "<p>Definition <sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\" data-footnote-ref>2</a></sup> <sup class=\"footnote-ref\"><a href=\"#fn3\" id=\"fnref3\" data-footnote-ref>3</a></sup> <a href=\"#fnref-1\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn2\">\n" + + "<p>inline <sup class=\"footnote-ref\"><a href=\"#fn4\" id=\"fnref4\" data-footnote-ref>4</a></sup> <a href=\"#fnref2\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"2\" aria-label=\"Back to reference 2\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn3\">\n" + + "<p>foo <a href=\"#fnref3\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"3\" aria-label=\"Back to reference 3\">↩</a></p>\n" + + "</li>\n" + + "<li id=\"fn4\">\n" + + "<p>nested <a href=\"#fnref4\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"4\" aria-label=\"Back to reference 4\">↩</a></p>\n" + + "</li>\n" + + "</ol>\n" + + "</section>\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 @@ +<!DOCTYPE html> + +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>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 690/815] 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 691/815] 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 692/815] 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 693/815] 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 694/815] 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 695/815] 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 696/815] 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 697/815] 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 698/815] 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 699/815] [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 700/815] [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 701/815] 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 702/815] 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 703/815] 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 704/815] 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 705/815] 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 706/815] 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 707/815] 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 708/815] 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 709/815] 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 710/815] 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 711/815] 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 712/815] 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 713/815] 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 714/815] 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 715/815] 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 716/815] 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 717/815] 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 718/815] 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 719/815] 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 720/815] 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 721/815] 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 722/815] 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 723/815] [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 724/815] [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 725/815] 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 726/815] 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 727/815] 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 728/815] 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 729/815] 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 730/815] 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 731/815] 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 732/815] 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 733/815] 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 734/815] 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 735/815] 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 736/815] 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 737/815] 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 738/815] 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 739/815] 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 740/815] 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 741/815] 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 742/815] 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 743/815] 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 744/815] 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 745/815] 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 746/815] 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 747/815] 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 748/815] 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 749/815] 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 750/815] 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 751/815] 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 752/815] [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 753/815] [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 754/815] 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 755/815] 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 756/815] 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 757/815] 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 758/815] [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 759/815] [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 760/815] 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 761/815] 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 762/815] 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 763/815] 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 764/815] 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 765/815] 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 766/815] 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 767/815] 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 768/815] [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 769/815] [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 770/815] 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 771/815] 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 772/815] 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 773/815] 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 774/815] 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 775/815] 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 776/815] 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 777/815] 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 778/815] 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 779/815] 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 780/815] 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 781/815] 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 782/815] [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 783/815] [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 784/815] 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 785/815] 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 786/815] 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 787/815] 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 788/815] 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 789/815] 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 790/815] 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 791/815] [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 792/815] [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 793/815] README: Bump version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 702cc3abc..a917167f6 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.27.0 + 0.27.1 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.27.0 + 0.27.1 ``` From af45936db125ed9e29b2229551c41425e8bd8162 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:55:37 +0000 Subject: [PATCH 794/815] Bump org.assertj:assertj-core from 3.27.3 to 3.27.7 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.7. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.7) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.7 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b3a8e2b9f..d55d1fd85 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ org.assertj assertj-core - 3.27.3 + 3.27.7 org.openjdk.jmh From 057f13dd8e9472a45c714ad0c06a1a425ec21f61 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Tue, 3 Mar 2026 17:02:16 +0100 Subject: [PATCH 795/815] Add GFM alerts extension Co-Authored-By: Claude Opus 4.6 --- commonmark-ext-gfm-alerts/README.md | 70 +++ commonmark-ext-gfm-alerts/pom.xml | 27 + .../src/main/java/module-info.java | 5 + .../org/commonmark/ext/gfm/alerts/Alert.java | 19 + .../ext/gfm/alerts/AlertsExtension.java | 117 +++++ .../internal/AlertHtmlNodeRenderer.java | 77 +++ .../internal/AlertMarkdownNodeRenderer.java | 38 ++ .../alerts/internal/AlertNodeRenderer.java | 22 + .../alerts/internal/AlertPostProcessor.java | 170 ++++++ .../src/main/javadoc/overview.html | 6 + .../alerts/AlertsMarkdownRendererTest.java | 73 +++ .../ext/gfm/alerts/AlertsSpecTest.java | 43 ++ .../commonmark/ext/gfm/alerts/AlertsTest.java | 140 +++++ .../gfm/alerts/examples/AlertsExample.java | 94 ++++ .../src/test/resources/alerts-spec.txt | 492 ++++++++++++++++++ pom.xml | 1 + 16 files changed, 1394 insertions(+) create mode 100644 commonmark-ext-gfm-alerts/README.md create mode 100644 commonmark-ext-gfm-alerts/pom.xml create mode 100644 commonmark-ext-gfm-alerts/src/main/java/module-info.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java create mode 100644 commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java create mode 100644 commonmark-ext-gfm-alerts/src/main/javadoc/overview.html create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsMarkdownRendererTest.java create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsSpecTest.java create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/AlertsTest.java create mode 100644 commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java create mode 100644 commonmark-ext-gfm-alerts/src/test/resources/alerts-spec.txt diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md new file mode 100644 index 000000000..b70584e9b --- /dev/null +++ b/commonmark-ext-gfm-alerts/README.md @@ -0,0 +1,70 @@ +# commonmark-ext-gfm-alerts + +Extension for [commonmark-java](https://github.com/commonmark/commonmark-java) that adds support for [GitHub Flavored Markdown alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts). + +Enables highlighting important information using blockquote syntax with five standard alert types: NOTE, TIP, IMPORTANT, WARNING, and CAUTION. + +## Usage + +#### Markdown Syntax + +```markdown +> [!NOTE] +> Useful information + +> [!WARNING] +> Critical information +``` + +#### Standard GFM Types + +```java +Extension extension = AlertsExtension.create(); +Parser parser = Parser.builder().extensions(List.of(extension)).build(); +HtmlRenderer renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); +``` + +#### Custom Alert Types + +Add custom types beyond the five standard GFM types: + +```java +Extension extension = AlertsExtension.builder() + .addCustomType("INFO", "Information") + .build(); +``` + +Custom types must be UPPERCASE and cannot override standard types. + +#### Styling + +Alerts render as `
    ` elements with CSS classes: + +```html +
    +

    Note

    +

    Content

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

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

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

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

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

    Information

    \n" + + "

    Custom alert

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

    Information

    \n" + + "

    Info content

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

    Success!

    \n" + + "

    Success content

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

    Danger!

    \n" + + "

    Danger content

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

    Note

    \n" + + "

    Standard type

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

    Nota

    \n" + + "

    Localized title

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

    Note

    +

    This is a note

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

    Tip

    +

    This is a tip

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

    Important

    +

    This is important

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

    Warning

    +

    This is a warning

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

    Caution

    +

    This is a caution

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

    Note

    +

    Content

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

    Note

    +

    Content

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

    Note

    +

    Content

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

    Note

    +

    First paragraph

    +

    Second paragraph

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

    Tip

    +

    This is bold and italic

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

    Tip

    +

    Code:

    +
    function() { }
    +
    +

    End

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

    Important

    +

    Items:

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

    Note

    +

    Check out this link for more info

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

    Important

    +

    Heading

    +

    Content below heading

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

    Note

    +

    First

    +

    After empty lines

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

    [!NOTE] Some text

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

    [!INVALID] +Some text

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

    [!INFO] +Should be blockquote

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

    [!NOTE]

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

    [!TIP]

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

    [! NOTE] +Should be blockquote

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

    !NOTE +Should be blockquote

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

    [NOTE] +Should be blockquote

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

    This is a regular blockquote

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

    Note

    +

    This is a note

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

    Warning

    +

    Be careful

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

    Important

    +

    Content

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

    [!NOTE]

    +
    +

    Some text

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

    Note

    +

    This is an alert

    +
    +
    +

    This is a blockquote

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

    Note

    +

    First alert

    +
    +
    +

    Warning

    +

    Second alert

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

    Note

    +

    This is a note

    +
    +

    [!WARNING] +Nested content

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

    Note

    +

    This is a note

    +
    +

    Nested blockquote

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

      [!NOTE] +Test

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

    Note

    +

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

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

    Note

    +

    First line +Lazy continuation +Continues alert

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

    Regular blockquote +[!NOTE] +More text

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

    [!INVALID] +

    [!INVALID]
    Some text

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

    [!INFO] +

    [!INFO]
    Should be blockquote

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

    [! NOTE] +

    [! NOTE]
    Should be blockquote

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

    !NOTE +

    !NOTE
    Should be blockquote

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

    [NOTE] +

    [NOTE]
    Should be blockquote

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

    Note

    This is a note

    -

    [!WARNING] +

    [!WARNING]
    Nested content

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

      [!NOTE] +

      [!NOTE]
      Test

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

      Note

      -

      This is a note -[!WARNING] +

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

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

      Note

      -

      First line -Lazy continuation +

      First line
      +Lazy continuation
      Continues alert

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

      Regular blockquote -[!NOTE] +

      Regular blockquote
      +[!NOTE]
      More text

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

      ", "

      \n

      "); + return html.replace("\r\n", "\n").lines() + .map(String::stripTrailing) + .reduce((a, b) -> a + "\n" + b) + .orElse("") + .strip(); + } +} \ No newline at end of file From f3be489803f3446e610f2948fd77d4e821a5cefd Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Mon, 30 Mar 2026 10:55:31 +0200 Subject: [PATCH 797/815] Address review feedback: simplify PostProcessor and clean up code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace AbstractVisitor with direct Document child iteration (only top-level block quotes are alerts, no need to walk the full tree) - Remove unnecessary isContentWhitespaceOnly method (the parser never produces content nodes for whitespace-only block quote continuations) - Remove redundant markerText/literal variables, use textNode.getLiteral() - Restructure afterMarker checks to avoid 3x instanceof repetition - Add Locale.ROOT to toUpperCase() calls (Turkish locale safety) - Use var throughout all main source files - Use single get()+null check instead of containsKey()+get() in renderer - Add proper Node import in AlertNodeRenderer, remove FQN references - LinkedHashMap → HashMap in AlertsExtension (ordering not needed) - Update README: fix outdated custom type docs, align examples with AlertsExample.java - Add screenshots showing rendered alerts with and without icons Co-Authored-By: Claude Opus 4.6 --- commonmark-ext-gfm-alerts/README.md | 18 +- .../screenshots/alerts-with-icons.png | Bin 0 -> 20571 bytes .../screenshots/alerts.png | Bin 0 -> 19824 bytes .../ext/gfm/alerts/AlertsExtension.java | 11 +- .../internal/AlertHtmlNodeRenderer.java | 15 +- .../internal/AlertMarkdownNodeRenderer.java | 4 +- .../alerts/internal/AlertNodeRenderer.java | 7 +- .../alerts/internal/AlertPostProcessor.java | 198 ++++++------------ .../gfm/alerts/examples/AlertsExample.java | 41 ++-- 9 files changed, 116 insertions(+), 178 deletions(-) create mode 100644 commonmark-ext-gfm-alerts/screenshots/alerts-with-icons.png create mode 100644 commonmark-ext-gfm-alerts/screenshots/alerts.png diff --git a/commonmark-ext-gfm-alerts/README.md b/commonmark-ext-gfm-alerts/README.md index b70584e9b..2368812e5 100644 --- a/commonmark-ext-gfm-alerts/README.md +++ b/commonmark-ext-gfm-alerts/README.md @@ -19,9 +19,9 @@ Enables highlighting important information using blockquote syntax with five sta #### Standard GFM Types ```java -Extension extension = AlertsExtension.create(); -Parser parser = Parser.builder().extensions(List.of(extension)).build(); -HtmlRenderer renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); +var extension = AlertsExtension.create(); +var parser = Parser.builder().extensions(List.of(extension)).build(); +var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build(); ``` #### Custom Alert Types @@ -29,12 +29,12 @@ HtmlRenderer renderer = HtmlRenderer.builder().extensions(List.of(extension)).bu Add custom types beyond the five standard GFM types: ```java -Extension extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") +var extension = AlertsExtension.builder() + .addCustomType("BUG", "Known Bug") .build(); ``` -Custom types must be UPPERCASE and cannot override standard types. +Custom types must be UPPERCASE. Standard type titles can also be overridden for localization. #### Styling @@ -63,7 +63,11 @@ Basic CSS example: .markdown-alert-caution { border-color: #cf222e; background-color: #ffebe9; } ``` -Icons can be added using CSS `::before` pseudo-elements with GitHub's [Octicons](https://primer.style/octicons/) (info, light-bulb, report, alert, stop icons). +![Alerts](screenshots/alerts.png) + +Icons can be added using GitHub's [Octicons](https://primer.style/octicons/): + +![Alerts with icons](screenshots/alerts-with-icons.png) ## License diff --git a/commonmark-ext-gfm-alerts/screenshots/alerts-with-icons.png b/commonmark-ext-gfm-alerts/screenshots/alerts-with-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..47da9402b557d4f1d8fac1f1d9dd7b2cf73b4b14 GIT binary patch literal 20571 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 literal 0 HcmV?d00001 diff --git a/commonmark-ext-gfm-alerts/screenshots/alerts.png b/commonmark-ext-gfm-alerts/screenshots/alerts.png new file mode 100644 index 0000000000000000000000000000000000000000..83d4009f017bea3a0fe5d9fcc1aee6c1908964e8 GIT binary patch literal 19824 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 literal 0 HcmV?d00001 diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java index 63a69f746..3990034d2 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java @@ -13,8 +13,9 @@ import org.commonmark.renderer.markdown.MarkdownNodeRendererFactory; import org.commonmark.renderer.markdown.MarkdownRenderer; +import java.util.HashMap; +import java.util.Locale; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -34,7 +35,7 @@ public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.Htm private final Map customTypes; private AlertsExtension(Builder builder) { - this.customTypes = new LinkedHashMap<>(builder.customTypes); + this.customTypes = new HashMap<>(builder.customTypes); } public static Extension create() { @@ -47,7 +48,7 @@ public static Builder builder() { @Override public void extend(Parser.Builder parserBuilder) { - Set allowedTypes = new HashSet<>(STANDARD_TYPES); + var allowedTypes = new HashSet<>(STANDARD_TYPES); allowedTypes.addAll(customTypes.keySet()); parserBuilder.postProcessor(new AlertPostProcessor(allowedTypes)); } @@ -81,7 +82,7 @@ public Set getSpecialCharacters() { * Builder for configuring the alerts extension. */ public static class Builder { - private final Map customTypes = new LinkedHashMap<>(); + private final Map customTypes = new HashMap<>(); /** * Adds a custom alert type with a display title. @@ -100,7 +101,7 @@ public Builder addCustomType(String type, String title) { if (title == null || title.isEmpty()) { throw new IllegalArgumentException("Title must not be null or empty"); } - if (!type.equals(type.toUpperCase())) { + if (!type.equals(type.toUpperCase(Locale.ROOT))) { throw new IllegalArgumentException("Type must be uppercase: " + type); } customTypes.put(type, title); diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index 202c20290..763b2e217 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -22,11 +22,11 @@ public AlertHtmlNodeRenderer(HtmlNodeRendererContext context, Map attributes = new LinkedHashMap<>(); + var attributes = new LinkedHashMap(); attributes.put("class", "markdown-alert markdown-alert-" + cssClass); attributes.put("data-alert-type", cssClass); @@ -47,8 +47,9 @@ protected void renderAlert(Alert alert) { } private String getAlertTitle(String type) { - if (customTypeTitles.containsKey(type)) { - return customTypeTitles.get(type); + var customTypeTitle = customTypeTitles.get(type); + if (customTypeTitle != null) { + return customTypeTitle; } switch (type) { case "NOTE": @@ -67,9 +68,9 @@ private String getAlertTitle(String type) { } private void renderChildren(Node parent) { - Node node = parent.getFirstChild(); + var node = parent.getFirstChild(); while (node != null) { - Node next = node.getNext(); + var next = node.getNext(); context.render(node); node = next; } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java index edc988c94..e3da62aea 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertMarkdownNodeRenderer.java @@ -28,9 +28,9 @@ protected void renderAlert(Alert alert) { } private void renderChildren(Node parent) { - Node node = parent.getFirstChild(); + var node = parent.getFirstChild(); while (node != null) { - Node next = node.getNext(); + var next = node.getNext(); context.render(node); node = next; } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java index dcb1a25eb..45b34bb46 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertNodeRenderer.java @@ -1,6 +1,7 @@ package org.commonmark.ext.gfm.alerts.internal; import org.commonmark.ext.gfm.alerts.Alert; +import org.commonmark.node.Node; import org.commonmark.renderer.NodeRenderer; import java.util.Set; @@ -8,13 +9,13 @@ public abstract class AlertNodeRenderer implements NodeRenderer { @Override - public Set> getNodeTypes() { + public Set> getNodeTypes() { return Set.of(Alert.class); } @Override - public void render(org.commonmark.node.Node node) { - Alert alert = (Alert) node; + public void render(Node node) { + var alert = (Alert) node; renderAlert(alert); } diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java index 0d02c3527..b524adc05 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java @@ -1,16 +1,21 @@ package org.commonmark.ext.gfm.alerts.internal; import org.commonmark.ext.gfm.alerts.Alert; -import org.commonmark.node.*; +import org.commonmark.node.BlockQuote; +import org.commonmark.node.HardLineBreak; +import org.commonmark.node.Node; +import org.commonmark.node.Paragraph; +import org.commonmark.node.SoftLineBreak; +import org.commonmark.node.Text; import org.commonmark.parser.PostProcessor; +import java.util.Locale; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; public class AlertPostProcessor implements PostProcessor { - // Case-insensitive matching for alert type (GitHub supports any case) + // Alert type marker, matching any case (GitHub supports lowercase, mixed, and uppercase) private static final Pattern ALERT_PATTERN = Pattern.compile("^\\[!([a-zA-Z]+)]\\s*$"); private final Set allowedTypes; @@ -21,150 +26,85 @@ public AlertPostProcessor(Set allowedTypes) { @Override public Node process(Node document) { - AlertVisitor visitor = new AlertVisitor(allowedTypes); - document.accept(visitor); + // Only look at direct children of Document — GitHub only detects alerts at the top level. + var child = document.getFirstChild(); + while (child != null) { + var next = child.getNext(); + if (child instanceof BlockQuote) { + tryConvertToAlert((BlockQuote) child); + } + child = next; + } return document; } - private static class AlertVisitor extends AbstractVisitor { - - private final Set allowedTypes; - - AlertVisitor(Set allowedTypes) { - this.allowedTypes = allowedTypes; + private void tryConvertToAlert(BlockQuote blockQuote) { + var firstChild = blockQuote.getFirstChild(); + if (!(firstChild instanceof Paragraph)) { + return; } - @Override - public void visit(BlockQuote blockQuote) { - // Only convert top-level block quotes (direct children of Document). - // This matches GitHub's behavior where alerts are only detected at the document level. - if (blockQuote.getParent() instanceof Document) { - if (tryConvertToAlert(blockQuote)) { - return; - } - } - visitChildren(blockQuote); + var paragraph = (Paragraph) firstChild; + var firstInline = paragraph.getFirstChild(); + if (!(firstInline instanceof Text)) { + return; } - private boolean tryConvertToAlert(BlockQuote blockQuote) { - Node firstChild = blockQuote.getFirstChild(); - if (!(firstChild instanceof Paragraph)) { - return false; - } - - Paragraph paragraph = (Paragraph) firstChild; - Node firstInline = paragraph.getFirstChild(); - if (!(firstInline instanceof Text)) { - return false; - } + var textNode = (Text) firstInline; - Text textNode = (Text) firstInline; - String literal = textNode.getLiteral(); - - // The alert marker can be the entire text node content, or just the first line - // before a line break. We need to check both cases. - // Trailing spaces on the marker line create a HardLineBreak instead of SoftLineBreak. - String markerText; - Node afterMarker = firstInline.getNext(); - if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak || afterMarker == null) { - markerText = literal; - } else { - // Text followed by something other than a line break on same line - not an alert - return false; - } - - Matcher matcher = ALERT_PATTERN.matcher(markerText); - if (!matcher.matches()) { - return false; - } - - String type = matcher.group(1).toUpperCase(); - if (!allowedTypes.contains(type)) { - return false; - } + // The alert marker can be the entire text node content, or just the first line + // before a line break (trailing spaces create a HardLineBreak instead of SoftLineBreak). + var afterMarker = firstInline.getNext(); + if (afterMarker != null && !(afterMarker instanceof SoftLineBreak) && !(afterMarker instanceof HardLineBreak)) { + // Text followed by something other than a line break - not an alert + return; + } - // Must have content after the marker line. An alert with ONLY the marker - // and no content is a normal blockquote on GitHub. - boolean hasContentAfterMarker; - if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak) { - // There's a line break after marker - check if there's content after it - hasContentAfterMarker = afterMarker.getNext() != null || paragraph.getNext() != null; - } else { - // Marker is the only thing in this text node - hasContentAfterMarker = paragraph.getNext() != null; - } + var matcher = ALERT_PATTERN.matcher(textNode.getLiteral()); + if (!matcher.matches()) { + return; + } - if (!hasContentAfterMarker) { - return false; - } + var type = matcher.group(1).toUpperCase(Locale.ROOT); + if (!allowedTypes.contains(type)) { + return; + } - // Check if the content after the marker is only whitespace - if (isContentWhitespaceOnly(paragraph, firstInline)) { - return false; + // Must have content after the marker line. An alert with ONLY the marker + // and no content is a normal blockquote on GitHub. + if (afterMarker != null) { + // There's a line break after marker - check if there's content after it + if (afterMarker.getNext() == null && paragraph.getNext() == null) { + return; } - - // Valid alert. Create Alert node and transfer children. - Alert alert = new Alert(type); - - blockQuote.insertAfter(alert); - - // Remove the marker text and line break from the first paragraph - if (afterMarker instanceof SoftLineBreak || afterMarker instanceof HardLineBreak) { - afterMarker.unlink(); + afterMarker.unlink(); + } else { + // Marker is the only thing in the paragraph + if (paragraph.getNext() == null) { + return; } - firstInline.unlink(); + } - // If paragraph is now empty, remove it - if (paragraph.getFirstChild() == null) { - paragraph.unlink(); - } + // Valid alert. Create Alert node and transfer children. + var alert = new Alert(type); + blockQuote.insertAfter(alert); - // Move remaining children from blockquote to alert - Node child = blockQuote.getFirstChild(); - while (child != null) { - Node next = child.getNext(); - alert.appendChild(child); - child = next; - } + // Remove the marker text from the first paragraph + firstInline.unlink(); - blockQuote.unlink(); - return true; + // If paragraph is now empty, remove it + if (paragraph.getFirstChild() == null) { + paragraph.unlink(); } - private boolean isContentWhitespaceOnly(Paragraph firstParagraph, Node markerNode) { - // Check inline nodes after the marker in the first paragraph - Node next = markerNode.getNext(); - while (next != null) { - if (next instanceof Text) { - if (!((Text) next).getLiteral().trim().isEmpty()) { - return false; - } - } else if (!(next instanceof SoftLineBreak) && !(next instanceof HardLineBreak)) { - return false; - } - next = next.getNext(); - } - - // Check block-level siblings after the first paragraph - Node block = firstParagraph.getNext(); - while (block != null) { - if (block instanceof Paragraph) { - Node child = block.getFirstChild(); - while (child != null) { - if (child instanceof Text && !((Text) child).getLiteral().trim().isEmpty()) { - return false; - } else if (!(child instanceof Text) && !(child instanceof SoftLineBreak) && !(child instanceof HardLineBreak)) { - return false; - } - child = child.getNext(); - } - } else { - return false; - } - block = block.getNext(); - } - - return true; + // Move remaining children from blockquote to alert + var child = blockQuote.getFirstChild(); + while (child != null) { + var next = child.getNext(); + alert.appendChild(child); + child = next; } + + blockQuote.unlink(); } } diff --git a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java index dba1628cb..34b78385c 100644 --- a/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java +++ b/commonmark-ext-gfm-alerts/src/test/java/org/commonmark/ext/gfm/alerts/examples/AlertsExample.java @@ -1,6 +1,5 @@ package org.commonmark.ext.gfm.alerts.examples; -import org.commonmark.Extension; import org.commonmark.ext.gfm.alerts.AlertsExtension; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; @@ -22,19 +21,17 @@ private static void standardTypesExample() { System.out.println("STANDARD GFM ALERT TYPES"); System.out.println("=".repeat(60)); - // Create the alerts extension with default settings - Extension extension = AlertsExtension.create(); + var extension = AlertsExtension.create(); - Parser parser = Parser.builder() + var parser = Parser.builder() .extensions(List.of(extension)) .build(); - HtmlRenderer renderer = HtmlRenderer.builder() + var renderer = HtmlRenderer.builder() .extensions(List.of(extension)) .build(); - // Example markdown with all standard alert types - String markdown = "# GFM Alerts Demo\n\n" + + var markdown = "# GFM Alerts Demo\n\n" + "> [!NOTE]\n" + "> Highlights information that users should take into account.\n\n" + "> [!TIP]\n" + @@ -46,7 +43,7 @@ private static void standardTypesExample() { "> [!CAUTION]\n" + "> Advises about risks or negative outcomes.\n"; - String html = renderer.render(parser.parse(markdown)); + var html = renderer.render(parser.parse(markdown)); System.out.println("Markdown Input:"); System.out.println(markdown); @@ -58,33 +55,27 @@ private static void customTypesExample() { System.out.println("CUSTOM ALERT TYPES"); System.out.println("=".repeat(60)); - // Create extension with custom types - Extension extension = AlertsExtension.builder() - .addCustomType("INFO", "Information") - .addCustomType("SUCCESS", "Success") - .addCustomType("DANGER", "Danger") + var extension = AlertsExtension.builder() + .addCustomType("BUG", "Known Bug") .build(); - Parser parser = Parser.builder() + var parser = Parser.builder() .extensions(List.of(extension)) .build(); - HtmlRenderer renderer = HtmlRenderer.builder() + var renderer = HtmlRenderer.builder() .extensions(List.of(extension)) .build(); - // Example markdown with custom alert types - String markdown = "# Custom Alert Types\n\n" + - "> [!INFO]\n" + - "> This is a custom information alert.\n\n" + - "> [!SUCCESS]\n" + - "> Operation completed successfully!\n\n" + - "> [!DANGER]\n" + - "> This action is dangerous and irreversible.\n\n" + + var markdown = "# Custom Alert Types\n\n" + "> [!NOTE]\n" + - "> Standard types still work alongside custom types.\n"; + "> Useful information that users should know.\n\n" + + "> [!TIP]\n" + + "> Helpful advice for doing things better.\n\n" + + "> [!BUG]\n" + + "> This feature has a known issue with large files (see #42).\n"; - String html = renderer.render(parser.parse(markdown)); + var html = renderer.render(parser.parse(markdown)); System.out.println("Markdown Input:"); System.out.println(markdown); From f8f7684fb67d92d679ff87da4a32e89c4588fdd3 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 19:24:09 +1100 Subject: [PATCH 798/815] add node depth limit --- .../org/commonmark/node/AbstractVisitor.java | 23 ++++++- .../commonmark/test/AbstractVisitorTest.java | 64 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java index 7edd635d7..b7389ceb9 100644 --- a/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java +++ b/commonmark/src/main/java/org/commonmark/node/AbstractVisitor.java @@ -7,6 +7,19 @@ * call {@link #visitChildren}. */ public abstract class AbstractVisitor implements Visitor { + private final int maxDepth; + private int currentDepth; + + public AbstractVisitor() { + this(Integer.MAX_VALUE); + } + + protected AbstractVisitor(int maxDepth) { + if (maxDepth < 0) { + throw new IllegalArgumentException("maxDepth must be >= 0"); + } + this.maxDepth = maxDepth; + } @Override public void visit(BlockQuote blockQuote) { @@ -129,12 +142,20 @@ public void visit(CustomNode customNode) { * @param parent the parent node whose children should be visited */ protected void visitChildren(Node parent) { + if (currentDepth >= maxDepth) { + return; + } Node node = parent.getFirstChild(); while (node != null) { // A subclass of this visitor might modify the node, resulting in getNext returning a different node or no // node after visiting it. So get the next node before visiting. Node next = node.getNext(); - node.accept(this); + currentDepth++; + try { + node.accept(this); + } finally { + currentDepth--; + } node = next; } } diff --git a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java index edb6936f4..554f9c25e 100644 --- a/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java +++ b/commonmark/src/test/java/org/commonmark/test/AbstractVisitorTest.java @@ -3,9 +3,38 @@ import org.commonmark.node.*; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class AbstractVisitorTest { + @Test + public void maxDepthMustBeZeroOrGreater() { + assertThatThrownBy(() -> new RecordingVisitor(-1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void maxDepthZeroVisitsOnlyRoot() { + var paragraph = paragraphTree(); + var visitor = new RecordingVisitor(0); + + paragraph.accept(visitor); + + assertThat(visitor.visited).containsExactly("paragraph"); + } + + @Test + public void maxDepthOneVisitsDirectChildrenButNotGrandchildren() { + var paragraph = paragraphTree(); + var visitor = new RecordingVisitor(1); + + paragraph.accept(visitor); + + assertThat(visitor.visited).containsExactly("paragraph", "emphasis", "text:tail"); + } @Test public void replacingNodeInVisitorShouldNotDestroyVisitOrder() { @@ -34,4 +63,39 @@ private static void assertCode(String expectedLiteral, Node node) { Code code = (Code) node; assertThat(code.getLiteral()).isEqualTo(expectedLiteral); } + + private static Paragraph paragraphTree() { + var paragraph = new Paragraph(); + var emphasis = new Emphasis(); + emphasis.appendChild(new Text("nested")); + paragraph.appendChild(emphasis); + paragraph.appendChild(new Text("tail")); + return paragraph; + } + + private static final class RecordingVisitor extends AbstractVisitor { + private final List visited = new ArrayList<>(); + + private RecordingVisitor(int maxDepth) { + super(maxDepth); + } + + @Override + public void visit(Paragraph paragraph) { + visited.add("paragraph"); + super.visit(paragraph); + } + + @Override + public void visit(Emphasis emphasis) { + visited.add("emphasis"); + super.visit(emphasis); + } + + @Override + public void visit(Text text) { + visited.add("text:" + text.getLiteral()); + super.visit(text); + } + } } From 50d2c368f8f4225da809697f371c7c4ea9ee05c2 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Mon, 30 Mar 2026 21:21:55 +1100 Subject: [PATCH 799/815] abort deep nodes early --- .../commonmark/internal/DocumentParser.java | 8 +- .../java/org/commonmark/parser/Parser.java | 26 ++++- .../java/org/commonmark/test/ParserTest.java | 102 ++++++++++++++++++ 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java index d935f8d27..2b9db45cc 100644 --- a/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/DocumentParser.java @@ -76,6 +76,7 @@ public class DocumentParser implements ParserState { private final List linkProcessors; private final Set linkMarkers; private final IncludeSourceSpans includeSourceSpans; + private final int maxOpenBlockParsers; private final DocumentBlockParser documentBlockParser; private final Definitions definitions = new Definitions(); @@ -84,7 +85,8 @@ public class DocumentParser implements ParserState { public DocumentParser(List blockParserFactories, InlineParserFactory inlineParserFactory, List inlineContentParserFactories, List delimiterProcessors, - List linkProcessors, Set linkMarkers, IncludeSourceSpans includeSourceSpans) { + List linkProcessors, Set linkMarkers, + IncludeSourceSpans includeSourceSpans, int maxOpenBlockParsers) { this.blockParserFactories = blockParserFactories; this.inlineParserFactory = inlineParserFactory; this.inlineContentParserFactories = inlineContentParserFactories; @@ -92,6 +94,7 @@ public DocumentParser(List blockParserFactories, InlineParse this.linkProcessors = linkProcessors; this.linkMarkers = linkMarkers; this.includeSourceSpans = includeSourceSpans; + this.maxOpenBlockParsers = maxOpenBlockParsers; this.documentBlockParser = new DocumentBlockParser(); activateBlockParser(new OpenBlockParser(documentBlockParser, 0)); @@ -461,6 +464,9 @@ private void addSourceSpans() { } private BlockStartImpl findBlockStart(BlockParser blockParser) { + if (openBlockParsers.size() - 1 >= maxOpenBlockParsers) { + return null; + } MatchedBlockParser matchedBlockParser = new MatchedBlockParserImpl(blockParser); for (BlockParserFactory blockParserFactory : blockParserFactories) { BlockStart result = blockParserFactory.tryStart(this, matchedBlockParser); diff --git a/commonmark/src/main/java/org/commonmark/parser/Parser.java b/commonmark/src/main/java/org/commonmark/parser/Parser.java index b98d0581f..9e90e906b 100644 --- a/commonmark/src/main/java/org/commonmark/parser/Parser.java +++ b/commonmark/src/main/java/org/commonmark/parser/Parser.java @@ -37,6 +37,7 @@ public class Parser { private final InlineParserFactory inlineParserFactory; private final List postProcessors; private final IncludeSourceSpans includeSourceSpans; + private final int maxOpenBlockParsers; private Parser(Builder builder) { this.blockParserFactories = DocumentParser.calculateBlockParserFactories(builder.blockParserFactories, builder.enabledBlockTypes); @@ -47,6 +48,7 @@ private Parser(Builder builder) { this.linkProcessors = builder.linkProcessors; this.linkMarkers = builder.linkMarkers; this.includeSourceSpans = builder.includeSourceSpans; + this.maxOpenBlockParsers = builder.maxOpenBlockParsers; // Try to construct an inline parser. Invalid configuration might result in an exception, which we want to // detect as soon as possible. @@ -106,7 +108,7 @@ public Node parseReader(Reader input) throws IOException { private DocumentParser createDocumentParser() { return new DocumentParser(blockParserFactories, inlineParserFactory, inlineContentParserFactories, - delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans); + delimiterProcessors, linkProcessors, linkMarkers, includeSourceSpans, maxOpenBlockParsers); } private Node postProcess(Node document) { @@ -129,6 +131,7 @@ public static class Builder { private Set> enabledBlockTypes = DocumentParser.getDefaultBlockParserTypes(); private InlineParserFactory inlineParserFactory; private IncludeSourceSpans includeSourceSpans = IncludeSourceSpans.NONE; + private int maxOpenBlockParsers = Integer.MAX_VALUE; /** * @return the configured {@link Parser} @@ -200,6 +203,27 @@ public Builder includeSourceSpans(IncludeSourceSpans includeSourceSpans) { return this; } + /** + * Limit how many non-document block parsers may be open at once while parsing. + *

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

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

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

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

      * Once the limit is reached, additional block starts are treated as plain text instead of * creating deeper nested block structure. From bef429e670479a13af66b2ef74bb39094dd53014 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:01:27 +1100 Subject: [PATCH 804/815] review --- .../src/test/java/org/commonmark/test/ParserTest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 5550df225..89df86407 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -2,9 +2,8 @@ import org.commonmark.node.*; import org.commonmark.parser.*; -import org.commonmark.parser.block.*; import org.commonmark.renderer.html.HtmlRenderer; -import org.commonmark.renderer.text.TextContentRenderer; +import org.commonmark.renderer.markdown.MarkdownRenderer; import org.commonmark.testutil.TestResources; import org.junit.jupiter.api.Test; @@ -16,8 +15,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -279,7 +276,7 @@ private Paragraph deepestStructuredParagraph(Node document, int levels) { } private String renderText(Node node) { - return TextContentRenderer.builder().build().render(node).trim(); + return MarkdownRenderer.builder().build().render(node).trim(); } private String alternatingNestedList(int levels) { From b9dd2d5747aa8d04070811b098e2032cf9556ecd Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:08:56 +1100 Subject: [PATCH 805/815] remove stale test --- .../java/org/commonmark/test/ParserTest.java | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 89df86407..2427347ea 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -203,55 +203,6 @@ public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { assertThat(deepestParagraph.getNext()).isNull(); } - @Test - public void maxOpenBlockParsersAlignsWithVisitorDepthAtDocumentRoot() { - var parser = Parser.builder().maxOpenBlockParsers(5).build(); - - var document = parser.parse(String.join("\n", - "- level1", - " > level2", - " > > level3", - " > > > level4")); - var listBlock = document.getFirstChild(); - var listItem = listBlock.getFirstChild(); - var blockQuote1 = listItem.getLastChild(); - var blockQuote2 = blockQuote1.getLastChild(); - var deepestParagraph = blockQuote2.getLastChild(); - var depthFiveVisitor = new RecordingVisitor(5); - var unlimitedVisitor = new RecordingVisitor(); - - assertThat(depth(deepestParagraph.getFirstChild())).isEqualTo(6); - assertThat(depth(deepestParagraph.getLastChild())).isEqualTo(6); - - document.accept(depthFiveVisitor); - document.accept(unlimitedVisitor); - - assertThat(depthFiveVisitor.visited).containsExactly( - "document", - "bulletList", - "listItem", - "paragraph", - "text:level1", - "blockQuote", - "paragraph", - "text:level2", - "blockQuote", - "paragraph"); - assertThat(unlimitedVisitor.visited).containsExactly( - "document", - "bulletList", - "listItem", - "paragraph", - "text:level1", - "blockQuote", - "paragraph", - "text:level2", - "blockQuote", - "paragraph", - "text:level3", - "text:> level4"); - } - private String firstText(Node n) { while (!(n instanceof Text)) { assertThat(n).isNotNull(); From 0308e3494a8945e370b6b0833ac72346d923ff32 Mon Sep 17 00:00:00 2001 From: Spirit Wolf Date: Tue, 31 Mar 2026 13:13:55 +1100 Subject: [PATCH 806/815] compile --- .../java/org/commonmark/test/ParserTest.java | 51 +------------------ 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/commonmark/src/test/java/org/commonmark/test/ParserTest.java b/commonmark/src/test/java/org/commonmark/test/ParserTest.java index 2427347ea..337196c56 100644 --- a/commonmark/src/test/java/org/commonmark/test/ParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/ParserTest.java @@ -173,7 +173,7 @@ public void maxOpenBlockParsersDegradesTheNinthLogicalListLevelToPlainText() { var document = parser.parse(alternatingNestedList(9)); var deepestParagraph = deepestStructuredParagraph(document, 8); - assertThat(renderText(deepestParagraph)).isEqualTo("level8\n- level9"); + assertThat(renderText(deepestParagraph)).isEqualTo("level8\n\\- level9"); assertThat(deepestParagraph.getNext()).isNull(); } @@ -199,7 +199,7 @@ public void maxOpenBlockParsersAlsoLimitsMixedListAndBlockQuoteNesting() { var deepestParagraph = blockQuote2.getLastChild(); assertThat(deepestParagraph).isInstanceOf(Paragraph.class); - assertThat(renderText(deepestParagraph)).isEqualTo("level3\n> level4"); + assertThat(renderText(deepestParagraph)).isEqualTo("level3\n\\> level4"); assertThat(deepestParagraph.getNext()).isNull(); } @@ -250,51 +250,4 @@ private int depth(Node node) { } return depth; } - - private static final class RecordingVisitor extends AbstractVisitor { - private final List visited = new ArrayList<>(); - - private RecordingVisitor() { - } - - private RecordingVisitor(int maxDepth) { - super(maxDepth); - } - - @Override - public void visit(Document document) { - visited.add("document"); - super.visit(document); - } - - @Override - public void visit(BulletList bulletList) { - visited.add("bulletList"); - super.visit(bulletList); - } - - @Override - public void visit(ListItem listItem) { - visited.add("listItem"); - super.visit(listItem); - } - - @Override - public void visit(BlockQuote blockQuote) { - visited.add("blockQuote"); - super.visit(blockQuote); - } - - @Override - public void visit(Paragraph paragraph) { - visited.add("paragraph"); - super.visit(paragraph); - } - - @Override - public void visit(Text text) { - visited.add("text:" + text.getLiteral()); - super.visit(text); - } - } } From 61200f9c27cb7b8706b0754ca958a5822bdf801b Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:04:40 +1100 Subject: [PATCH 807/815] Update commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java --- .../commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java index b524adc05..8008fc8dd 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertPostProcessor.java @@ -87,6 +87,7 @@ private void tryConvertToAlert(BlockQuote blockQuote) { // Valid alert. Create Alert node and transfer children. var alert = new Alert(type); + alert.setSourceSpans(blockQuote.getSourceSpans()); blockQuote.insertAfter(alert); // Remove the marker text from the first paragraph From e2e27557e475588652902bb261ad0bb8f3be094e Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:20:29 +1100 Subject: [PATCH 808/815] README: Add section about alerts --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index a917167f6..b9e09a1a9 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,19 @@ Enables tables using pipes as in [GitHub Flavored Markdown][gfm-tables]. Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`. +### Alerts + +Adds support for GitHub-style alerts (also known as callouts or admonitions) as described [here](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts), e.g.: + +``` +> [!NOTE] +> The text of the note. +``` + +As types you can use NOTE, TIP, IMPORTANT, WARNING, CAUTION; or configure the extension to add additional ones. + +Use class `AlertsExtension` in artifact `commonmark-ext-gfm-alerts`. + ### Footnotes Enables footnotes like in [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes) From 66210650c3bee8c3129fb4239edf93d902206d20 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:27:24 +1100 Subject: [PATCH 809/815] Add alerts extension to integration tests --- commonmark-integration-test/pom.xml | 4 ++++ .../src/test/java/org/commonmark/integration/Extensions.java | 2 ++ pom.xml | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 3433b42d4..8e52dd383 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -28,6 +28,10 @@ org.commonmark commonmark-ext-ins + + org.commonmark + commonmark-ext-gfm-alerts + org.commonmark commonmark-ext-gfm-strikethrough diff --git a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java index 8df0408cb..9090c797f 100644 --- a/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java +++ b/commonmark-integration-test/src/test/java/org/commonmark/integration/Extensions.java @@ -4,6 +4,7 @@ import org.commonmark.ext.autolink.AutolinkExtension; import org.commonmark.ext.footnotes.FootnotesExtension; import org.commonmark.ext.front.matter.YamlFrontMatterExtension; +import org.commonmark.ext.gfm.alerts.AlertsExtension; import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.ext.gfm.tables.TablesExtension; import org.commonmark.ext.image.attributes.ImageAttributesExtension; @@ -19,6 +20,7 @@ public class Extensions { FootnotesExtension.create(), ImageAttributesExtension.create(), InsExtension.create(), + AlertsExtension.create(), StrikethroughExtension.create(), TablesExtension.create(), TaskListItemsExtension.create(), diff --git a/pom.xml b/pom.xml index 58c3ead2c..1c9a53f28 100644 --- a/pom.xml +++ b/pom.xml @@ -155,6 +155,11 @@ commonmark-ext-ins 0.27.2-SNAPSHOT + + org.commonmark + commonmark-ext-gfm-alerts + 0.27.2-SNAPSHOT + org.commonmark commonmark-ext-gfm-strikethrough From 873a861938a28653002549a1c418a1ff4b519657 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:20:37 +1100 Subject: [PATCH 810/815] Prepare CHANGELOG for version 0.28.0 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b973b700f..399071347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## [0.28.0] - 2026-03-31 +### Added +- New extension for alerts (aka callouts/admonitions) + - Syntax: + ``` + > [!NOTE] + > The text of the note. + ``` + - As types you can use NOTE, TIP, IMPORTANT, WARNING, CAUTION; or configure the + extension to add additional ones. + - Use class `AlertsExtension` in artifact `commonmark-ext-gfm-alerts` (#420) +- New option `maxOpenBlockParsers` for `Parser.Builder` to set an overall limit + for the depth of block parsing. If set, any nesting beyond the limit will be + parsed as paragraph text instead. The default remains unlimited. + ## [0.27.1] - 2026-01-14 ### Fixed - Line(s) after a hard line break would sometimes also get an unwanted hard @@ -518,6 +533,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[0.28.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0 [0.27.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...commonmark-parent-0.27.1 [0.27.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.26.0...commonmark-parent-0.27.0 [0.26.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.1...commonmark-parent-0.26.0 From d278947053b678c639bc888d5410cb507853cf59 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:31:56 +1100 Subject: [PATCH 811/815] mvn versions:set -DnewVersion=0.28.0-SNAPSHOT --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 26 ++++++++++++------------ 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index a99fd0b8c..79d8db07a 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 8bb88d74a..14a4a9290 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 5235af6b2..7780e4e2d 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index f6cedc69a..8c9a6b296 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 4e94f623c..37ead1ce6 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index 707948a10..c2be557e7 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 456ac04a2..19ea007a9 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index a06c27b29..7288b397b 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 0fc164672..bdd592d78 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 860cfbac7..85b3ad219 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 8e52dd383..6b1d76a65 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 699adb3de..063b8af46 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 76dad6c83..650a5562a 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index 1c9a53f28..ace89ff32 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-ins - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-gfm-alerts - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT org.commonmark commonmark-test-util - 0.27.2-SNAPSHOT + 0.28.0-SNAPSHOT From 9e30657b678fd41b29ed3a957c3dd6a92fb445e1 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:40:37 +0000 Subject: [PATCH 812/815] [maven-release-plugin] prepare release commonmark-parent-0.28.0 --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 28 ++++++++++++------------ 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 79d8db07a..818ccf4fb 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index 14a4a9290..a81dcbfd1 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 7780e4e2d..4e5b4e1cc 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 8c9a6b296..3b7afb3b1 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index 37ead1ce6..ed4b5276b 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index c2be557e7..a5cb099cf 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 19ea007a9..07415ec8c 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 7288b397b..0a41c4dae 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index bdd592d78..7f96168ef 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 85b3ad219..83185bad3 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.28.0-SNAPSHOT + 0.28.0 commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index 6b1d76a65..e50de5bc6 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 063b8af46..542eedc2e 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 650a5562a..673479fd4 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark diff --git a/pom.xml b/pom.xml index ace89ff32..c43a5aed5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.28.0-SNAPSHOT + 0.28.0 commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-autolink - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-footnotes - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-image-attributes - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-ins - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-gfm-alerts - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-gfm-strikethrough - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-gfm-tables - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-heading-anchor - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-task-list-items - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-ext-yaml-front-matter - 0.28.0-SNAPSHOT + 0.28.0 org.commonmark commonmark-test-util - 0.28.0-SNAPSHOT + 0.28.0 @@ -315,7 +315,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - HEAD + commonmark-parent-0.28.0 From ba867e1e63fcabfb08511ce868707f0c1f20f453 Mon Sep 17 00:00:00 2001 From: "Robin Stocker (GitHub Actions)" <16778+robinst@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:40:38 +0000 Subject: [PATCH 813/815] [maven-release-plugin] prepare for next development iteration --- commonmark-ext-autolink/pom.xml | 2 +- commonmark-ext-footnotes/pom.xml | 2 +- commonmark-ext-gfm-alerts/pom.xml | 2 +- commonmark-ext-gfm-strikethrough/pom.xml | 2 +- commonmark-ext-gfm-tables/pom.xml | 2 +- commonmark-ext-heading-anchor/pom.xml | 2 +- commonmark-ext-image-attributes/pom.xml | 2 +- commonmark-ext-ins/pom.xml | 2 +- commonmark-ext-task-list-items/pom.xml | 2 +- commonmark-ext-yaml-front-matter/pom.xml | 2 +- commonmark-integration-test/pom.xml | 2 +- commonmark-test-util/pom.xml | 2 +- commonmark/pom.xml | 2 +- pom.xml | 28 ++++++++++++------------ 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/commonmark-ext-autolink/pom.xml b/commonmark-ext-autolink/pom.xml index 818ccf4fb..2cc4d53ca 100644 --- a/commonmark-ext-autolink/pom.xml +++ b/commonmark-ext-autolink/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-autolink diff --git a/commonmark-ext-footnotes/pom.xml b/commonmark-ext-footnotes/pom.xml index a81dcbfd1..0d9e2f30c 100644 --- a/commonmark-ext-footnotes/pom.xml +++ b/commonmark-ext-footnotes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-footnotes diff --git a/commonmark-ext-gfm-alerts/pom.xml b/commonmark-ext-gfm-alerts/pom.xml index 4e5b4e1cc..02ecbf802 100644 --- a/commonmark-ext-gfm-alerts/pom.xml +++ b/commonmark-ext-gfm-alerts/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-gfm-alerts diff --git a/commonmark-ext-gfm-strikethrough/pom.xml b/commonmark-ext-gfm-strikethrough/pom.xml index 3b7afb3b1..9d8f55e5f 100644 --- a/commonmark-ext-gfm-strikethrough/pom.xml +++ b/commonmark-ext-gfm-strikethrough/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-gfm-strikethrough diff --git a/commonmark-ext-gfm-tables/pom.xml b/commonmark-ext-gfm-tables/pom.xml index ed4b5276b..5bd323168 100644 --- a/commonmark-ext-gfm-tables/pom.xml +++ b/commonmark-ext-gfm-tables/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-gfm-tables diff --git a/commonmark-ext-heading-anchor/pom.xml b/commonmark-ext-heading-anchor/pom.xml index a5cb099cf..26d2d19b1 100644 --- a/commonmark-ext-heading-anchor/pom.xml +++ b/commonmark-ext-heading-anchor/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-heading-anchor diff --git a/commonmark-ext-image-attributes/pom.xml b/commonmark-ext-image-attributes/pom.xml index 07415ec8c..e646bc3fd 100644 --- a/commonmark-ext-image-attributes/pom.xml +++ b/commonmark-ext-image-attributes/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-image-attributes diff --git a/commonmark-ext-ins/pom.xml b/commonmark-ext-ins/pom.xml index 0a41c4dae..48481c073 100644 --- a/commonmark-ext-ins/pom.xml +++ b/commonmark-ext-ins/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-ins diff --git a/commonmark-ext-task-list-items/pom.xml b/commonmark-ext-task-list-items/pom.xml index 7f96168ef..4359f8707 100644 --- a/commonmark-ext-task-list-items/pom.xml +++ b/commonmark-ext-task-list-items/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-task-list-items diff --git a/commonmark-ext-yaml-front-matter/pom.xml b/commonmark-ext-yaml-front-matter/pom.xml index 83185bad3..e6822f771 100644 --- a/commonmark-ext-yaml-front-matter/pom.xml +++ b/commonmark-ext-yaml-front-matter/pom.xml @@ -4,7 +4,7 @@ commonmark-parent org.commonmark - 0.28.0 + 0.28.1-SNAPSHOT commonmark-ext-yaml-front-matter diff --git a/commonmark-integration-test/pom.xml b/commonmark-integration-test/pom.xml index e50de5bc6..7e0048a73 100644 --- a/commonmark-integration-test/pom.xml +++ b/commonmark-integration-test/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-integration-test diff --git a/commonmark-test-util/pom.xml b/commonmark-test-util/pom.xml index 542eedc2e..6a9c342cc 100644 --- a/commonmark-test-util/pom.xml +++ b/commonmark-test-util/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-test-util diff --git a/commonmark/pom.xml b/commonmark/pom.xml index 673479fd4..4e060edaa 100644 --- a/commonmark/pom.xml +++ b/commonmark/pom.xml @@ -4,7 +4,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark diff --git a/pom.xml b/pom.xml index c43a5aed5..f12805316 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.commonmark commonmark-parent - 0.28.0 + 0.28.1-SNAPSHOT commonmark-java parent Java implementation of CommonMark, a specification of the Markdown format for turning plain text into formatted @@ -133,62 +133,62 @@ org.commonmark commonmark - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-autolink - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-footnotes - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-image-attributes - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-ins - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-gfm-alerts - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-gfm-strikethrough - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-gfm-tables - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-heading-anchor - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-task-list-items - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-ext-yaml-front-matter - 0.28.0 + 0.28.1-SNAPSHOT org.commonmark commonmark-test-util - 0.28.0 + 0.28.1-SNAPSHOT @@ -315,7 +315,7 @@ scm:git:https://github.com/commonmark/commonmark-java scm:git:https://github.com/commonmark/commonmark-java https://github.com/commonmark/commonmark-java - commonmark-parent-0.28.0 + HEAD From 41586ddabdee9d455c1b8b7c816bd5f0f86647a5 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 31 Mar 2026 14:52:18 +1100 Subject: [PATCH 814/815] Bump versions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b9e09a1a9..845226729 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Coordinates for core library (see all on [Maven Central]): org.commonmark commonmark - 0.27.1 + 0.28.0 ``` @@ -291,7 +291,7 @@ First, add an additional dependency (see [Maven Central] for others): org.commonmark commonmark-ext-gfm-tables - 0.27.1 + 0.28.0 ``` From 8df50617946609975b21f3e791c7d0712cb0b34c Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Fri, 10 Apr 2026 15:51:59 +1000 Subject: [PATCH 815/815] Allow customizing HTML attributes for alert title --- CHANGELOG.md | 5 +++++ .../ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 399071347..9c5c67268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## [Unreleased] +### Added +- Allow customizing HTML attributes for alert title `

      ` tag via `AttributeProvider` + ## [0.28.0] - 2026-03-31 ### Added - New extension for alerts (aka callouts/admonitions) @@ -533,6 +537,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.28.0...main [0.28.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0 [0.27.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...commonmark-parent-0.27.1 [0.27.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.26.0...commonmark-parent-0.27.0 diff --git a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java index 763b2e217..ca562ba33 100644 --- a/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java +++ b/commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/internal/AlertHtmlNodeRenderer.java @@ -34,7 +34,7 @@ protected void renderAlert(Alert alert) { htmlWriter.line(); // Render alert title - htmlWriter.tag("p", Map.of("class", "markdown-alert-title")); + htmlWriter.tag("p", context.extendAttributes(alert, "p", Map.of("class", "markdown-alert-title"))); htmlWriter.text(getAlertTitle(type)); htmlWriter.tag("/p"); htmlWriter.line();

    foo@bar.example.com