@@ -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
0 commit comments