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'
|