From 724619b0efc5937b4fba974769cea7fa6049dffc Mon Sep 17 00:00:00 2001 From: Robin Stocker <robin@nibor.org> Date: Mon, 22 Jan 2024 22:46:33 +1100 Subject: [PATCH] Use Setext heading if necessary --- .../markdown/CoreMarkdownNodeRenderer.java | 47 +++++++++++++++++++ .../renderer/markdown/MarkdownRenderer.java | 2 +- .../markdown/MarkdownRendererTest.java | 3 ++ .../markdown/SpecMarkdownRendererTest.java | 2 +- 4 files changed, 52 insertions(+), 2 deletions(-) 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 a135d73e..dbaa9a47 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 4dc8dbff..4dee17ed 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 f6ce1b4e..05a253fd 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 4e9396e7..1dd06441 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); } -- GitLab