You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			155 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			155 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
import re
 | 
						|
from typing import TYPE_CHECKING, Any, Dict, List, Match, Union
 | 
						|
 | 
						|
from ..core import BlockState
 | 
						|
from ..helpers import LINK_LABEL
 | 
						|
from ..util import unikey
 | 
						|
 | 
						|
if TYPE_CHECKING:
 | 
						|
    from ..block_parser import BlockParser
 | 
						|
    from ..core import BaseRenderer, InlineState
 | 
						|
    from ..inline_parser import InlineParser
 | 
						|
    from ..markdown import Markdown
 | 
						|
 | 
						|
__all__ = ["footnotes"]
 | 
						|
 | 
						|
_PARAGRAPH_SPLIT = re.compile(r"\n{2,}")
 | 
						|
# https://michelf.ca/projects/php-markdown/extra/#footnotes
 | 
						|
REF_FOOTNOTE = (
 | 
						|
    r"^(?P<footnote_lead> {0,3})"
 | 
						|
    r"\[\^(?P<footnote_key>" + LINK_LABEL + r")]:[ \t]"
 | 
						|
    r"(?P<footnote_text>[^\n]*(?:\n+|$)"
 | 
						|
    r"(?:(?P=footnote_lead) {1,3}(?! )[^\n]*\n+)*"
 | 
						|
    r")"
 | 
						|
)
 | 
						|
 | 
						|
INLINE_FOOTNOTE = r"\[\^(?P<footnote_key>" + LINK_LABEL + r")\]"
 | 
						|
 | 
						|
 | 
						|
def parse_inline_footnote(inline: "InlineParser", m: Match[str], state: "InlineState") -> int:
 | 
						|
    key = unikey(m.group("footnote_key"))
 | 
						|
    ref = state.env.get("ref_footnotes")
 | 
						|
    if ref and key in ref:
 | 
						|
        notes = state.env.get("footnotes")
 | 
						|
        if not notes:
 | 
						|
            notes = []
 | 
						|
        if key not in notes:
 | 
						|
            notes.append(key)
 | 
						|
            state.env["footnotes"] = notes
 | 
						|
        state.append_token({"type": "footnote_ref", "raw": key, "attrs": {"index": notes.index(key) + 1}})
 | 
						|
    else:
 | 
						|
        state.append_token({"type": "text", "raw": m.group(0)})
 | 
						|
    return m.end()
 | 
						|
 | 
						|
 | 
						|
def parse_ref_footnote(block: "BlockParser", m: Match[str], state: BlockState) -> int:
 | 
						|
    ref = state.env.get("ref_footnotes")
 | 
						|
    if not ref:
 | 
						|
        ref = {}
 | 
						|
 | 
						|
    key = unikey(m.group("footnote_key"))
 | 
						|
    if key not in ref:
 | 
						|
        ref[key] = m.group("footnote_text")
 | 
						|
        state.env["ref_footnotes"] = ref
 | 
						|
    return m.end()
 | 
						|
 | 
						|
 | 
						|
def parse_footnote_item(block: "BlockParser", key: str, index: int, state: BlockState) -> Dict[str, Any]:
 | 
						|
    ref = state.env.get("ref_footnotes")
 | 
						|
    if not ref:
 | 
						|
        raise ValueError("Missing 'ref_footnotes'.")
 | 
						|
    text = ref[key]
 | 
						|
 | 
						|
    lines = text.splitlines()
 | 
						|
    second_line = None
 | 
						|
    for second_line in lines[1:]:
 | 
						|
        if second_line:
 | 
						|
            break
 | 
						|
 | 
						|
    if second_line:
 | 
						|
        spaces = len(second_line) - len(second_line.lstrip())
 | 
						|
        pattern = re.compile(r"^ {" + str(spaces) + r",}", flags=re.M)
 | 
						|
        text = pattern.sub("", text).strip()
 | 
						|
        items = _PARAGRAPH_SPLIT.split(text)
 | 
						|
        children = [{"type": "paragraph", "text": s} for s in items]
 | 
						|
    else:
 | 
						|
        text = text.strip()
 | 
						|
        children = [{"type": "paragraph", "text": text}]
 | 
						|
    return {"type": "footnote_item", "children": children, "attrs": {"key": key, "index": index}}
 | 
						|
 | 
						|
 | 
						|
