From 3ae4b506d7a7e5560b4c7cf3ea996168bc597839 Mon Sep 17 00:00:00 2001 From: Brumbelow Date: Wed, 22 Apr 2026 14:16:01 -0400 Subject: [PATCH] fix: escape [ and ] in link text and image alt --- .../src/markitdown/converters/_markdownify.py | 7 +++++++ packages/markitdown/tests/test_module_misc.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/markitdown/src/markitdown/converters/_markdownify.py b/packages/markitdown/src/markitdown/converters/_markdownify.py index 19e8a2984..7712e4dc7 100644 --- a/packages/markitdown/src/markitdown/converters/_markdownify.py +++ b/packages/markitdown/src/markitdown/converters/_markdownify.py @@ -76,6 +76,11 @@ def convert_a( if self.options["default_title"] and not title: title = href title_part = ' "%s"' % title.replace('"', r"\"") if title else "" + # Escape [ and ] in link text per CommonMark ยง6.1; otherwise inner + # brackets cause nested-link ambiguity and renderers like GitHub + # silently truncate the text at the first unescaped ']' (#1302). + if href: + text = text.replace("[", r"\[").replace("]", r"\]") return ( "%s[%s](%s%s)%s" % (prefix, text, href, title_part, suffix) if href @@ -97,6 +102,8 @@ def convert_img( title_part = ' "%s"' % title.replace('"', r"\"") if title else "" # Remove all line breaks from alt alt = alt.replace("\n", " ") + # Escape [ and ] in alt text for the same reason as convert_a (#1302). + alt = alt.replace("[", r"\[").replace("]", r"\]") if ( convert_as_inline and el.parent.name not in self.options["keep_inline_images_in"] diff --git a/packages/markitdown/tests/test_module_misc.py b/packages/markitdown/tests/test_module_misc.py index 4d62e4919..c3942c21b 100644 --- a/packages/markitdown/tests/test_module_misc.py +++ b/packages/markitdown/tests/test_module_misc.py @@ -532,6 +532,24 @@ def test_markitdown_llm() -> None: validate_strings(result, PPTX_TEST_STRINGS) +def test_html_link_text_bracket_escaping() -> None: + """Brackets in text and alt must be backslash-escaped so + downstream Markdown parsers don't mis-parse them as nested / reference- + style links (issue #1302).""" + markitdown = MarkItDown() + + html = ( + b"

" + b'See Learn [GPT] and ' + b'Alt [with] brackets.' + b"

" + ) + result = markitdown.convert_stream(io.BytesIO(html), file_extension=".html") + + assert r"[Learn \[GPT\]](https://example.com/)" in result.text_content + assert r"![Alt \[with\] brackets](https://example.com/x.png)" in result.text_content + + if __name__ == "__main__": """Runs this file's tests from the command line.""" for test in [ @@ -547,6 +565,7 @@ def test_markitdown_llm() -> None: test_markitdown_exiftool, test_markitdown_llm_parameters, test_markitdown_llm, + test_html_link_text_bracket_escaping, ]: print(f"Running {test.__name__}...", end="") test()