import re from ..core import BlockState from ..util import unikey from ..helpers import LINK_LABEL __all__ = ['footnotes'] _PARAGRAPH_SPLIT = re.compile(r'\n{2,}') # https://michelf.ca/projects/php-markdown/extra/#footnotes REF_FOOTNOTE = ( r'^(?P {0,3})' r'\[\^(?P' + LINK_LABEL + r')]:[ \t]' r'(?P[^\n]*(?:\n+|$)' r'(?:(?P=footnote_lead) {1,3}(?! )[^\n]*\n+)*' r')' ) INLINE_FOOTNOTE = r'\[\^(?P' + LINK_LABEL + r')\]' def parse_inline_footnote(inline, m: re.Match, state): 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, m: re.Match, state: BlockState): 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, key: str, index: int, state: BlockState): ref = state.env.get('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, result: str, state: BlockState): 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 def render_footnote_ref(renderer, key: str, index: int): i = str(index) html = '' return html + '' + i + '' def render_footnotes(renderer, text: str): return '
\n
    \n' + text + '
\n
\n' def render_footnote_item(renderer, text: str, key: str, index: int): i = str(index) back = '' text = text.rstrip()[:-4] + back + '

' return '
  • ' + text + '
  • \n' def footnotes(md): """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

    That's some text with a footnote.1

    1. And that's the footnote.

    :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)