From ed9d49539bd4d680bea894a49ba428a836f91b1c Mon Sep 17 00:00:00 2001 From: cherith <315844584+Cherith1222@users.noreply.github.com> Date: Wed, 7 Oct 2026 06:47:24 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20Keep=20escapes=20and?= =?UTF-8?q?=20entities=20in=20image=20alt=20text?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Image alt text dropped text_special tokens, so a backslash escape or an HTML entity disappeared from the alt attribute. AI-assisted (Cursor). Not yet reviewed by a human. --- myst_parser/mdit_to_docutils/base.py | 7 ++++--- tests/test_image_alt.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 tests/test_image_alt.py diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py index 68cfae72..0d853b6f 100644 --- a/myst_parser/mdit_to_docutils/base.py +++ b/myst_parser/mdit_to_docutils/base.py @@ -482,10 +482,11 @@ def renderInlineAsText(self, tokens: list[SyntaxTreeNode]) -> str: # noqa: N802 result = "" for token in tokens or []: - if token.type == "text": + if token.type in {"text", "text_special"}: + # An escape or entity is a text_special token with no children. + # Its content is the character the alt text must keep + # (executablebooks/MyST-Parser#1210). result += token.content - # elif token.type == "image": - # result += self.renderInlineAsText(token.children) else: result += self.renderInlineAsText(token.children or []) return result diff --git a/tests/test_image_alt.py b/tests/test_image_alt.py new file mode 100644 index 00000000..3f13405b --- /dev/null +++ b/tests/test_image_alt.py @@ -0,0 +1,30 @@ +"""Image alt text keeps CommonMark escapes and entities.""" + +from docutils.core import publish_parts + +from myst_parser.parsers.docutils_ import Parser + + +def _html(source: str) -> str: + return publish_parts(source, parser=Parser(), writer_name="html5")["body"].strip() + + +def test_image_alt_keeps_escapes_and_entities(): + """Escapes and entities inside an image alt are not dropped. + + markdown-it-py represents them as ``text_special`` tokens with no children. + Reading only ``text`` tokens used to drop the character + (executablebooks/MyST-Parser#1210). The same source in a link already + rendered the character. + """ + assert _html(r"![a \* b](x.png)") == '

a * b

' + assert _html("![a & b](x.png)") == '

a & b

' + assert ( + _html(r"![C:\Python26](x.png)") + == '

C:\\Python26

' + ) + assert ( + _html(r"[a \* b & c](https://example.com)") + == '

' + "a * b & c

" + ) From bcf5d06da4da277506edd40e9972c3ca534cb4fe Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 9 Nov 2026 20:32:56 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=A7=AA=20TEST:=20move=20the=20image?= =?UTF-8?q?=20alt=20cases=20into=20the=20syntax=20fixtures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Image rendering is pinned in `docutil_syntax_elements.md`, so the alt cases live there as doctree fixtures rather than as html5 writer strings. Adds an escape and entity inside emphasis, an escape in a nested image, and a reference-style image. All four fail without the fix. --- tests/test_image_alt.py | 30 --------------- .../fixtures/docutil_syntax_elements.md | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 30 deletions(-) delete mode 100644 tests/test_image_alt.py diff --git a/tests/test_image_alt.py b/tests/test_image_alt.py deleted file mode 100644 index 3f13405b..00000000 --- a/tests/test_image_alt.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Image alt text keeps CommonMark escapes and entities.""" - -from docutils.core import publish_parts - -from myst_parser.parsers.docutils_ import Parser - - -def _html(source: str) -> str: - return publish_parts(source, parser=Parser(), writer_name="html5")["body"].strip() - - -def test_image_alt_keeps_escapes_and_entities(): - """Escapes and entities inside an image alt are not dropped. - - markdown-it-py represents them as ``text_special`` tokens with no children. - Reading only ``text`` tokens used to drop the character - (executablebooks/MyST-Parser#1210). The same source in a link already - rendered the character. - """ - assert _html(r"![a \* b](x.png)") == '

a * b

' - assert _html("![a & b](x.png)") == '

a & b

' - assert ( - _html(r"![C:\Python26](x.png)") - == '

C:\\Python26

' - ) - assert ( - _html(r"[a \* b & c](https://example.com)") - == '

' - "a * b & c

" - ) diff --git a/tests/test_renderers/fixtures/docutil_syntax_elements.md b/tests/test_renderers/fixtures/docutil_syntax_elements.md index c2307953..42c909a1 100644 --- a/tests/test_renderers/fixtures/docutil_syntax_elements.md +++ b/tests/test_renderers/fixtures/docutil_syntax_elements.md @@ -190,6 +190,44 @@ Image with escapable html: alt . +Image alt with escapes and entities: +. +![a \* b & C:\Python26](x.png) +. + + + a * b & C:\Python26 +. + +Image alt with escapes and entities in emphasis: +. +![a *\* b ©*](x.png) +. + + + a * b © +. + +Image alt with an escape in a nested image: +. +![outer ![in\*ner](y.png)](x.png) +. + + + outer in*ner +. + +Image alt with an escape, reference style: +. +![a \* b][r] + +[r]: x.png +. + + + a * b +. + Block Quote: . > *foo*