1+ import re
12from typing import TYPE_CHECKING , Any , Callable , Dict , Iterable , List , Optional , Tuple
23
34from .core import BlockState
67if TYPE_CHECKING :
78 from .markdown import Markdown
89
10+ _HTML_ID_RE = re .compile (r"""\bid\s*=\s*(?:"([^"]*)"|'([^']*)')""" , re .I )
11+
912
1013def add_toc_hook (
1114 md : "Markdown" ,
@@ -32,12 +35,17 @@ def add_toc_hook(
3235 :param heading_id: a function to generate heading_id
3336 """
3437 if heading_id is None :
38+ auto_heading_id = True
3539
3640 def heading_id (token : Dict [str , Any ], index : int ) -> str :
3741 return "toc_" + str (index + 1 )
3842
43+ else :
44+ auto_heading_id = False
45+
3946 def toc_hook (md : "Markdown" , state : "BlockState" ) -> None :
4047 headings = []
48+ used_ids = _find_html_ids (state .src )
4149
4250 for tok in state .tokens :
4351 if tok ["type" ] == "heading" :
@@ -47,7 +55,11 @@ def toc_hook(md: "Markdown", state: "BlockState") -> None:
4755
4856 toc_items = []
4957 for i , tok in enumerate (headings ):
50- tok ["attrs" ]["id" ] = heading_id (tok , i )
58+ _id = heading_id (tok , i )
59+ if auto_heading_id :
60+ _id = _unique_id (_id , used_ids )
61+ used_ids .add (_id )
62+ tok ["attrs" ]["id" ] = _id
5163 toc_items .append (normalize_toc_item (md , tok , parent = state ))
5264
5365 # save items into state
@@ -56,6 +68,22 @@ def toc_hook(md: "Markdown", state: "BlockState") -> None:
5668 md .before_render_hooks .append (toc_hook )
5769
5870
71+ def _find_html_ids (src : str ) -> set :
72+ return {m .group (1 ) or m .group (2 ) for m in _HTML_ID_RE .finditer (src )}
73+
74+
75+ def _unique_id (value : str , used_ids : set ) -> str :
76+ if value not in used_ids :
77+ return value
78+
79+ i = 1
80+ while True :
81+ new_value = value + "_" + str (i )
82+ if new_value not in used_ids :
83+ return new_value
84+ i += 1
85+
86+
5987def normalize_toc_item (md : "Markdown" , token : Dict [str , Any ], parent : Optional [Any ] = None ) -> Tuple [int , str , str ]:
6088 text = token ["text" ]
6189 tokens = md .inline (text , parent .env if parent else {})
0 commit comments