| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- 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,4})"
- r"\[\^(?P<footnote_key>" + LINK_LABEL + r")]:[ \t\n]"
- r"(?P<footnote_text>[^\n]*(?:\n+|$)"
- r"(?:(?P=footnote_lead) {1,4}(?! )[^\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()
- footer_state = BlockState()
- footer_state.process(text)
- block.parse(footer_state)
- children = footer_state.tokens
- 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(parent=state)
- 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()
- if text.endswith("</p>"):
- text = text[:-4] + back + "</p>"
- else:
- text = text + "\n" + back
- 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)
|