diff --git a/mdit_py_plugins/anchors/index.py b/mdit_py_plugins/anchors/index.py index 7fca0d5..681250d 100644 --- a/mdit_py_plugins/anchors/index.py +++ b/mdit_py_plugins/anchors/index.py @@ -32,6 +32,8 @@ def anchors_plugin( Explicit heading IDs (e.g. ``{#id}`` from ``attrs_block_plugin``) are preserved, at any level, and generated IDs avoid collisions with them. Explicit IDs are not de-duplicated, and an empty explicit ID counts as no ID. + A generated empty slug (a title with no slug-able characters, e.g. ``## ???``) + likewise counts as no ID. :param min_level: minimum header level to apply anchors :param max_level: maximum header level to apply anchors @@ -93,7 +95,14 @@ def _anchor_func(state: StateCore) -> None: ) slug = token.attrGet("id") if not slug: - slug = unique_slug(slug_func(title), slugs) + generated = slug_func(title) + if not generated: + # A title with no slug-able characters slugifies to "". + # An empty id="" is invalid HTML, so treat a generated empty + # slug like an empty explicit ID: the heading gets no anchor + # (and no permalink, which would point at "#"). + continue + slug = unique_slug(generated, slugs) token.attrSet("id", slug) if permalink: diff --git a/tests/test_anchors.py b/tests/test_anchors.py index 3e30ab9..aaab38d 100644 --- a/tests/test_anchors.py +++ b/tests/test_anchors.py @@ -115,6 +115,35 @@ def test_explicit_heading_id_plugin_order() -> None: assert md.render("{#custom}\n# Heading") == '