You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

154 lines
4.8 KiB
Python

from typing import Any, ClassVar, Dict, Optional, Tuple, Literal
from ..core import BaseRenderer, BlockState
from ..util import escape as escape_text
from ..util import safe_entity, striptags
class HTMLRenderer(BaseRenderer):
"""A renderer for converting Markdown to HTML."""
_escape: bool
NAME: ClassVar[Literal["html"]] = "html"
HARMFUL_PROTOCOLS: ClassVar[Tuple[str, ...]] = (
"javascript:",
"vbscript:",
"file:",
"data:",
)
GOOD_DATA_PROTOCOLS: ClassVar[Tuple[str, ...]] = (
"data:image/gif;",
"data:image/png;",
"data:image/jpeg;",
"data:image/webp;",
)
def __init__(self, escape: bool = True, allow_harmful_protocols: Optional[bool] = None) -> None:
super(HTMLRenderer, self).__init__()
self._allow_harmful_protocols = allow_harmful_protocols
self._escape = escape
def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
# 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 escape_text(url)
_url = url.lower()
if self._allow_harmful_protocols and _url.startswith(tuple(self._allow_harmful_protocols)):
return escape_text(url)
if _url.startswith(self.HARMFUL_PROTOCOLS) and not _url.startswith(self.GOOD_DATA_PROTOCOLS):
return "#harmful-link"
return escape_text(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: Optional[str] = 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: Optional[str] = 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>" + escape_text(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: Any) -> 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: Optional[str] = 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.strip()) + "</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: Any) -> 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"