def md_footnotes_hook(
 | 
						|
    md: "Markdown", result: Union[str, List[Dict[str, Any]]], state: BlockState
 | 
						|
) -> Union[str, List[Dict[str, Any]]]:
 | 
						|
    notes = state.env.get("footnotes")
 | 
						|
    if not notes:
 | 
						|
        return result
 | 
						|
 | 
						|
    children = [parse_footnote_item(md.block, k, i + 1, state) for i, k in enumerate(notes)]
 | 
						|
    state = BlockState()
 | 
						|
    state.tokens = [{"type": "footnotes", "children": children}]
 | 
						|
    output = md.render_state(state)
 | 
						|
    return result + output  # type: ignore[operator]
 | 
						|
 | 
						|
 | 
						|
def render_footnote_ref(renderer: "BaseRenderer", key: str, index: int) -> str:
 | 
						|
    i = str(index)
 | 
						|
    html = '<sup class="footnote-ref" id="fnref-' + i + '">'
 | 
						|
    return html + '<a href="#fn-' + i + '">' + i + "</a></sup>"
 | 
						|
 | 
						|
 | 
						|
def render_footnotes(renderer: "BaseRenderer", text: str) -> str:
 | 
						|
    return '<section class="footnotes">\n<ol>\n' + text + "</ol>\n</section>\n"
 | 
						|
 | 
						|
 | 
						|
def render_footnote_item(renderer: "BaseRenderer", text: str, key: str, index: int) -> str:
 | 
						|
    i = str(index)
 | 
						|
    back = '<a href="#fnref-' + i + '" class="footnote">↩</a>'
 | 
						|
    text = text.rstrip()[:-4] + back + "</p>"
 | 
						|
    return '<li id="fn-' + i + '">' + text + "</li>\n"
 | 
						|
 | 
						|
 | 
						|
def footnotes(md: "Markdown") -> None:
 | 
						|
    """A mistune plugin to support footnotes, spec defined at
 | 
						|
    https://michelf.ca/projects/php-markdown/extra/#footnotes
 | 
						|
 | 
						|
    Here is an example:
 | 
						|
 | 
						|
    .. code-block:: text
 | 
						|
 | 
						|
        That's some text with a footnote.[^1]
 | 
						|
 | 
						|
        [^1]: And that's the footnote.
 | 
						|
 | 
						|
    It will be converted into HTML:
 | 
						|
 | 
						|
    .. code-block:: html
 | 
						|
 | 
						|
        <p>That's some text with a footnote.<sup class="footnote-ref" id="fnref-1"><a href="#fn-1">1</a></sup></p>
 | 
						|
        <section class="footnotes">
 | 
						|
        <ol>
 | 
						|
        <li id="fn-1"><p>And that's the footnote.<a href="#fnref-1" class="footnote">↩</a></p></li>
 | 
						|
        </ol>
 | 
						|
        </section>
 | 
						|
 | 
						|
    :param md: Markdown instance
 | 
						|
    """
 | 
						|
    md.inline.register(
 | 
						|
        "footnote",
 | 
						|
        INLINE_FOOTNOTE,
 | 
						|
        parse_inline_footnote,
 | 
						|
        before="link",
 | 
						|
    )
 | 
						|
    md.block.register(
 | 
						|
        "ref_footnote",
 | 
						|
        REF_FOOTNOTE,
 | 
						|
        parse_ref_footnote,
 | 
						|
        before="ref_link",
 | 
						|
    )
 | 
						|
    md.after_render_hooks.append(md_footnotes_hook)
 | 
						|
 | 
						|
    if md.renderer and md.renderer.NAME == "html":
 | 
						|
        md.renderer.register("footnote_ref", render_footnote_ref)
 | 
						|
        md.renderer.register("footnote_item", render_footnote_item)
 | 
						|
        md.renderer.register("footnotes", render_footnotes)
 |