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)); } } 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; } 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 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..989406446 --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/content/TextContentRenderer.java @@ -0,0 +1,136 @@ +package org.commonmark.content; + +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 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); + } + }); + } + + /** + * Create a new builder for configuring an {@link TextContentRenderer}. + * + * @return a builder + */ + public static Builder builder() { + return new Builder(); + } + + @Override + protected NodeRendererContext createContext(Appendable out) { + return new RendererContext(new TextContentWriter(out)); + } + + /** + * Builder for configuring an {@link TextContentRenderer}. See methods for default configuration. + */ + public static class Builder { + + private boolean stripNewlines = false; + private List> nodeRendererFactories = new ArrayList<>(); + + /** + * @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; + } + + /** + * 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; + } + + /** + * @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; + } + } + + /** + * Extension for {@link TextContentRenderer}. + */ + public interface TextContentRendererExtension extends Extension { + void extend(TextContentRenderer.Builder rendererBuilder); + } + + private class RendererContext extends TextContentNodeRendererContext { + private final TextContentWriter textContentWriter; + + private RendererContext(TextContentWriter textContentWriter) { + this.textContentWriter = textContentWriter; + + List renderers = new ArrayList<>(nodeRendererFactories.size()); + for (NodeRendererFactory nodeRendererFactory : nodeRendererFactories) { + renderers.add(nodeRendererFactory.create(this)); + } + addNodeRenderers(renderers); + } + + @Override + public boolean stripNewlines() { + return stripNewlines; + } + + @Override + 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 new file mode 100644 index 000000000..852d759db --- /dev/null +++ b/commonmark/src/main/java/org/commonmark/content/TextContentWriter.java @@ -0,0 +1,69 @@ +package org.commonmark.content; + +import org.commonmark.renderer.Writer; + +import java.io.IOException; + +public class TextContentWriter implements Writer { + + 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/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/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"); } }; } 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..7a6b6dca8 --- /dev/null +++ b/commonmark/src/test/java/org/commonmark/test/TextContentRendererTest.java @@ -0,0 +1,169 @@ +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 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; + + 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 = 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 = 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 + 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()); + } +}