From e45a48033f430d7675cd8388eaf5057e94e9ea96 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Mon, 3 Aug 2026 20:53:10 +1000 Subject: [PATCH] New option lineSeparator for MarkdownRenderer.Builder --- CHANGELOG.md | 6 +++++ .../renderer/markdown/MarkdownRenderer.java | 17 +++++++++++- .../renderer/markdown/MarkdownWriter.java | 27 +++++++++++++++---- .../markdown/MarkdownRendererTest.java | 7 +++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1dc50874..5d5c88239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html), with the exception that 0.x versions can break between minor versions. +## [Unreleased] +### Added +- New option `lineSeparator` for `MarkdownRenderer.Builder` to change the default line + separator from `\n` (e.g. to `\r\n`) (#442) + ## [0.29.0] - 2026-06-20 ### Added - Support rendering GFM task list items to Markdown (#433) @@ -560,6 +565,7 @@ API breaking changes (caused by changes in spec): Initial release of commonmark-java, a port of commonmark.js with extensions for autolinking URLs, GitHub flavored strikethrough and tables. +[Unreleased]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.29.0...HEAD [0.29.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.28.0...commonmark-parent-0.29.0 [0.28.0]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.1...commonmark-parent-0.28.0 [0.27.1]: https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.27.0...commonmark-parent-0.27.1 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 1acde9a55..edef645a4 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownRenderer.java @@ -27,9 +27,11 @@ */ public class MarkdownRenderer implements Renderer { + private final String lineSeparator; private final List nodeRendererFactories; private MarkdownRenderer(Builder builder) { + this.lineSeparator = builder.lineSeparator; 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. @@ -58,7 +60,7 @@ public static Builder builder() { @Override public void render(Node node, Appendable output) { - RendererContext context = new RendererContext(new MarkdownWriter(output)); + var context = new RendererContext(new MarkdownWriter(output, lineSeparator)); context.render(node); } @@ -74,6 +76,7 @@ public String render(Node node) { */ public static class Builder { + private String lineSeparator = "\n"; private final List nodeRendererFactories = new ArrayList<>(); /** @@ -83,6 +86,18 @@ public MarkdownRenderer build() { return new MarkdownRenderer(this); } + /** + * Set the line separator (line terminator) that the writer should use. The default is a + * newline ({@code \n}) on all systems. + * + * @param lineSeparator + * @return {@code this} + */ + public Builder lineSeparator(String lineSeparator) { + this.lineSeparator = lineSeparator; + 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. 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 074a8e939..9c3aba483 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/MarkdownWriter.java @@ -8,6 +8,7 @@ public class MarkdownWriter { private final Appendable buffer; + private final String lineSeparator; private int blockSeparator = 0; private char lastChar; @@ -21,7 +22,12 @@ public class MarkdownWriter { private final LinkedList rawEscapes = new LinkedList<>(); public MarkdownWriter(Appendable out) { - buffer = out; + this(out, "\n"); + } + + public MarkdownWriter(Appendable out, String lineSeparator) { + this.buffer = out; + this.lineSeparator = lineSeparator; } /** Write the supplied string (raw/unescaped except if {@link #pushRawEscape} was used). */ @@ -53,9 +59,9 @@ public void text(String s, CharMatcher escape) { atLineStart = false; } - /** Write a newline (line terminator). */ + /** Write a line separator (line terminator). */ public void line() { - write('\n'); + write(lineSeparator); writePrefixes(); atLineStart = true; } @@ -167,6 +173,17 @@ private void write(String s, CharMatcher escape) { atLineStart = false; } + private void write(String s) { + try { + buffer.append(s); + } catch (IOException e) { + throw new RuntimeException(e); + } + + lastChar = s.charAt(s.length() - 1); + atLineStart = false; + } + private void write(char c) { try { append(c, null); @@ -192,10 +209,10 @@ private void writePrefixes() { */ private void flushBlockSeparator() { if (blockSeparator != 0) { - write('\n'); + write(lineSeparator); writePrefixes(); if (blockSeparator > 1) { - write('\n'); + write(lineSeparator); writePrefixes(); } blockSeparator = 0; 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 c51fb895f..270024542 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -312,6 +312,13 @@ public void testSoftLineBreaks() { assertRoundTrip("foo\nbar\n"); } + @Test + public void testCustomLineSeparator() { + var renderer = MarkdownRenderer.builder().lineSeparator("\r\n").build(); + assertThat(renderer.render(parse("# Heading\n\nBody\n"))) + .isEqualTo("# Heading\r\n\r\nBody\r\n"); + } + @Test public void overrideNodeRender() { var nodeRendererFactory =