Skip to content

Preserve literal ampersands in Markdown links and images - #468

Merged
robinst merged 1 commit into
commonmark:mainfrom
PHJ2000:fix-markdown-link-ampersands
Oct 27, 2026
Merged

robinst merged 1 commit into
commonmark:mainfrom
PHJ2000:fix-markdown-link-ampersands

Conversation

@PHJ2000

@PHJ2000 PHJ2000 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Rendering a Link or Image whose destination/title contains literal entity-like text (for example © or A) writes an unescaped ampersand. Parsing that Markdown again turns the text into a character, changing the AST value.

For example:

import org.commonmark.node.*;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.markdown.MarkdownRenderer;

public class CommonmarkAmpersandProbe {
    public static void main(String[] args) {
        Link link = new Link("/url?value=©", "literal A");
        link.appendChild(new Text("example"));
        String markdown = MarkdownRenderer.builder().build().render(link);
        Link reparsed = (Link) Parser.builder().build().parse(markdown).getFirstChild().getFirstChild();
        if (!link.getDestination().equals(reparsed.getDestination())
                || !link.getTitle().equals(reparsed.getTitle())) {
            throw new AssertionError("Changed to: " + reparsed.getDestination() + " / " + reparsed.getTitle());
        }
        System.out.println(markdown);
        System.out.println("Literal destination and title preserved");
    }
}

Use the existing MarkdownWriter escaping for ampersands in bare/angle-bracket destinations and quoted titles. The regression tests cover links and images, named/decimal/hex entities, angle-bracket destinations, and ordinary ampersands. Ordinary query separators are also escaped in the Markdown output, while the parsed values stay the same; the renderer does not promise identical Markdown syntax.

Validation:

  • Baseline: 14 of the 18 new parameter cases fail; the four ordinary-ampersand controls pass.
  • JDK 21: focused renderer and spec tests pass; ./mvnw clean verify javadoc:javadoc spotless:check succeeds (4,603 test entries, 1 skipped, no failures/errors). Javadoc emits nonfatal missing sibling target/apidocs link messages; generation completes.
  • Actual JDK 11: a standalone destination/title round-trip example compiles and runs. The separate Android project was not run.
  • A separate AI review reproduced the 14 baseline failures and passing focused tests. It did not repeat the full reactor or JDK 11 probe.

AI disclosure: OpenAI Codex assisted with investigation, implementation, tests, and this description. The separate review was also performed by an AI session, not a human reviewer.

Escape entity-like values in destinations and titles through the existing
MarkdownWriter path so parsing the output preserves the AST values.

Generated-by: OpenAI Codex
@robinst
robinst merged commit 3706cda into commonmark:main Oct 27, 2026
5 checks passed
@robinst

robinst commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants