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 199add94dfb9e7f8f181f9829a906648be66e1f3..f5a9377f6626145574f1ab7af31b6969a53aed8d 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -88,7 +88,7 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen @Override public void visit(ThematicBreak thematicBreak) { - writer.write("***"); + writer.raw("***"); writer.block(); } @@ -106,9 +106,9 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen if (heading.getLevel() == 1) { // Note that it would be nice to match the length of the contents instead of just using 3, but that's // not easy. - writer.write("==="); + writer.raw("==="); } else { - writer.write("---"); + writer.raw("---"); } writer.block(); return; @@ -117,9 +117,9 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen // ATX headings: Can't have multiple lines, but up to level 6. for (int i = 0; i < heading.getLevel(); i++) { - writer.write('#'); + writer.raw('#'); } - writer.write(' '); + writer.raw(' '); visitChildren(heading); writer.block(); @@ -135,7 +135,7 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen List<String> lines = getLines(literal); for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); - writer.write(line); + writer.raw(line); if (i != lines.size() - 1) { writer.line(); } @@ -156,19 +156,19 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen writer.pushPrefix(indentPrefix); } - writer.write(fence); + writer.raw(fence); if (fencedCodeBlock.getInfo() != null) { - writer.write(fencedCodeBlock.getInfo()); + writer.raw(fencedCodeBlock.getInfo()); } writer.line(); if (!literal.isEmpty()) { List<String> lines = getLines(literal); for (String line : lines) { - writer.write(line); + writer.raw(line); writer.line(); } } - writer.write(fence); + writer.raw(fence); if (indent > 0) { writer.popPrefix(); } @@ -180,7 +180,7 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen List<String> lines = getLines(htmlBlock.getLiteral()); for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); - writer.write(line); + writer.raw(line); if (i != lines.size() - 1) { writer.line(); } @@ -262,7 +262,7 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen // If the literal includes backticks, we can surround them by using one more backtick. int backticks = findMaxRunLength('`', literal); for (int i = 0; i < backticks + 1; i++) { - writer.write('`'); + writer.raw('`'); } // If the literal starts or ends with a backtick, surround it with a single space. // If it starts and ends with a space (but is not only spaces), add an additional space (otherwise they would @@ -270,14 +270,14 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen boolean addSpace = literal.startsWith("`") || literal.endsWith("`") || (literal.startsWith(" ") && literal.endsWith(" ") && Parsing.hasNonSpace(literal)); if (addSpace) { - writer.write(' '); + writer.raw(' '); } - writer.write(literal); + writer.raw(literal); if (addSpace) { - writer.write(' '); + writer.raw(' '); } for (int i = 0; i < backticks + 1; i++) { - writer.write('`'); + writer.raw('`'); } } @@ -285,16 +285,16 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen public void visit(Emphasis emphasis) { // When emphasis is nested, a different delimiter needs to be used char delimiter = writer.getLastChar() == '*' ? '_' : '*'; - writer.write(delimiter); + writer.raw(delimiter); super.visit(emphasis); - writer.write(delimiter); + writer.raw(delimiter); } @Override public void visit(StrongEmphasis strongEmphasis) { - writer.write("**"); + writer.raw("**"); super.visit(strongEmphasis); - writer.write("**"); + writer.raw("**"); } @Override @@ -309,12 +309,12 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen @Override public void visit(HtmlInline htmlInline) { - writer.write(htmlInline.getLiteral()); + writer.raw(htmlInline.getLiteral()); } @Override public void visit(HardLineBreak hardLineBreak) { - writer.write(" "); + writer.raw(" "); writer.line(); } @@ -339,19 +339,19 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen switch (c) { case '-': { // Would be ambiguous with a bullet list marker, escape - writer.write("\\-"); + writer.raw("\\-"); literal = literal.substring(1); break; } case '#': { // Would be ambiguous with an ATX heading, escape - writer.write("\\#"); + writer.raw("\\#"); literal = literal.substring(1); break; } case '=': { // Would be ambiguous with a Setext heading, escape - writer.write("\\="); + writer.raw("\\="); literal = literal.substring(1); break; } @@ -368,19 +368,19 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen // Check for ordered list marker Matcher m = orderedListMarkerPattern.matcher(literal); if (m.find()) { - writer.write(m.group(1)); - writer.write("\\" + m.group(2)); + writer.raw(m.group(1)); + writer.raw("\\" + m.group(2)); literal = literal.substring(m.end()); } break; } case '\t': { - writer.write("	"); + writer.raw("	"); literal = literal.substring(1); break; } case ' ': { - writer.write(" "); + writer.raw(" "); literal = literal.substring(1); break; } @@ -391,10 +391,10 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen if (literal.endsWith("!") && text.getNext() instanceof Link) { // If we wrote the `!` unescaped, it would turn the link into an image instead. - writer.writeEscaped(literal.substring(0, literal.length() - 1), escape); - writer.write("\\!"); + writer.text(literal.substring(0, literal.length() - 1), escape); + writer.raw("\\!"); } else { - writer.writeEscaped(literal, escape); + writer.text(literal, escape); } } @@ -455,24 +455,24 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen } private void writeLinkLike(String title, String destination, Node node, String opener) { - writer.write(opener); + writer.raw(opener); visitChildren(node); - writer.write(']'); - writer.write('('); + writer.raw(']'); + writer.raw('('); if (contains(destination, linkDestinationNeedsAngleBrackets)) { - writer.write('<'); - writer.writeEscaped(destination, linkDestinationEscapeInAngleBrackets); - writer.write('>'); + writer.raw('<'); + writer.text(destination, linkDestinationEscapeInAngleBrackets); + writer.raw('>'); } else { - writer.write(destination); + writer.raw(destination); } if (title != null) { - writer.write(' '); - writer.write('"'); - writer.writeEscaped(title, linkTitleEscapeInQuotes); - writer.write('"'); + writer.raw(' '); + writer.raw('"'); + writer.text(title, linkTitleEscapeInQuotes); + writer.raw('"'); } - writer.write(')'); + writer.raw(')'); } private static class ListHolder { diff --git a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java index ef294535933051fbd8aa43641b4e86f1a264f7cc..3b2ae18b02ab6a3830bfd69e156dd269cc06242e 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -5,6 +5,9 @@ import org.commonmark.internal.util.CharMatcher; import java.io.IOException; import java.util.LinkedList; +/** + * Writer for Markdown (CommonMark) text. + */ public class MarkdownWriter { private final Appendable buffer; @@ -19,28 +22,29 @@ public class MarkdownWriter { buffer = out; } - public char getLastChar() { - return lastChar; - } - /** - * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}. + * Write the supplied string (raw/unescaped). */ - public boolean isAtLineStart() { - return atLineStart; - } - - public void write(String s) { + public void raw(String s) { flushBlockSeparator(); append(s); } - public void write(char c) { + /** + * Write the supplied character (raw/unescaped). + */ + public void raw(char c) { flushBlockSeparator(); append(c); } - public void writeEscaped(String s, CharMatcher escape) { + /** + * Write the supplied string with escaping. + * + * @param s the string to write + * @param escape which characters to escape + */ + public void text(String s, CharMatcher escape) { if (s.isEmpty()) { return; } @@ -66,6 +70,9 @@ public class MarkdownWriter { atLineStart = false; } + /** + * Write a newline (line terminator). + */ public void line() { append('\n'); writePrefixes(); @@ -83,20 +90,67 @@ public class MarkdownWriter { atLineStart = true; } + /** + * Push a prefix onto the top of the stack. All prefixes are written at the beginning of each line, until the + * prefix is popped again. + * + * @param prefix the raw prefix string + */ public void pushPrefix(String prefix) { prefixes.addLast(prefix); } + /** + * Write a prefix. + * + * @param prefix the raw prefix string to write + */ public void writePrefix(String prefix) { boolean tmp = atLineStart; - write(prefix); + raw(prefix); atLineStart = tmp; } + /** + * Remove the last prefix from the top of the stack. + */ public void popPrefix() { prefixes.removeLast(); } + /** + * @return the last character that was written + */ + public char getLastChar() { + return lastChar; + } + + /** + * @return whether we're at the line start (not counting any prefixes), i.e. after a {@link #line} or {@link #block}. + */ + public boolean isAtLineStart() { + return atLineStart; + } + + /** + * @return whether blocks are currently set to tight or loose, see {@link #setTight(boolean)} + */ + public boolean getTight() { + return tight; + } + + /** + * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight + * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines + * within the list. + * <p> + * Note that changing this does not affect block separators that have already been enqueued (with {@link #block()}, + * only future ones. + */ + public void setTight(boolean tight) { + this.tight = tight; + } + private void append(String s) { try { buffer.append(s); @@ -144,23 +198,4 @@ public class MarkdownWriter { blockSeparator = 0; } } - - /** - * @return whether blocks are currently set to tight or loose, see {@link #setTight(boolean)} - */ - public boolean getTight() { - return tight; - } - - /** - * Change whether blocks are tight or loose. Loose is the default where blocks are separated by a blank line. Tight - * is where blocks are not separated by a blank line. Tight blocks are used in lists, if there are no blank lines - * within the list. - * <p> - * Note that changing this does not affect block separators that have already been enqueued (with {@link #block()}, - * only future ones. - */ - public void setTight(boolean tight) { - this.tight = tight; - } }