🐛 FIX: render hardbreak and inline HTML in image alt text (#409)
`RendererHTML.renderInlineAsText` builds the `alt` attribute for images
by walking the inline token stream, but it only handles `text`, `image`
and `softbreak` tokens. `hardbreak`, `html_inline` and `html_block` are
silently dropped, so a hard line break or inline HTML inside an image
description disappears from the alt text.
```python
from markdown_it import MarkdownIt
md = MarkdownIt("commonmark")
md.render("") # hard break (backslash)
# now: <p><img src="/url" alt="foobar" /></p>
# want: <p><img src="/url" alt="foo\nbar" /></p> (soft break already does this)
md.render("") # inline HTML
# now: <p><img src="/url" alt="ac" /></p>
# want: <p><img src="/url" alt="a<b>c" /></p>
```
### Why this is the intended behaviour
Two independent references, both pointing the same way:
- **JS parity.** This project is a port of `markdown-it` and the
changelog pins the parser to `markdown-it v14.1.0`. Upstream
`renderInlineAsText` at that version handles `text`, `image`,
`html_inline`, `html_block`, `softbreak` **and** `hardbreak`. The Python
port is behind its own declared target for the last three.
- **Internal consistency.** `softbreak` and `hardbreak` are treated as
an identical pair everywhere else in the codebase; #157 previously added
the `softbreak` branch to this very function but stopped there. This
change is the sibling of that fix.
HTML content in `alt` is escaped downstream by `renderAttrs` →
`escapeHtml` (so `<b>` becomes `<b>`), matching the JS behaviour —
no markup reaches the attribute unescaped.
### The fix
Two grouped branches added to `renderInlineAsText`, making it a 1:1
match of the upstream reference:
```python
elif token.type in ("html_inline", "html_block"):
result += token.content
elif token.type in ("softbreak", "hardbreak"):
result += "\n"
```
### Verification
- Added two cases to `tests/test_port/fixtures/commonmark_extras.md`
(hard break and inline HTML in an image description), mirroring the
existing #157 soft-break fixture. Both **fail without the change** and
**pass with it**.
- `renderInlineAsText` has a single production caller (the `image`
renderer), and the soft-break / plain-text paths are unchanged (verified
as controls).
- Full test suite: **983 passed**. `pre-commit` ruff, ruff-format and
mypy (strict) all clean.
---
This PR was authored by an AI coding agent (Claude Code) running on this
account: the AI found the bug, ran the repro, wrote the tests, and wrote
this description. The human account holder reviews every change and is
accountable for it. The verification above is real and re-runnable from
the diff. If this isn't the kind of contribution you want, say so and
I'll close it.
Signed-off-by: chuenchen309 <48723787+chuenchen309@users.noreply.github.com>
Co-authored-by: Chris Sewell <chrisj_sewell@hotmail.com>
GitOrigin-RevId: 8501cf3369dc879353c5e5a2280941f73c42d392
Change-Id: Ifca14f1ecc8e9c02e24e8c52f69f60d3599074a4
Markdown parser done right.
This is a Python port of markdown-it, and some of its associated plugins. For more details see: https://markdown-it-py.readthedocs.io.
For details on markdown-it itself, see:
See also: markdown-it-pyrs for an experimental Rust binding, for even more speed!
pip install markdown-it-py[plugins]
or with extras
pip install markdown-it-py[linkify,plugins]
conda install -c conda-forge markdown-it-py
or with extras
conda install -c conda-forge markdown-it-py linkify-it-py mdit-py-plugins
Render markdown to HTML with markdown-it-py and a custom configuration with and without plugins and features:
from markdown_it import MarkdownIt from mdit_py_plugins.front_matter import front_matter_plugin from mdit_py_plugins.footnote import footnote_plugin md = ( MarkdownIt('commonmark', {'breaks':True,'html':True}) .use(front_matter_plugin) .use(footnote_plugin) .enable('table') ) text = (""" --- a: 1 --- a | b - | - 1 | 2 A footnote [^1] [^1]: some details """) tokens = md.parse(text) html_text = md.render(text) ## To export the html to a file, uncomment the lines below: # from pathlib import Path # Path("output.html").write_text(html_text)
Render markdown to HTML with markdown-it-py from the command-line:
usage: markdown-it [-h] [-v] [--stdin|filenames [filenames ...]] Parse one or more markdown files, convert each to HTML, and print to stdout positional arguments: --stdin read source Markdown file from standard input filenames specify an optional list of files to convert optional arguments: -h, --help show this help message and exit -v, --version show program's version number and exit Interactive: $ markdown-it markdown-it-py [version 0.0.0] (interactive) Type Ctrl-D to complete input, or Ctrl-C to exit. >>> # Example ... > markdown *input* ... <h1>Example</h1> <blockquote> <p>markdown <em>input</em></p> </blockquote> Batch: $ markdown-it README.md README.footer.md > index.html
Big thanks to the authors of markdown-it:
Also John MacFarlane for his work on the CommonMark spec and reference implementations.