summaryrefslogtreecommitdiff
path: root/src/mistune/plugins/abbr.py
blob: 1b45790aaa4745707ef71f014cb81231615f09f4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import re
import types
from ..util import escape
from ..helpers import PREVENT_BACKSLASH

__all__ = ['abbr']

# https://michelf.ca/projects/php-markdown/extra/#abbr
REF_ABBR = (
  r'^ {0,3}\*\[(?P<abbr_key>[^\]]+)'+ PREVENT_BACKSLASH + r'\]:'
  r'(?P<abbr_text>(?:[ \t]*\n(?: {3,}|\t)[^\n]+)|(?:[^\n]*))$'
)


def parse_ref_abbr(block, m, state):
    ref = state.env.get('ref_abbrs')
    if not ref:
        ref = {}
    key = m.group('abbr_key')
    text = m.group('abbr_text')
    ref[key] = text.strip()
    state.env['ref_abbrs'] = ref
    # abbr definition can split paragraph
    state.append_token({'type': 'blank_line'})
    return m.end() + 1


def process_text(inline, text, state):
    ref = state.env.get('ref_abbrs')
    if not ref:
        return state.append_token({'type': 'text', 'raw': text})

    if state.tokens:
        last = state.tokens[-1]
        if last['type'] == 'text':
            state.tokens.pop()
            text = last['raw'] + text

    abbrs_re = state.env.get('abbrs_re')
    if not abbrs_re:
        abbrs_re = re.compile(r'|'.join(re.escape(k) for k in ref.keys()))
        state.env['abbrs_re'] = abbrs_re

    pos = 0
    while pos < len(text):
        m = abbrs_re.search(text, pos)
        if not m:
            break

        end_pos = m.start()
        if end_pos > pos:
            hole = text[pos:end_pos]
            state.append_token({'type': 'text', 'raw': hole})

        label = m.group(0)
        state.append_token({
            'type': 'abbr',
            'children': [{'type': 'text', 'raw': label}],
            'attrs': {'title': ref[label]}
        })
        pos = m.end()

    if pos == 0:
        # special case, just pure text
        state.append_token({'type': 'text', 'raw': text})
    elif pos < len(text):
        state.append_token({'type': 'text', 'raw': text[pos:]})


def render_abbr(renderer, text, title):
    if not title:
        return '<abbr>' + text + '</abbr>'
    return '<abbr title="' + escape(title) + '">' + text + '</abbr>'


def abbr(md):
    """A mistune plugin to support abbreviations, spec defined at
    https://michelf.ca/projects/php-markdown/extra/#abbr

    Here is an example:

    .. code-block:: text

        The HTML specification
        is maintained by the W3C.

        *[HTML]: Hyper Text Markup Language
        *[W3C]:  World Wide Web Consortium

    It will be converted into HTML:

    .. code-block:: html

        The <abbr title="Hyper Text Markup Language">HTML</abbr> specification
        is maintained by the <abbr title="World Wide Web Consortium">W3C</abbr>.

    :param md: Markdown instance
    """
    md.block.register('ref_abbr', REF_ABBR, parse_ref_abbr, before='paragraph')
    # replace process_text
    md.inline.process_text = types.MethodType(process_text, md.inline)
    if md.renderer and md.renderer.NAME == 'html':
        md.renderer.register('abbr', render_abbr)