diff --git a/CHANGES.md b/CHANGES.md index 157e8341..97a54dfb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ - [pull #701] Allow boolean attribute syntax in `markdown-in-html` extra - [pull #704] Fix XSS from smuggling spans into image attributes (#702, #703) - [pull #710] Add emoji support (#709) +- Fix `header-ids` extra generating duplicate ids when a suffixed id collides with another header (#661) ## python-markdown2 2.5.5 diff --git a/lib/markdown2.py b/lib/markdown2.py index 8dd1c48e..f2177919 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -412,6 +412,8 @@ def _setup_extras(self): if "header-ids" in self.extras: if not hasattr(self, '_count_from_header_id') or self.extras['header-ids'].get('reset-count', False): self._count_from_header_id = defaultdict(int) + if not hasattr(self, '_header_ids_seen') or self.extras['header-ids'].get('reset-count', False): + self._header_ids_seen = set() if "metadata" in self.extras: self.metadata: dict[str, Any] = {} @@ -1582,9 +1584,17 @@ def header_id_from_text(self, if prefix and isinstance(prefix, str): header_id = prefix + '-' + header_id - self._count_from_header_id[header_id] += 1 - if 0 == len(header_id) or self._count_from_header_id[header_id] > 1: - header_id += '-%s' % self._count_from_header_id[header_id] + base_id = header_id + self._count_from_header_id[base_id] += 1 + if 0 == len(base_id) or self._count_from_header_id[base_id] > 1: + header_id = '%s-%s' % (base_id, self._count_from_header_id[base_id]) + # A suffixed id may still collide with a differently-named header + # (e.g. "# Chapter" twice yields "chapter-2", which clashes with + # "# Chapter 2"). Keep bumping until the id is genuinely unique. + while header_id in self._header_ids_seen: + self._count_from_header_id[base_id] += 1 + header_id = '%s-%s' % (base_id, self._count_from_header_id[base_id]) + self._header_ids_seen.add(header_id) return header_id diff --git a/test/tm-cases/header_ids_duplicate.html b/test/tm-cases/header_ids_duplicate.html new file mode 100644 index 00000000..e78f25eb --- /dev/null +++ b/test/tm-cases/header_ids_duplicate.html @@ -0,0 +1,11 @@ +
Test
+ +Test
+ +Test
diff --git a/test/tm-cases/header_ids_duplicate.opts b/test/tm-cases/header_ids_duplicate.opts new file mode 100644 index 00000000..38b8bfe8 --- /dev/null +++ b/test/tm-cases/header_ids_duplicate.opts @@ -0,0 +1 @@ +{"extras": ["header-ids"]} diff --git a/test/tm-cases/header_ids_duplicate.tags b/test/tm-cases/header_ids_duplicate.tags new file mode 100644 index 00000000..cb381121 --- /dev/null +++ b/test/tm-cases/header_ids_duplicate.tags @@ -0,0 +1 @@ +extra header-ids issue661 diff --git a/test/tm-cases/header_ids_duplicate.text b/test/tm-cases/header_ids_duplicate.text new file mode 100644 index 00000000..fc3a5245 --- /dev/null +++ b/test/tm-cases/header_ids_duplicate.text @@ -0,0 +1,11 @@ +# Chapter + +Test + +# Chapter + +Test + +# Chapter 2 + +Test