Skip to content

Commit b0cc58f

Browse files
Merge branch 'master' into fix/breaks-extra-typeerror-with-break-on-newline
2 parents 7ed9437 + dac7137 commit b0cc58f

7 files changed

Lines changed: 83 additions & 4 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [pull #701] Allow boolean attribute syntax in `markdown-in-html` extra
1010
- [pull #704] Fix XSS from smuggling spans into image attributes (#702, #703)
1111
- [pull #710] Add emoji support (#709)
12+
- [pull #713] Fix `header-ids` extra generating duplicate ids when a suffixed id collides with another header (#661)
1213

1314

1415
## python-markdown2 2.5.5

lib/markdown2.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,8 @@ def _setup_extras(self):
419419
if "header-ids" in self.extras:
420420
if not hasattr(self, '_count_from_header_id') or self.extras['header-ids'].get('reset-count', False):
421421
self._count_from_header_id = defaultdict(int)
422+
if not hasattr(self, '_header_ids_seen') or self.extras['header-ids'].get('reset-count', False):
423+
self._header_ids_seen = set()
422424
if "metadata" in self.extras:
423425
self.metadata: dict[str, Any] = {}
424426

@@ -638,12 +640,21 @@ def preprocess(self, text: str) -> str:
638640
def _extract_metadata(self, text: str) -> str:
639641
if text.startswith("---"):
640642
fence_splits = re.split(self._meta_data_fence_pattern, text, maxsplit=2)
643+
if len(fence_splits) < 3:
644+
# A leading '---' with no closing fence is a horizontal rule (or
645+
# unterminated front matter), not metadata. re.split returns
646+
# fewer than three elements in that case, so leave text as-is.
647+
return text
641648
metadata_content = fence_splits[1]
642649
tail = fence_splits[2]
643650
else:
644651
metadata_split = re.split(self._meta_data_newline, text, maxsplit=1)
645652
metadata_content = metadata_split[0]
646-
tail = metadata_split[1]
653+
# There is no blank line to split on when the whole document is a
654+
# single block (e.g. a tab-indented code block with no trailing
655+
# blank line), so re.split returns a single element and there is
656+
# no document body after the metadata.
657+
tail = metadata_split[1] if len(metadata_split) > 1 else ""
647658

648659
# _meta_data_pattern only has one capturing group, so we can assume
649660
# the returned type to be list[str]
@@ -1589,9 +1600,17 @@ def header_id_from_text(self,
15891600
if prefix and isinstance(prefix, str):
15901601
header_id = prefix + '-' + header_id
15911602

1592-
self._count_from_header_id[header_id] += 1
1593-
if 0 == len(header_id) or self._count_from_header_id[header_id] > 1:
1594-
header_id += '-%s' % self._count_from_header_id[header_id]
1603+
base_id = header_id
1604+
self._count_from_header_id[base_id] += 1
1605+
if 0 == len(base_id) or self._count_from_header_id[base_id] > 1:
1606+
header_id = '%s-%s' % (base_id, self._count_from_header_id[base_id])
1607+
# A suffixed id may still collide with a differently-named header
1608+
# (e.g. "# Chapter" twice yields "chapter-2", which clashes with
1609+
# "# Chapter 2"). Keep bumping until the id is genuinely unique.
1610+
while header_id in self._header_ids_seen:
1611+
self._count_from_header_id[base_id] += 1
1612+
header_id = '%s-%s' % (base_id, self._count_from_header_id[base_id])
1613+
self._header_ids_seen.add(header_id)
15951614

15961615
return header_id
15971616

test/test_markdown2.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,41 @@ def test_breaks_and_break_on_newline_together(self):
295295
'break-on-newline': None}),
296296
'<p>a<br />\nb</p>\n')
297297
test_breaks_and_break_on_newline_together.tags = ["breaks", "extras"]
298+
299+
def test_metadata_no_blank_line(self):
300+
# A single-block document with no blank line (e.g. a tab-indented code
301+
# block) gives the metadata extra nothing to split on, so re.split
302+
# returns a single element. This used to raise IndexError; it should
303+
# render normally with no metadata extracted.
304+
result = markdown2.markdown('\tsome indented code', extras=['metadata'])
305+
self.assertEqual(result, '<pre><code>some indented code\n</code></pre>\n')
306+
self.assertEqual(result.metadata, {})
307+
test_metadata_no_blank_line.tags = ["metadata", "issue"]
308+
309+
def test_metadata_leading_hr_no_closing_fence(self):
310+
# A document that opens with '---' (a horizontal rule) but has no
311+
# closing '---' fence is not front matter. This used to raise
312+
# IndexError; it should render the '---' as an <hr>.
313+
result = markdown2.markdown('---\n# My Document\n', extras=['metadata'])
314+
self.assertEqual(result, '<hr />\n\n<h1>My Document</h1>\n')
315+
self.assertEqual(result.metadata, {})
316+
test_metadata_leading_hr_no_closing_fence.tags = ["metadata", "issue"]
317+
318+
def test_metadata_fenced_front_matter_still_parsed(self):
319+
# A complete '---' fenced front-matter block is still extracted.
320+
result = markdown2.markdown('---\ntitle: Hi\n---\n# Body\n',
321+
extras=['metadata'])
322+
self.assertEqual(result.metadata, {'title': 'Hi'})
323+
self.assertEqual(result, '<h1>Body</h1>\n')
324+
test_metadata_fenced_front_matter_still_parsed.tags = ["metadata"]
325+
326+
def test_metadata_still_parsed_without_fence(self):
327+
# The fix must not change how leading key: value metadata is parsed.
328+
result = markdown2.markdown('title: Hello\nauthor: Me\n\n# Body\n',
329+
extras=['metadata'])
330+
self.assertEqual(result.metadata, {'title': 'Hello', 'author': 'Me'})
331+
self.assertEqual(result, '<h1>Body</h1>\n')
332+
test_metadata_still_parsed_without_fence.tags = ["metadata"]
298333

299334
def test_toc_with_persistent_object(self):
300335
"""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<h1 id="chapter">Chapter</h1>
2+
3+
<p>Test</p>
4+
5+
<h1 id="chapter-2">Chapter</h1>
6+
7+
<p>Test</p>
8+
9+
<h1 id="chapter-2-2">Chapter 2</h1>
10+
11+
<p>Test</p>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"extras": ["header-ids"]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
extra header-ids issue661
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Chapter
2+
3+
Test
4+
5+
# Chapter
6+
7+
Test
8+
9+
# Chapter 2
10+
11+
Test

0 commit comments

Comments
 (0)