Skip to content
Open
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## python-markdown2 2.5.6 (not yet released)

- [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)
- [pull #732] Fix excessive CPU use in the inline HTML tokenizer on repeated unclosed tag fragments (#707)
- [pull #736] Clarify how to enable file variables in the Python API and command-line help (#269, #365).
Expand Down
30 changes: 22 additions & 8 deletions lib/markdown2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3928,7 +3928,8 @@ class _MarkdownFileLinksExtraOpts(_LinkProcessorExtraOpts, total=False):

class MarkdownFileLinks(LinkProcessor):
'''
Replace links to `.md` files with `.html` links
Replace links to `.md` files with `.html` links, preserving query strings
and fragments.
'''

name = 'markdown-file-links'
Expand All @@ -3942,24 +3943,37 @@ def __init__(self, md: Markdown, options: Optional[dict]):

def parse_inline_anchor_or_image(self, text: str, _link_text: str, start_idx: int):
result = super().parse_inline_anchor_or_image(text, _link_text, start_idx)
if not result or not result[1] or not result[1].endswith('.md'):
# return None for invalid markup, or links that don't end with '.md'
# so that we don't touch them, and other extras can process them freely
if not result or not result[1]:
return
url = self._convert_url(result[1])
if url == result[1]:
# Leave other links available for processing by other extras.
return
url = result[1].removesuffix('.md') + '.html'
return result[0], url, *result[2:]

@staticmethod
def _convert_url(url: str) -> str:
# Split before the suffix, keeping its spelling and empty delimiters.
path = re.split(r'[?#]', url, maxsplit=1)[0]
if path.endswith('.md'):
return path.removesuffix('.md') + '.html' + url[len(path):]
return url

def run(self, text: str):
if Stage.LINKS > self.md.order > Stage.LINK_DEFS and self.options.get('link_defs', True):
# running just after link defs have been stripped
for key, url in self.md.urls.items():
if url.endswith('.md'):
self.md.urls[key] = url.removesuffix('.md') + '.html'
path = re.split(r'[?#]', url, maxsplit=1)[0]
if path.endswith('.md'):
self.md.urls[key] = self._convert_url(url)

return super().run(text)

def test(self, text: str):
return super().test(text) and '.md' in text
# Link definitions have already been removed from text at this stage.
return super().test(text) and (
'.md' in text or self.md.stage == Stage.LINK_DEFS
)


class Mermaid(FencedCodeBlocks):
Expand Down
35 changes: 35 additions & 0 deletions test/tm-cases/markdown_file_links_inline_suffixes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<p><a href="guide.html">doc</a></p>

<p><a href="guide.html#install">doc</a></p>

<p><a href="guide.html?view=print">doc</a></p>

<p><a href="guide.html?view=print#install">doc</a></p>

<p><a href="guide.html#part?view=print">doc</a></p>

<p><a href="guide.html?">doc</a></p>

<p><a href="guide.html#">doc</a></p>

<p><a href="guide.html?#">doc</a></p>

<p><a href="./guide.html?next=other.md#part.md">doc</a></p>

<p><a href="https://example.com/guide.html#install">doc</a></p>

<p><a href="//example.com/guide.html?view=print">doc</a></p>

<p><a href="#part.md">doc</a></p>

<p><a href="?download=guide.md">doc</a></p>

<p><a href="guide.html#part.md">doc</a></p>

<p><a href="guide.html?next=guide.md">doc</a></p>

<p><a href="guide.md/child">doc</a></p>

<p><a href="guide.md.bak#part">doc</a></p>

<p><img src="guide.md#install" alt="image" /></p>
1 change: 1 addition & 0 deletions test/tm-cases/markdown_file_links_inline_suffixes.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{'extras': ['markdown-file-links']}
35 changes: 35 additions & 0 deletions test/tm-cases/markdown_file_links_inline_suffixes.text
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[doc](guide.md)

[doc](guide.md#install)

[doc](guide.md?view=print)

[doc](guide.md?view=print#install)

[doc](guide.md#part?view=print)

[doc](guide.md?)

[doc](guide.md#)

[doc](guide.md?#)

[doc](./guide.md?next=other.md#part.md)

[doc](https://example.com/guide.md#install)

[doc](//example.com/guide.md?view=print)

[doc](#part.md)

[doc](?download=guide.md)

[doc](guide.html#part.md)

[doc](guide.html?next=guide.md)

[doc](guide.md/child)

[doc](guide.md.bak#part)

![image](guide.md#install)
2 changes: 2 additions & 0 deletions test/tm-cases/markdown_file_links_no_linkdefs.html
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
<p><a href="./something.md">This is a reference to a markdown file link</a> but link definition swapping is disabled</p>

<p><a href="guide.html#install">inline</a> and <a href="guide.md#install">reference</a></p>
3 changes: 3 additions & 0 deletions test/tm-cases/markdown_file_links_no_linkdefs.text
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[This is a reference to a markdown file link][] but link definition swapping is disabled


[inline](guide.md#install) and [reference][suffix-ref]

[This is a reference to a markdown file link]: ./something.md
[suffix-ref]: guide.md#install
33 changes: 33 additions & 0 deletions test/tm-cases/markdown_file_links_reference_suffixes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<p><a href="guide.html">doc</a></p>

<p><a href="guide.html#install">doc</a></p>

<p><a href="guide.html?view=print">doc</a></p>

<p><a href="guide.html?view=print#install">doc</a></p>

<p><a href="guide.html#part?view=print">doc</a></p>

<p><a href="guide.html?">doc</a></p>

<p><a href="guide.html#">doc</a></p>

<p><a href="guide.html?#">doc</a></p>

<p><a href="./guide.html?next=other.md#part.md">doc</a></p>

<p><a href="https://example.com/guide.html#install">doc</a></p>

<p><a href="//example.com/guide.html?view=print">doc</a></p>

<p><a href="#part.md">doc</a></p>

<p><a href="?download=guide.md">doc</a></p>

<p><a href="guide.html#part.md">doc</a></p>

<p><a href="guide.html?next=guide.md">doc</a></p>

<p><a href="guide.md/child">doc</a></p>

<p><a href="guide.md.bak#part">doc</a></p>
1 change: 1 addition & 0 deletions test/tm-cases/markdown_file_links_reference_suffixes.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{'extras': ['markdown-file-links']}
51 changes: 51 additions & 0 deletions test/tm-cases/markdown_file_links_reference_suffixes.text
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[doc][suffix-1]

[doc][suffix-2]

[doc][suffix-3]

[doc][suffix-4]

[doc][suffix-5]

[doc][suffix-6]

[doc][suffix-7]

[doc][suffix-8]

[doc][suffix-9]

[doc][suffix-10]

[doc][suffix-11]

[doc][suffix-12]

[doc][suffix-13]

[doc][suffix-14]

[doc][suffix-15]

[doc][suffix-16]

[doc][suffix-17]

[suffix-1]: guide.md
[suffix-2]: guide.md#install
[suffix-3]: guide.md?view=print
[suffix-4]: guide.md?view=print#install
[suffix-5]: guide.md#part?view=print
[suffix-6]: guide.md?
[suffix-7]: guide.md#
[suffix-8]: guide.md?#
[suffix-9]: ./guide.md?next=other.md#part.md
[suffix-10]: https://example.com/guide.md#install
[suffix-11]: //example.com/guide.md?view=print
[suffix-12]: #part.md
[suffix-13]: ?download=guide.md
[suffix-14]: guide.html#part.md
[suffix-15]: guide.html?next=guide.md
[suffix-16]: guide.md/child
[suffix-17]: guide.md.bak#part