الملفات
Building-API-and-Monitoring…/venv/lib/python3.12/site-packages/mistune/toc.py
2025-09-27 23:34:33 +03:00

127 أسطر
3.6 KiB
Python

from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple
from .core import BlockState
from .util import striptags
if TYPE_CHECKING:
from .markdown import Markdown
def add_toc_hook(
md: "Markdown",
min_level: int = 1,
max_level: int = 3,
heading_id: Optional[Callable[[Dict[str, Any], int], str]] = None,
) -> None:
"""Add a hook to save toc items into ``state.env``. This is
usually helpful for doc generator::
import mistune
from mistune.toc import add_toc_hook, render_toc_ul
md = mistune.create_markdown(...)
add_toc_hook(md)
html, state = md.parse(text)
toc_items = state.env['toc_items']
toc_html = render_toc_ul(toc_items)
:param md: Markdown instance
:param min_level: min heading level
:param max_level: max heading level
:param heading_id: a function to generate heading_id
"""
if heading_id is None:
def heading_id(token: Dict[str, Any], index: int) -> str:
return "toc_" + str(index + 1)
def toc_hook(md: "Markdown", state: "BlockState") -> None:
headings = []
for tok in state.tokens:
if tok["type"] == "heading":
level = tok["attrs"]["level"]
if min_level <= level <= max_level:
headings.append(tok)
toc_items = []
for i, tok in enumerate(headings):
tok["attrs"]["id"] = heading_id(tok, i)
toc_items.append(normalize_toc_item(md, tok))
# save items into state
state.env["toc_items"] = toc_items
md.before_render_hooks.append(toc_hook)
def normalize_toc_item(md: "Markdown", token: Dict[str, Any]) -> Tuple[int, str, str]:
text = token["text"]
tokens = md.inline(text, {})
assert md.renderer is not None
html = md.renderer(tokens, BlockState())
text = striptags(html)
attrs = token["attrs"]
return attrs["level"], attrs["id"], text
def render_toc_ul(toc: Iterable[Tuple[int, str, str]]) -> str:
"""Render a <ul> table of content HTML. The param "toc" should
be formatted into this structure::
[
(level, id, text),
]
For example::
[
(1, 'toc-intro', 'Introduction'),
(2, 'toc-install', 'Install'),
(2, 'toc-upgrade', 'Upgrade'),
(1, 'toc-license', 'License'),
]
"""
if not toc:
return ""
s = ""
levels: List[int] = []
for level, k, text in toc:
item = '<a href="#{}">{}</a>'.format(k, text)
if not levels:
s += "<li>" + item
levels.append(level)
elif level == levels[-1]:
s += "</li>\n<li>" + item
elif level > levels[-1]:
s += "\n<ul>\n<li>" + item
levels.append(level)
else:
levels.pop()
while levels:
last_level = levels.pop()
if level == last_level:
s += "</li>\n</ul>\n</li>\n<li>" + item
levels.append(level)
break
elif level > last_level:
s += "</li>\n<li>" + item
levels.append(last_level)
levels.append(level)
break
else:
s += "</li>\n</ul>\n"
else:
levels.append(level)
s += "</li>\n<li>" + item
while len(levels) > 1:
s += "</li>\n</ul>\n"
levels.pop()
if not s:
return ""
return "<ul>\n" + s + "</li>\n</ul>\n"