Skip to content
Snippets Groups Projects
Commit 981d2363 authored by Robin Stocker's avatar Robin Stocker
Browse files

Add getSpecialCharacters for escaping of extension characters

parent a490faee
No related branches found
No related tags found
No related merge requests found
Showing
with 71 additions and 13 deletions
...@@ -17,6 +17,9 @@ import org.commonmark.renderer.text.TextContentNodeRendererContext; ...@@ -17,6 +17,9 @@ import org.commonmark.renderer.text.TextContentNodeRendererContext;
import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentNodeRendererFactory;
import org.commonmark.renderer.text.TextContentRenderer; 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). * Extension for GFM strikethrough using {@code ~} or {@code ~~} (GitHub Flavored Markdown).
* <p>Example input:</p> * <p>Example input:</p>
...@@ -100,6 +103,11 @@ public class StrikethroughExtension implements Parser.ParserExtension, HtmlRende ...@@ -100,6 +103,11 @@ public class StrikethroughExtension implements Parser.ParserExtension, HtmlRende
public NodeRenderer create(MarkdownNodeRendererContext context) { public NodeRenderer create(MarkdownNodeRendererContext context) {
return new StrikethroughMarkdownNodeRenderer(context); return new StrikethroughMarkdownNodeRenderer(context);
} }
@Override
public Set<Character> getSpecialCharacters() {
return Collections.singleton('~');
}
}); });
} }
......
...@@ -19,10 +19,10 @@ public class StrikethroughMarkdownRendererTest { ...@@ -19,10 +19,10 @@ public class StrikethroughMarkdownRendererTest {
@Test @Test
public void testStrikethrough() { public void testStrikethrough() {
assertRoundTrip("~foo~ ~bar~\n"); 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) { protected String render(String source) {
......
...@@ -17,6 +17,9 @@ import org.commonmark.renderer.text.TextContentNodeRendererContext; ...@@ -17,6 +17,9 @@ import org.commonmark.renderer.text.TextContentNodeRendererContext;
import org.commonmark.renderer.text.TextContentNodeRendererFactory; import org.commonmark.renderer.text.TextContentNodeRendererFactory;
import org.commonmark.renderer.text.TextContentRenderer; import org.commonmark.renderer.text.TextContentRenderer;
import java.util.Collections;
import java.util.Set;
/** /**
* Extension for GFM tables using "|" pipes (GitHub Flavored Markdown). * Extension for GFM tables using "|" pipes (GitHub Flavored Markdown).
* <p> * <p>
...@@ -72,6 +75,11 @@ public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.Htm ...@@ -72,6 +75,11 @@ public class TablesExtension implements Parser.ParserExtension, HtmlRenderer.Htm
public NodeRenderer create(MarkdownNodeRendererContext context) { public NodeRenderer create(MarkdownNodeRendererContext context) {
return new TableMarkdownNodeRenderer(context); return new TableMarkdownNodeRenderer(context);
} }
@Override
public Set<Character> getSpecialCharacters() {
return Collections.emptySet();
}
}); });
} }
} }
package org.commonmark.internal.util; package org.commonmark.internal.util;
import java.util.BitSet; import java.util.BitSet;
import java.util.Set;
public class AsciiMatcher implements CharMatcher { public class AsciiMatcher implements CharMatcher {
private final BitSet set; private final BitSet set;
...@@ -33,6 +34,14 @@ public class AsciiMatcher implements CharMatcher { ...@@ -33,6 +34,14 @@ public class AsciiMatcher implements CharMatcher {
this.set = 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) { public Builder anyOf(String s) {
for (int i = 0; i < s.length(); i++) { for (int i = 0; i < s.length(); i++) {
c(s.charAt(i)); c(s.charAt(i));
...@@ -40,11 +49,10 @@ public class AsciiMatcher implements CharMatcher { ...@@ -40,11 +49,10 @@ public class AsciiMatcher implements CharMatcher {
return this; return this;
} }
public Builder c(char c) { public Builder anyOf(Set<Character> characters) {
if (c > 127) { for (Character c : characters) {
throw new IllegalArgumentException("Can only match ASCII characters"); c(c);
} }
set.set(c);
return this; return this;
} }
......
...@@ -22,10 +22,8 @@ import java.util.regex.Pattern; ...@@ -22,10 +22,8 @@ import java.util.regex.Pattern;
*/ */
public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer {
private final AsciiMatcher textEscape = private final AsciiMatcher textEscape;
AsciiMatcher.builder().anyOf("[]<>`*&\n\\").build(); private final CharMatcher textEscapeInHeading;
private final CharMatcher textEscapeInHeading =
AsciiMatcher.builder(textEscape).anyOf("#").build();
private final CharMatcher linkDestinationNeedsAngleBrackets = private final CharMatcher linkDestinationNeedsAngleBrackets =
AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\n').c('\\').build(); AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\n').c('\\').build();
private final CharMatcher linkDestinationEscapeInAngleBrackets = private final CharMatcher linkDestinationEscapeInAngleBrackets =
...@@ -46,6 +44,9 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen ...@@ -46,6 +44,9 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen
public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) { public CoreMarkdownNodeRenderer(MarkdownNodeRendererContext context) {
this.context = context; this.context = context;
this.writer = context.getWriter(); this.writer = context.getWriter();
textEscape = AsciiMatcher.builder().anyOf("[]<>`*&\n\\").anyOf(context.getSpecialCharacters()).build();
textEscapeInHeading = AsciiMatcher.builder(textEscape).anyOf("#").build();
} }
@Override @Override
......
...@@ -2,6 +2,8 @@ package org.commonmark.renderer.markdown; ...@@ -2,6 +2,8 @@ package org.commonmark.renderer.markdown;
import org.commonmark.node.Node; import org.commonmark.node.Node;
import java.util.Set;
public interface MarkdownNodeRendererContext { public interface MarkdownNodeRendererContext {
/** /**
...@@ -16,4 +18,10 @@ public interface MarkdownNodeRendererContext { ...@@ -16,4 +18,10 @@ public interface MarkdownNodeRendererContext {
* @param node the node to render * @param node the node to render
*/ */
void render(Node node); 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();
} }
...@@ -2,6 +2,8 @@ package org.commonmark.renderer.markdown; ...@@ -2,6 +2,8 @@ package org.commonmark.renderer.markdown;
import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.NodeRenderer;
import java.util.Set;
/** /**
* Factory for instantiating new node renderers ƒor rendering. * Factory for instantiating new node renderers ƒor rendering.
*/ */
...@@ -14,4 +16,10 @@ public interface MarkdownNodeRendererFactory { ...@@ -14,4 +16,10 @@ public interface MarkdownNodeRendererFactory {
* @return a node renderer * @return a node renderer
*/ */
NodeRenderer create(MarkdownNodeRendererContext context); 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();
} }
...@@ -6,8 +6,7 @@ import org.commonmark.node.Node; ...@@ -6,8 +6,7 @@ import org.commonmark.node.Node;
import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.NodeRenderer;
import org.commonmark.renderer.Renderer; import org.commonmark.renderer.Renderer;
import java.util.ArrayList; import java.util.*;
import java.util.List;
/** /**
* Renders nodes to CommonMark Markdown. * Renders nodes to CommonMark Markdown.
...@@ -32,6 +31,11 @@ public class MarkdownRenderer implements Renderer { ...@@ -32,6 +31,11 @@ public class MarkdownRenderer implements Renderer {
public NodeRenderer create(MarkdownNodeRendererContext context) { public NodeRenderer create(MarkdownNodeRendererContext context) {
return new CoreMarkdownNodeRenderer(context); return new CoreMarkdownNodeRenderer(context);
} }
@Override
public Set<Character> getSpecialCharacters() {
return Collections.emptySet();
}
}); });
} }
...@@ -111,13 +115,21 @@ public class MarkdownRenderer implements Renderer { ...@@ -111,13 +115,21 @@ public class MarkdownRenderer implements Renderer {
private class RendererContext implements MarkdownNodeRendererContext { private class RendererContext implements MarkdownNodeRendererContext {
private final MarkdownWriter writer; private final MarkdownWriter writer;
private final NodeRendererMap nodeRendererMap = new NodeRendererMap(); private final NodeRendererMap nodeRendererMap = new NodeRendererMap();
private final Set<Character> additionalTextEscapes;
private RendererContext(MarkdownWriter writer) { private RendererContext(MarkdownWriter writer) {
// Set fields that are used by interface
this.writer = writer; 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". // The first node renderer for a node type "wins".
for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) {
MarkdownNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); MarkdownNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i);
// Pass in this as context here, which uses the fields set above
NodeRenderer nodeRenderer = nodeRendererFactory.create(this); NodeRenderer nodeRenderer = nodeRendererFactory.create(this);
nodeRendererMap.add(nodeRenderer); nodeRendererMap.add(nodeRenderer);
} }
...@@ -132,5 +144,10 @@ public class MarkdownRenderer implements Renderer { ...@@ -132,5 +144,10 @@ public class MarkdownRenderer implements Renderer {
public void render(Node node) { public void render(Node node) {
nodeRendererMap.render(node); nodeRendererMap.render(node);
} }
@Override
public Set<Character> getSpecialCharacters() {
return additionalTextEscapes;
}
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment