From 552ab95d98f01f49b1d9a5422a1be01f4cc8b2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar=20Rubio?= Date: Fri, 26 Sep 2025 10:48:17 +0200 Subject: [PATCH 1/3] Escape placeholders to avoid input collisions --- pyproject.toml | 2 +- src/mkdocs_include_markdown_plugin/event.py | 15 +++++++++++- tests/test_unit/test_include_markdown.py | 26 ++++++++++++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cbe52d7..001c794 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mkdocs-include-markdown-plugin" -version = "7.1.7" +version = "7.1.8" description = "Mkdocs Markdown includer plugin." readme = "README.md" license = "Apache-2.0" diff --git a/src/mkdocs_include_markdown_plugin/event.py b/src/mkdocs_include_markdown_plugin/event.py index 5924927..01e72ff 100644 --- a/src/mkdocs_include_markdown_plugin/event.py +++ b/src/mkdocs_include_markdown_plugin/event.py @@ -59,6 +59,16 @@ def build_placeholder(num: int) -> str: return f'{INLINE_PLACEHOLDER_PREFIX}{num}{ETX}' +def escape_placeholders(text: str) -> str: + """Escape placeholders in the given text.""" + return text.replace(STX, f'\\{STX}').replace(ETX, f'\\{ETX}') + + +def unescape_placeholders(text: str) -> str: + """Unescape placeholders in the given text.""" + return text.replace(f'\\{STX}', STX).replace(f'\\{ETX}', ETX) + + def save_placeholder( placeholders_contents: list[tuple[str, str]], text_to_include: str, @@ -107,6 +117,9 @@ def get_file_content( # noqa: PLR0913, PLR0915 placeholders_contents: list[tuple[str, str]] = [] + # Escape placeholders + markdown = escape_placeholders(markdown) + def found_include_tag( # noqa: PLR0912, PLR0915 match: re.Match[str], ) -> str: @@ -624,7 +637,7 @@ def found_include_markdown_tag( # noqa: PLR0912, PLR0915 # Replace placeholders by contents for placeholder, text in placeholders_contents: markdown = markdown.replace(placeholder, text, 1) - return markdown + return unescape_placeholders(markdown) def on_page_markdown( diff --git a/tests/test_unit/test_include_markdown.py b/tests/test_unit/test_include_markdown.py index 3d5ca94..3cd535b 100644 --- a/tests/test_unit/test_include_markdown.py +++ b/tests/test_unit/test_include_markdown.py @@ -2,7 +2,10 @@ import pytest -from mkdocs_include_markdown_plugin.event import on_page_markdown +from mkdocs_include_markdown_plugin.event import ( + build_placeholder, + on_page_markdown, +) @pytest.mark.parametrize( @@ -776,6 +779,27 @@ [], id='internal-anchor', ), + + # Placeholder collision + pytest.param( + '''# Header + +''' + build_placeholder(0) + ''' + +{% + include-markdown "{filepath}" +%} +''', + 'Content to include', + '''# Header + +''' + build_placeholder(0) + ''' + +Content to include +''', + [], + id='placeholder-collision', + ), ), ) def test_include_markdown( From de4f7d5d10b46b15b7cab972fd71f2a45dacaa57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar=20Rubio?= Date: Fri, 26 Sep 2025 10:50:01 +0200 Subject: [PATCH 2/3] Drop comment --- src/mkdocs_include_markdown_plugin/event.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mkdocs_include_markdown_plugin/event.py b/src/mkdocs_include_markdown_plugin/event.py index 01e72ff..d8aa73b 100644 --- a/src/mkdocs_include_markdown_plugin/event.py +++ b/src/mkdocs_include_markdown_plugin/event.py @@ -115,10 +115,8 @@ def get_file_content( # noqa: PLR0913, PLR0915 else: settings_ignore_paths = [] - placeholders_contents: list[tuple[str, str]] = [] - - # Escape placeholders markdown = escape_placeholders(markdown) + placeholders_contents: list[tuple[str, str]] = [] def found_include_tag( # noqa: PLR0912, PLR0915 match: re.Match[str], From f7fba4e1721dbf1b811217ea12bf73ba28dd377e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar=20Rubio?= Date: Fri, 26 Sep 2025 10:54:03 +0200 Subject: [PATCH 3/3] Refactor --- src/mkdocs_include_markdown_plugin/event.py | 39 +++---------------- .../placeholders.py | 37 ++++++++++++++++++ tests/test_unit/test_include_markdown.py | 6 +-- 3 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 src/mkdocs_include_markdown_plugin/placeholders.py diff --git a/src/mkdocs_include_markdown_plugin/event.py b/src/mkdocs_include_markdown_plugin/event.py index d8aa73b..e621ad6 100644 --- a/src/mkdocs_include_markdown_plugin/event.py +++ b/src/mkdocs_include_markdown_plugin/event.py @@ -28,6 +28,11 @@ ) from mkdocs_include_markdown_plugin.files_watcher import FilesWatcher from mkdocs_include_markdown_plugin.logger import logger +from mkdocs_include_markdown_plugin.placeholders import ( + escape_placeholders, + save_placeholder, + unescape_placeholders, +) if TYPE_CHECKING: # pragma: no cover @@ -46,40 +51,6 @@ ) -# Placeholders (taken from Python-Markdown) -STX = '\u0002' -''' "Start of Text" marker for placeholder templates. ''' -ETX = '\u0003' -''' "End of Text" marker for placeholder templates. ''' -INLINE_PLACEHOLDER_PREFIX = f'{STX}klzzwxh:' - - -def build_placeholder(num: int) -> str: - """Return a placeholder.""" - return f'{INLINE_PLACEHOLDER_PREFIX}{num}{ETX}' - - -def escape_placeholders(text: str) -> str: - """Escape placeholders in the given text.""" - return text.replace(STX, f'\\{STX}').replace(ETX, f'\\{ETX}') - - -def unescape_placeholders(text: str) -> str: - """Unescape placeholders in the given text.""" - return text.replace(f'\\{STX}', STX).replace(f'\\{ETX}', ETX) - - -def save_placeholder( - placeholders_contents: list[tuple[str, str]], - text_to_include: str, -) -> str: - """Save the included text and return the placeholder.""" - inclusion_index = len(placeholders_contents) - placeholder = build_placeholder(inclusion_index) - placeholders_contents.append((placeholder, text_to_include)) - return placeholder - - @dataclass class Settings: # noqa: D101 exclude: list[str] | None diff --git a/src/mkdocs_include_markdown_plugin/placeholders.py b/src/mkdocs_include_markdown_plugin/placeholders.py new file mode 100644 index 0000000..4b1e7a1 --- /dev/null +++ b/src/mkdocs_include_markdown_plugin/placeholders.py @@ -0,0 +1,37 @@ +"""Module for placeholders processing.""" + +# Placeholders (taken from Python-Markdown) +from __future__ import annotations + + +STX = '\u0002' +''' "Start of Text" marker for placeholder templates. ''' +ETX = '\u0003' +''' "End of Text" marker for placeholder templates. ''' +INLINE_PLACEHOLDER_PREFIX = f'{STX}klzzwxh:' + + +def build_placeholder(num: int) -> str: + """Return a placeholder.""" + return f'{INLINE_PLACEHOLDER_PREFIX}{num}{ETX}' + + +def escape_placeholders(text: str) -> str: + """Escape placeholders in the given text.""" + return text.replace(STX, f'\\{STX}').replace(ETX, f'\\{ETX}') + + +def unescape_placeholders(text: str) -> str: + """Unescape placeholders in the given text.""" + return text.replace(f'\\{STX}', STX).replace(f'\\{ETX}', ETX) + + +def save_placeholder( + placeholders_contents: list[tuple[str, str]], + text_to_include: str, +) -> str: + """Save the included text and return the placeholder.""" + inclusion_index = len(placeholders_contents) + placeholder = build_placeholder(inclusion_index) + placeholders_contents.append((placeholder, text_to_include)) + return placeholder diff --git a/tests/test_unit/test_include_markdown.py b/tests/test_unit/test_include_markdown.py index 3cd535b..f5a9717 100644 --- a/tests/test_unit/test_include_markdown.py +++ b/tests/test_unit/test_include_markdown.py @@ -2,10 +2,8 @@ import pytest -from mkdocs_include_markdown_plugin.event import ( - build_placeholder, - on_page_markdown, -) +from mkdocs_include_markdown_plugin.event import on_page_markdown +from mkdocs_include_markdown_plugin.placeholders import build_placeholder @pytest.mark.parametrize(