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] 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 115ae9ea..0c24642b 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.CustomNode; 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 4f0228a1..aa7dff71 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 @@ import org.commonmark.renderer.NodeRenderer; * </p> */ public class StrikethroughExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension, - TextContentRenderer.TextContentRendererExtension { + TextContentRenderer.TextContentRendererExtension, MarkdownRenderer.MarkdownRendererExtension { private final boolean requireTwoTildes; @@ -89,13 +93,23 @@ public class StrikethroughExtension implements Parser.ParserExtension, HtmlRende }); } + @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 3dedff1b..4657106a 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 class StrikethroughDelimiterProcessor implements DelimiterProcessor { 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 00000000..1c91dd64 --- /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 00000000..b722018b --- /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); + } +} -- GitLab