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 a135d73eee8b706e8a221b34fef41c4024422006..dbaa9a47cb670dca99a0bddd5f33b528cbd24abe 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -13,6 +13,10 @@ import java.util.Set; /** * The node renderer that renders all the core nodes (comes last in the order of node renderers). + * <p> + * Note that while sometimes it would be easier to record what kind of syntax was used on parsing (e.g. ATX vs Setext + * heading), this renderer is intended to also work for documents that were created by directly creating + * {@link Node Nodes} instead. So in order to support that, it sometimes needs to do a bit more work. */ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRenderer { @@ -211,11 +215,34 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen @Override public void visit(Heading heading) { + if (heading.getLevel() <= 2) { + LineBreakVisitor lineBreakVisitor = new LineBreakVisitor(); + heading.accept(lineBreakVisitor); + boolean isMultipleLines = lineBreakVisitor.hasLineBreak(); + + if (isMultipleLines) { + // Setext headings: Can have multiple lines, but only level 1 or 2 + visitChildren(heading); + writer.line(); + 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("==="); + } else { + writer.write("---"); + } + writer.block(); + return; + } + } + + // ATX headings: Can't have multiple lines, but up to level 6. for (int i = 0; i < heading.getLevel(); i++) { writer.write('#'); } writer.write(' '); visitChildren(heading); + writer.block(); } @@ -392,4 +419,24 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen number = orderedList.getStartNumber(); } } + + private static class LineBreakVisitor extends AbstractVisitor { + private boolean lineBreak = false; + + public boolean hasLineBreak() { + return lineBreak; + } + + @Override + public void visit(SoftLineBreak softLineBreak) { + super.visit(softLineBreak); + lineBreak = true; + } + + @Override + public void visit(HardLineBreak hardLineBreak) { + super.visit(hardLineBreak); + lineBreak = true; + } + } } 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 4dc8dbff9cb5918506403f027587cf1c03f734d8..4dee17ed622173f8be7a2933d852c79c03c451c2 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -14,7 +14,7 @@ import java.util.List; * <p> * Note that it does not currently attempt to preserve the exact syntax of the original input Markdown (if any): * <ul> - * <li>Headings are always output as ATX headings for simplicity</li> + * <li>Headings are output as ATX headings if possible (multi-line headings need Setext headings)</li> * <li>Escaping might be over-eager, e.g. a plain {@code *} might be escaped * even though it doesn't need to be in that particular context</li> * </ul> diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java index f6ce1b4e05f0eb38ab2c66b1d6fa37f425c5370c..05a253fde7863144e92f9cc306f3089e81acbe9e 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -28,6 +28,9 @@ public class MarkdownRendererTest { assertRoundTrip("##### foo\n"); assertRoundTrip("###### foo\n"); + assertRoundTrip("Foo\nbar\n===\n"); + assertRoundTrip("[foo\nbar](/url)\n===\n"); + assertRoundTrip("# foo\n\nbar\n"); } diff --git a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java index 4e9396e777f5b3dc147171326483a3bb7b702836..1dd06441467d84cd374966cd2356b6e8897722e3 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/SpecMarkdownRendererTest.java @@ -62,7 +62,7 @@ public class SpecMarkdownRendererTest { System.out.println(); } - int expectedPassed = 622; + int expectedPassed = 625; assertTrue("Expected at least " + expectedPassed + " examples to pass but was " + passes.size(), passes.size() >= expectedPassed); }