From 0517d2c5a0481c62bce23a6f15495e0ceec59bb5 Mon Sep 17 00:00:00 2001 From: MohammadYusif Date: Sun, 7 Jun 2026 20:57:11 +0300 Subject: [PATCH] fix: ignore URL fragments in markdown links when parsing tags (#52) get_tags removed wikilinks before tag parsing but left standard markdown inline links intact, so a URL fragment such as '.../features.html#hot-module-replacement' was matched by the tag regex and surfaced as a spurious tag ('hot-'). Strip the link URLs (keeping the link text) before tag extraction, using the existing INLINE_LINK_AFTER_HTML_PROC_REGEX, mirroring how wikilinks are already handled. Genuine tags, including a '#tag' written in a link's display text, are still captured. --- obsidiantools/md_utils.py | 8 ++++++++ tests/general/tags_url-fragment.md | 7 +++++++ tests/test_md_utils.py | 16 ++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 tests/general/tags_url-fragment.md diff --git a/obsidiantools/md_utils.py b/obsidiantools/md_utils.py index 50a6c3a..058932e 100644 --- a/obsidiantools/md_utils.py +++ b/obsidiantools/md_utils.py @@ -244,6 +244,8 @@ def get_tags(filepath: Path, *, show_nested: bool = False) -> list[str]: str_transform_func=_transform_md_file_string_for_tag_parsing) # remove wikilinks so that '#' headers are not caught: src_txt = _remove_wikilinks_from_source_text(src_txt) + # remove md link URLs so that '#' fragments in them are not caught: + src_txt = _remove_md_link_urls_from_source_text(src_txt) tags = _get_tags_from_source_text(src_txt, show_nested=show_nested) return tags @@ -447,6 +449,12 @@ def _remove_wikilinks_from_source_text(src_txt: str) -> str: return re.sub(WIKILINK_REGEX, '', src_txt) +def _remove_md_link_urls_from_source_text(src_txt: str) -> str: + # replace '[text]()' with its 'text' so that any '#' fragment + # in the URL is not mistaken for a tag, while keeping the link text: + return re.sub(INLINE_LINK_AFTER_HTML_PROC_REGEX, r'\1', src_txt) + + def _transform_md_file_string_for_tag_parsing(txt: str) -> str: return txt.replace('\\#', '') diff --git a/tests/general/tags_url-fragment.md b/tests/general/tags_url-fragment.md new file mode 100644 index 0000000..05a741d --- /dev/null +++ b/tests/general/tags_url-fragment.md @@ -0,0 +1,7 @@ +# Tags vs URL fragments + +thanks to [Vite's HMR feature](https://vitejs.dev/guide/features.html#hot-module-replacement). + +A real tag: #python + +Another link [docs](https://example.com/page#section-two) followed by #data text. diff --git a/tests/test_md_utils.py b/tests/test_md_utils.py index 4a3ed1f..f87290b 100644 --- a/tests/test_md_utils.py +++ b/tests/test_md_utils.py @@ -260,6 +260,22 @@ def test_sussudio_tags_with_nesting_shown(): assert actual_tags == expected_tags +def test_url_fragments_in_md_links_not_parsed_as_tags(): + # the '#' fragment of a markdown link URL must not be read as a tag, + # while genuine tags in the note are still captured (issue #52): + actual_tags = get_tags( + Path('.') / 'tests/general/tags_url-fragment.md') + expected_tags = ['python', 'data'] + assert actual_tags == expected_tags + + +def test_url_fragments_in_md_links_not_parsed_as_tags_with_nesting(): + actual_tags = get_tags( + Path('.') / 'tests/general/tags_url-fragment.md', show_nested=True) + expected_tags = ['python', 'data'] + assert actual_tags == expected_tags + + def test_embedded_files_alias_scaling(): actual_embedded_images = get_embedded_files( Path('.') / 'tests/general/embedded-images_in-table.md')