summaryrefslogtreecommitdiff
path: root/src/mistune/renderers/html.py
blob: c458a4ab5425b57b42e2cfa98e316d9849a76e82 (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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from ..core import BaseRenderer
from ..util import escape as escape_text, striptags, safe_entity


class HTMLRenderer(BaseRenderer):
    """A renderer for converting Markdown to HTML."""
    NAME = 'html'
    HARMFUL_PROTOCOLS = (
        'javascript:',
        'vbscript:',
        'file:',
        'data:',
    )
    GOOD_DATA_PROTOCOLS = (
        'data:image/gif;',
        'data:image/png;',
        'data:image/jpeg;',
        'data:image/webp;',
    )

    def __init__(self, escape=True, allow_harmful_protocols=None):
        super(HTMLRenderer, self).__init__()
        self._allow_harmful_protocols = allow_harmful_protocols
        self._escape = escape

    def render_token(self, token, state):
        # backward compitable with v2
        func = self._get_method(token['type'])
        attrs = token.get('attrs')

        if 'raw' in token:
            text = token['raw']
        elif 'children' in token:
            text = self.render_tokens(token['children'], state)
        else:
            if attrs:
                return func(**attrs)
            else:
                return func()
        if attrs:
            return func(text, **attrs)
        else:
            return func(text)

    def safe_url(self, url: str) -> str:
        """Ensure the given URL is safe. This method is used for rendering
        links, images, and etc.
        """
        if self._allow_harmful_protocols is True:
            return url

        _url = url.lower()
        if self._allow_harmful_protocols and \
            _url.startswith(tuple(self._allow_harmful_protocols)):
            return url

        if _url.startswith(self.HARMFUL_PROTOCOLS) and \
            not _url.startswith(self.GOOD_DATA_PROTOCOLS):
            return '#harmful-link'
        return url

    def text(self, text: str) -> str:
        if self._escape:
            return escape_text(text)
        return safe_entity(text)

    def emphasis(self, text: str) -> str:
        return '<em>' + text + '</em>'

    def strong(self, text: str) -> str:
        return '<strong>' + text + '</strong>'

    def link(self, text: str, url: str, title=None) -> str:
        s = '<a href="' + self.safe_url(url) + '"'
        if title:
            s += ' title="' + safe_entity(title) + '"'
        return s + '>' + text + '</a>'

    def image(self, text: str, url: str, title=None) -> str:
        src = self.safe_url(url)
        alt = escape_text(striptags(text))
        s = '<img src="' + src + '" alt="' + alt + '"'
        if title:
            s += ' title="' + safe_entity(title) + '"'
        return s + ' />'

    def codespan(self, text: str) -> str:
        return '<code>' + text + '</code>'

    def linebreak(self) -> str:
        return '<br />\n'

    def softbreak(self) -> str:
        return '\n'

    def inline_html(self, html: str) -> str:
        if self._escape:
            return escape_text(html)
        return html

    def paragraph(self, text: str) -> str:
        return '<p>' + text + '</p>\n'

    def heading(self, text: str, level: int, **attrs) -> str:
        tag = 'h' + str(level)
        html = '<' + tag
        _id = attrs.get('id')
        if _id:
            html += ' id="' + _id + '"'
        return html + '>' + text + '</' + tag + '>\n'

    def blank_line(self) -> str:
        return ''

    def thematic_break(self) -> str:
        return '<hr />\n'

    def block_text(self, text: str) -> str:
        return text

    def block_code(self, code: str, info=None) -> str:
        html = '<pre><code'
        if info is not None:
            info = safe_entity(info.strip())
        if info:
            lang = info.split(None, 1)[0]
            html += ' class="language-' + lang + '"'
        return html + '>' + escape_text(code) + '</code></pre>\n'

    def block_quote(self, text: str) -> str:
        return '<blockquote>\n' + text + '</blockquote>\n'

    def block_html(self, html: str) -> str:
        if self._escape:
            return '<p>' + escape_text(html) + '</p>\n'
        return html + '\n'

    def block_error(self, text: str) -> str:
        return '<div class="error"><pre>' + text + '</pre></div>\n'

    def list(self, text: str, ordered: bool, **attrs) -> str:
        if ordered:
            html = '<ol'
            start = attrs.get('start')
            if start is not None:
                html += ' start="' + str(start) + '"'
            return html + '>\n' + text + '</ol>\n'
        return '<ul>\n' + text + '</ul>\n'

    def list_item(self, text: str) -> str:
        return '<li>' + text + '</li>\n'