git @ Cat's Eye Technologies Cleandown / master src / marko / html_renderer.py
master

Tree @master (Download .tar.gz)

html_renderer.py @masterraw · history · blame

# Copyright (c) 2019 Frost Ming
#
# SPDX-License-Identifier: LicenseRef-MIT-X-Marko

"""
HTML renderer
"""

from __future__ import annotations

import html
from typing import TYPE_CHECKING, Any, cast
from urllib.parse import quote

from .renderer import Renderer

if TYPE_CHECKING:
    from . import block, inline


class HTMLRenderer(Renderer):
    """The most common renderer for markdown parser"""

    def render_paragraph(self, element: block.Paragraph) -> str:
        children = self.render_children(element)
        if element._tight:  # type: ignore
            return children
        else:
            return f"<p>{children}</p>\n"

    def render_list(self, element: block.List) -> str:
        if element.ordered:
            tag = "ol"
            extra = f' start="{element.start}"' if element.start != 1 else ""
        else:
            tag = "ul"
            extra = ""
        return "<{tag}{extra}>\n{children}</{tag}>\n".format(
            tag=tag, extra=extra, children=self.render_children(element)
        )

    def render_list_item(self, element: block.ListItem) -> str:
        if len(element.children) == 1 and getattr(element.children[0], "_tight", False):  # type: ignore
            sep = ""
        else:
            sep = "\n"
        return f"<li>{sep}{self.render_children(element)}</li>\n"

    def render_quote(self, element: block.Quote) -> str:
        return f"<blockquote>\n{self.render_children(element)}</blockquote>\n"

    def render_fenced_code(self, element: block.FencedCode) -> str:
        lang = (
            f' class="language-{self.escape_html(element.lang)}"'
            if element.lang
            else ""
        )
        return "<pre><code{}>{}</code></pre>\n".format(
            lang, html.escape(element.children[0].children)  # type: ignore
        )

    def render_code_block(self, element: block.CodeBlock) -> str:
        return self.render_fenced_code(cast("block.FencedCode", element))

    def render_html_block(self, element: block.HTMLBlock) -> str:
        return element.body

    def render_thematic_break(self, element: block.ThematicBreak) -> str:
        return "<hr />\n"

    def render_heading(self, element: block.Heading) -> str:
        return "<h{level}>{children}</h{level}>\n".format(
            level=element.level, children=self.render_children(element)
        )

    def render_setext_heading(self, element: block.SetextHeading) -> str:
        return self.render_heading(cast("block.Heading", element))

    def render_blank_line(self, element: block.BlankLine) -> str:
        return ""

    def render_link_ref_def(self, element: block.LinkRefDef) -> str:
        return ""

    def render_emphasis(self, element: inline.Emphasis) -> str:
        return f"<em>{self.render_children(element)}</em>"

    def render_strong_emphasis(self, element: inline.StrongEmphasis) -> str:
        return f"<strong>{self.render_children(element)}</strong>"

    def render_inline_html(self, element: inline.InlineHTML) -> str:
        return cast(str, element.children)

    def render_plain_text(self, element: Any) -> str:
        if isinstance(element.children, str):
            return self.escape_html(element.children)
        return self.render_children(element)

    def render_link(self, element: inline.Link) -> str:
        template = '<a href="{}"{}>{}</a>'
        title = f' title="{self.escape_html(element.title)}"' if element.title else ""
        url = self.escape_url(element.dest)
        body = self.render_children(element)
        return template.format(url, title, body)

    def render_auto_link(self, element: inline.AutoLink) -> str:
        return self.render_link(cast("inline.Link", element))

    def render_image(self, element: inline.Image) -> str:
        template = '<img src="{}" alt="{}"{} />'
        title = f' title="{self.escape_html(element.title)}"' if element.title else ""
        url = self.escape_url(element.dest)
        render_func = self.render
        self.render = self.render_plain_text  # type: ignore
        body = self.render_children(element)
        self.render = render_func  # type: ignore
        return template.format(url, body, title)

    def render_literal(self, element: inline.Literal) -> str:
        return self.render_raw_text(cast("inline.RawText", element))

    def render_raw_text(self, element: inline.RawText) -> str:
        return self.escape_html(element.children)

    def render_line_break(self, element: inline.LineBreak) -> str:
        if element.soft:
            return "\n"
        return "<br />\n"

    def render_code_span(self, element: inline.CodeSpan) -> str:
        return f"<code>{html.escape(cast(str, element.children))}</code>"

    @staticmethod
    def escape_html(raw: str) -> str:
        return html.escape(html.unescape(raw)).replace("&#x27;", "'")

    @staticmethod
    def escape_url(raw: str) -> str:
        """
        Escape urls to prevent code injection craziness. (Hopefully.)
        """
        return html.escape(quote(html.unescape(raw), safe="/#:()*?=%@+,&"))