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 "" + text + "" def strong(self, text: str) -> str: return "" + text + "" def link(self, text: str, url: str, title: Optional[str] = None) -> str: s = '" + text + "" def image(self, text: str, url: str, title: Optional[str] = None) -> str: src = self.safe_url(url) alt = escape_text(striptags(text)) s = '' + alt + '" def codespan(self, text: str) -> str: return "" + escape_text(text) + "" def linebreak(self) -> str: return "
\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 "

" + text + "

\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 + "\n" def blank_line(self) -> str: return "" def thematic_break(self) -> str: return "
\n" def block_text(self, text: str) -> str: return text def block_code(self, code: str, info: Optional[str] = None) -> str: html = "
" + escape_text(code) + "
\n" def block_quote(self, text: str) -> str: return "
\n" + text + "
\n" def block_html(self, html: str) -> str: if self._escape: return "

" + escape_text(html.strip()) + "

\n" return html + "\n" def block_error(self, text: str) -> str: return '
' + text + "
\n" def list(self, text: str, ordered: bool, **attrs: Any) -> str: if ordered: html = "\n" + text + "\n" return "\n" def list_item(self, text: str) -> str: return "
  • " + text + "
  • \n"