Source code for mdit_py_plugins.section_ref.index

"""Section reference inline rule.

A single inline scanner is registered on the character ``§``:

- **section_ref** (char ``§``): section-sign references like ``§1.2``.

``§`` is *not* one of markdown-it-py's default text-rule terminator
characters, so the text rule would otherwise swallow it mid-paragraph and
the inline rule would never fire.  ``add_terminator_char("§")`` makes the
text rule interrupt at every ``§``, giving this rule a chance to run.  That
API is only available in markdown-it-py >= 4.1.0.

Requires markdown-it-py >= 4.1.0.
"""

from collections.abc import Sequence
import re
from typing import TYPE_CHECKING

from markdown_it import MarkdownIt
from markdown_it.common.utils import escapeHtml
from markdown_it.rules_inline import StateInline

if TYPE_CHECKING:
    from markdown_it.renderer import RendererProtocol
    from markdown_it.token import Token
    from markdown_it.utils import EnvType, OptionsDict

# ASCII digits only (``[0-9]``, never ``\d`` which also matches unicode digits).
_SECTION_REF_PATTERN = re.compile(r"§([0-9]+(?:\.[0-9]+)*)")


[docs] def section_ref_plugin(md: MarkdownIt) -> None: """Parse section-sign references such as ``§1``, ``§1.1`` and ``§2.3.4``. A reference is a ``§`` immediately followed by ASCII digits, optionally grouped into further ``.``-separated levels (no spaces are allowed). A trailing dot is not consumed, so ``see §1.`` captures ``§1``. A ``§`` directly followed by an ASCII letter (e.g. ``§1a``) is *not* a reference, but a following non-ASCII letter does not block a match, so ``见§3章`` still captures ``§3``. Following CommonMark backslash-escape behaviour, ``\\§1`` stays literal text, while ``\\\\§1`` renders a literal backslash followed by a live reference. Each reference becomes a ``section_ref`` token, with the matched text (e.g. ``"§1.1"``) as its ``content``, ``markup`` set to ``§`` and ``meta["numbers"]`` holding the parsed section number as a list of ints (e.g. ``[1, 1]`` for ``§1.1``), so that downstream renderers can resolve it to a heading target without re-parsing. Leading zeros are normalized (``§01.02`` gives ``[1, 2]``); the original text remains in ``content``. The default rendering is ``<span class="section-ref">§1.1</span>``. Override it with ``md.add_render_rule("section_ref", ...)``. .. versionadded:: 0.7.0 Requires markdown-it-py >= 4.1.0. """ if not hasattr(md.inline, "add_terminator_char"): raise RuntimeError("section_ref_plugin requires markdown-it-py >= 4.1.0") md.inline.add_terminator_char("§") md.inline.ruler.push("section_ref", _section_ref_rule) md.add_render_rule("section_ref", render_section_ref)
def _section_ref_rule(state: StateInline, silent: bool) -> bool: match = _SECTION_REF_PATTERN.match(state.src, state.pos, state.posMax) if not match: return False # reject when directly followed by an ASCII letter (e.g. "§1a", "§1.2b"), # which indicates the text is not a plain section-number reference # (a following non-ASCII letter such as CJK must not block the match) end = match.end() if end < state.posMax and state.src[end].isascii() and state.src[end].isalpha(): return False if not silent: token = state.push("section_ref", "", 0) token.content = match.group(0) token.markup = "§" token.meta = {"numbers": [int(part) for part in match.group(1).split(".")]} state.pos = end return True def render_section_ref( self: "RendererProtocol", tokens: Sequence["Token"], idx: int, options: "OptionsDict", env: "EnvType", ) -> str: token = tokens[idx] return f'<span class="section-ref">{escapeHtml(token.content)}</span>'