From c20199db80164fce2464ba6323537bafe66c5c63 Mon Sep 17 00:00:00 2001 From: Alexandre <46300952+alexbertho@users.noreply.github.com> Date: Sun, 8 Nov 2026 14:01:31 +0100 Subject: [PATCH] Keep wiki links out of existing links and HTML attributes --- CHANGES.md | 1 + lib/markdown2.py | 18 ++++++++++++++++-- test/test_markdown2.py | 27 +++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 93e5b900..2bb0e251 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## python-markdown2 2.5.6 (not yet released) +- Fix `wiki-links` generating nested anchors and replacing syntax inside image descriptions and HTML attributes. - [pull #762] Speed up documents with many links by skipping the backslash-escape scan for text without a backslash - [pull #746] Preserve query strings and fragments in `markdown-file-links` conversions, including reference-link targets. - [pull #739] Fix `header-ids` extra putting sanitized HTML into header ids in safe mode, which allowed attribute injection (#649) diff --git a/lib/markdown2.py b/lib/markdown2.py index 9be0a262..5828763e 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -4482,6 +4482,8 @@ class WikiLinks(Extra): ''' Wiki-style links. `[[Page Name]]` becomes a link to another page. This is modelled on the wikilinks extension shipped with Python-Markdown. + Wiki syntax inside existing links, image descriptions, or HTML attributes + is left as literal text. The generated URL and CSS class can be tuned with the following options: @@ -4500,12 +4502,24 @@ class WikiLinks(Extra): yields ``Foo Bar``. ''' name = 'wiki-links' - order = (Stage.LINKS,), () + order = (), (Stage.LINKS,) _wiki_link_re = re.compile(r'\[\[([\w0-9_ -]+)\]\]', re.UNICODE) def run(self, text: str) -> str: - return self._wiki_link_re.sub(self.sub, text) + # Resolve Markdown links first, then only link text outside existing + # anchors and HTML markup (including image attributes). + tokens = self.md._sorta_html_tokenize_re.split(text) + in_anchor = False + for index, token in enumerate(tokens): + if index % 2: + if re.match(r')', token, re.I): + in_anchor = True + elif re.match(r'', token, re.I): + in_anchor = False + elif not in_anchor: + tokens[index] = self._wiki_link_re.sub(self.sub, token) + return ''.join(tokens) def build_url(self, label: str) -> str: base_url = self.options.get('base_url', '/') diff --git a/test/test_markdown2.py b/test/test_markdown2.py index 1a69c5b7..e81cd413 100755 --- a/test/test_markdown2.py +++ b/test/test_markdown2.py @@ -220,6 +220,33 @@ class DirectTestCase(_MarkdownTestCase): Python-markdown (markdown.py). """ + def test_wiki_links_in_links_and_images(self): + cases = [ + ('[go [[Home]]](https://example.com)', + '

go [[Home]]

\n'), + ('![go [[Home]]](img.png)', + '

go [[Home]]

\n'), + ('[go [[Home]]][target]\n\n[target]: https://example.com', + '

go [[Home]]

\n'), + ('[[Home]]', + '

[[Home]]

\n'), + ('[[Home]] [[Page]]', + '

[[Home]] Page

\n'), + ('![go [[Home]]][img]\n\n[img]: pic.png', + '

go [[Home]]

\n'), + ('[![go [[Home]]](img.png)](/go)', + '

go [[Home]]

\n'), + (r'\[[Home]] `[[Home]]` [[Page]]', + '

[[Home]] [[Home]] Page

\n'), + ('[[Page]]', + '

Page

\n'), + ('[go](https://example.com) [[Home]]', + '

go Home

\n'), + ] + for source, expected in cases: + with self.subTest(source=source): + self.assertEqual(markdown2.markdown(source, extras=['wiki-links']), expected) + def test_header_ids_from_file_vars(self): md = markdown2.Markdown(use_file_vars=True) modeline = "\n"