from __future__ import unicode_literals
import re
from builtins import str
from commonmark.common import escape_xml
from commonmark.render.renderer import Renderer
reUnsafeProtocol = re.compile(
r'^javascript:|vbscript:|file:|data:', re.IGNORECASE)
reSafeDataProtocol = re.compile(
r'^data:image\/(?:png|gif|jpeg|webp)', re.IGNORECASE)
def potentially_unsafe(url):
return re.search(reUnsafeProtocol, url) and \
(not re.search(reSafeDataProtocol, url))
class HtmlRenderer(Renderer):
def __init__(self, options={}):
# by default, soft breaks are rendered as newlines in HTML
options['softbreak'] = options.get('softbreak') or '\n'
# set to "
" to make them hard breaks
# set to " " if you want to ignore line wrapping in source
self.disable_tags = 0
self.last_out = '\n'
self.options = options
def escape(self, text):
return escape_xml(text)
def tag(self, name, attrs=None, selfclosing=None):
"""Helper function to produce an HTML tag."""
if self.disable_tags > 0:
return
self.buf += '<' + name
if attrs and len(attrs) > 0:
for attrib in attrs:
self.buf += ' ' + attrib[0] + '="' + attrib[1] + '"'
if selfclosing:
self.buf += ' /'
self.buf += '>'
self.last_out = '>'
# Node methods #
def text(self, node, entering=None):
self.out(node.literal)
def softbreak(self, node=None, entering=None):
self.lit(self.options['softbreak'])
def linebreak(self, node=None, entering=None):
self.tag('br', [], True)
self.cr()
def link(self, node, entering):
attrs = self.attrs(node)
if entering:
if not (self.options.get('safe') and
potentially_unsafe(node.destination)):
attrs.append(['href', self.escape(node.destination)])
if node.title:
attrs.append(['title', self.escape(node.title)])
self.tag('a', attrs)
else:
self.tag('/a')
def image(self, node, entering):
if entering:
if self.disable_tags == 0:
if self.options.get('safe') and \
potentially_unsafe(node.destination):
self.lit('')
def emph(self, node, entering):
self.tag('em' if entering else '/em')
def strong(self, node, entering):
self.tag('strong' if entering else '/strong')
def paragraph(self, node, entering):
grandparent = node.parent.parent
attrs = self.attrs(node)
if grandparent is not None and grandparent.t == 'list':
if grandparent.list_data['tight']:
return
if entering:
self.cr()
self.tag('p', attrs)
else:
self.tag('/p')
self.cr()
def heading(self, node, entering):
tagname = 'h' + str(node.level)
attrs = self.attrs(node)
if entering:
self.cr()
self.tag(tagname, attrs)
else:
self.tag('/' + tagname)
self.cr()
def code(self, node, entering):
self.tag('code')
self.out(node.literal)
self.tag('/code')
def code_block(self, node, entering):
info_words = node.info.split() if node.info else []
attrs = self.attrs(node)
if len(info_words) > 0 and len(info_words[0]) > 0:
attrs.append(['class', 'language-' +
self.escape(info_words[0])])
self.cr()
self.tag('pre')
self.tag('code', attrs)
self.out(node.literal)
self.tag('/code')
self.tag('/pre')
self.cr()
def thematic_break(self, node, entering):
attrs = self.attrs(node)
self.cr()
self.tag('hr', attrs, True)
self.cr()
def block_quote(self, node, entering):
attrs = self.attrs(node)
if entering:
self.cr()
self.tag('blockquote', attrs)
self.cr()
else:
self.cr()
self.tag('/blockquote')
self.cr()
def list(self, node, entering):
tagname = 'ul' if node.list_data['type'] == 'bullet' else 'ol'
attrs = self.attrs(node)
if entering:
start = node.list_data['start']
if start is not None and start != 1:
attrs.append(['start', str(start)])
self.cr()
self.tag(tagname, attrs)
self.cr()
else:
self.cr()
self.tag('/' + tagname)
self.cr()
def item(self, node, entering):
attrs = self.attrs(node)
if entering:
self.tag('li', attrs)
else:
self.tag('/li')
self.cr()
def html_inline(self, node, entering):
if self.options.get('safe'):
self.lit('')
else:
self.lit(node.literal)
def html_block(self, node, entering):
self.cr()
if self.options.get('safe'):
self.lit('')
else:
self.lit(node.literal)
self.cr()
def custom_inline(self, node, entering):
if entering and node.on_enter:
self.lit(node.on_enter)
elif (not entering) and node.on_exit:
self.lit(node.on_exit)
def custom_block(self, node, entering):
self.cr()
if entering and node.on_enter:
self.lit(node.on_enter)
elif (not entering) and node.on_exit:
self.lit(node.on_exit)
self.cr()
# Helper methods #
def out(self, s):
self.lit(self.escape(s))
def attrs(self, node):
att = []
if self.options.get('sourcepos'):
pos = node.sourcepos
if pos:
att.append(['data-sourcepos', str(pos[0][0]) + ':' +
str(pos[0][1]) + '-' + str(pos[1][0]) + ':' +
str(pos[1][1])])
return att