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] 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 aa7dff71..f87f3e9c 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 b722018b..96df48ce 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 d23f6f5f..92e1f0ba 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 d31020fa..dd7e8d5e 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 c7fa9be7..2db7ef30 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 8fe0f73d..5805c458 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 7b313427..adfe8a07 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 4dee17ed..92610520 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; + } } } -- GitLab