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 aa7dff716fdf4dc39ab6126e43af92c12506e05c..f87f3e9c8860418984811b312e4d9f2fede31245 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.TextContentNodeRendererContext; 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 class StrikethroughExtension implements Parser.ParserExtension, HtmlRende 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 b722018b8ad734d9338e90ccfaff8179b0d0296a..96df48cec006909806effdf837efd729e2764b10 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 d23f6f5fc35bb6bfaa86044ded3510dd0c15550f..92e1f0ba41c0c807bf122464352704262f43a9f7 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.TextContentNodeRendererContext; 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 class TablesExtension implements Parser.ParserExtension, HtmlRenderer.Htm 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 d31020fa37ea1343cc3dd425c673715eb52e0535..dd7e8d5eb7c5c3fcce03ebfa00dba0008e735565 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 @@ public class AsciiMatcher implements CharMatcher { 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 class AsciiMatcher implements CharMatcher { 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 c7fa9be7a48250be991655817e6aef087b2912b8..2db7ef30cb6e56486cf1e256ad50a821a7827f64 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 @@ import java.util.regex.Pattern; */ 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 8fe0f73d52a025efe9fbe632d0f584a761082184..5805c458b0ee4b38a2aaf7386ef013050767fd32 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 @@ package org.commonmark.renderer.markdown; 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 7b3134277cad55814dea0e2f863cb941497776ec..adfe8a07b7cd7f2cb5f1676202eaf0400bea791e 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 @@ package org.commonmark.renderer.markdown; 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 4dee17ed622173f8be7a2933d852c79c03c451c2..926105202e8f20c3a36e5481c7ed62604ed66f05 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.node.Node; 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 @@ public class MarkdownRenderer implements Renderer { public NodeRenderer create(MarkdownNodeRendererContext context) { return new CoreMarkdownNodeRenderer(context); } + + @Override + public Set<Character> getSpecialCharacters() { + return Collections.emptySet(); + } }); } @@ -111,13 +115,21 @@ public class MarkdownRenderer implements Renderer { 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 class MarkdownRenderer implements Renderer { public void render(Node node) { nodeRendererMap.render(node); } + + @Override + public Set<Character> getSpecialCharacters() { + return additionalTextEscapes; + } } }