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 1c6ee257..15e1fc2e 100644 --- a/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java +++ b/commonmark/src/main/java/org/commonmark/renderer/markdown/CoreMarkdownNodeRenderer.java @@ -27,10 +27,10 @@ public class CoreMarkdownNodeRenderer extends AbstractVisitor implements NodeRen private final CharMatcher textEscapeInHeading; private final CharMatcher linkDestinationNeedsAngleBrackets = AsciiMatcher.builder().c(' ').c('(').c(')').c('<').c('>').c('\n').c('\\').build(); - private final CharMatcher linkDestinationEscapeInAngleBrackets = - AsciiMatcher.builder().c('<').c('>').c('\n').c('\\').build(); + private final CharMatcher linkDestinationEscape = + AsciiMatcher.builder().c('<').c('>').c('\n').c('\\').c('&').build(); private final CharMatcher linkTitleEscapeInQuotes = - AsciiMatcher.builder().c('"').c('\n').c('\\').build(); + AsciiMatcher.builder().c('"').c('\n').c('\\').c('&').build(); private final Pattern orderedListMarkerPattern = Pattern.compile("^([0-9]{1,9})([.)])"); @@ -523,10 +523,10 @@ private void writeLinkLike(String title, String destination, Node node, String o writer.raw('('); if (contains(destination, linkDestinationNeedsAngleBrackets)) { writer.raw('<'); - writer.text(destination, linkDestinationEscapeInAngleBrackets); + writer.text(destination, linkDestinationEscape); writer.raw('>'); } else { - writer.raw(destination); + writer.text(destination, linkDestinationEscape); } if (title != null) { writer.raw(' '); 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 27002454..a0d4cdcd 100644 --- a/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java +++ b/commonmark/src/test/java/org/commonmark/renderer/markdown/MarkdownRendererTest.java @@ -8,6 +8,8 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class MarkdownRendererTest { @@ -297,6 +299,60 @@ public void testImages() { assertRoundTrip("![a](/uri \"foo \\\" bar\")\n"); } + @ParameterizedTest + @ValueSource( + strings = { + "/url?value=©", + "/my uri?value=©", + "/url?value=A", + "/url?value=A", + "/url?a=1&b=2" + }) + public void testLinkDestinationPreservesLiteralAmpersands(String destination) { + Link link = new Link(destination, null); + link.appendChild(new Text("link")); + + Link reparsed = (Link) parse(render(link)).getFirstChild().getFirstChild(); + assertThat(reparsed.getDestination()).isEqualTo(destination); + } + + @ParameterizedTest + @ValueSource( + strings = { + "/url?value=©", + "/my uri?value=©", + "/url?value=A", + "/url?value=A", + "/url?a=1&b=2" + }) + public void testImageDestinationPreservesLiteralAmpersands(String destination) { + Image image = new Image(destination, null); + image.appendChild(new Text("image")); + + Image reparsed = (Image) parse(render(image)).getFirstChild().getFirstChild(); + assertThat(reparsed.getDestination()).isEqualTo(destination); + } + + @ParameterizedTest + @ValueSource(strings = {"literal ©", "literal A", "literal A", "bread & butter"}) + public void testLinkTitlePreservesLiteralAmpersands(String title) { + Link link = new Link("/url", title); + link.appendChild(new Text("link")); + + Link reparsed = (Link) parse(render(link)).getFirstChild().getFirstChild(); + assertThat(reparsed.getTitle()).isEqualTo(title); + } + + @ParameterizedTest + @ValueSource(strings = {"literal ©", "literal A", "literal A", "bread & butter"}) + public void testImageTitlePreservesLiteralAmpersands(String title) { + Image image = new Image("/url", title); + image.appendChild(new Text("image")); + + Image reparsed = (Image) parse(render(image)).getFirstChild().getFirstChild(); + assertThat(reparsed.getTitle()).isEqualTo(title); + } + @Test public void testHtmlInline() { assertRoundTrip("*foo*\n");