Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
renderer.py337 linesDownload Raw Back to markdown_it
1"""2class Renderer3 4Generates HTML from parsed token stream. Each instance has independent5copy of rules. Those can be rewritten with ease. Also, you can add new6rules if you create plugin and adds new token types.7"""8from __future__ import annotations9 10from collections.abc import Sequence11import inspect12from typing import Any, ClassVar, Protocol13 14from .common.utils import escapeHtml, unescapeAll15from .token import Token16from .utils import EnvType, OptionsDict17 18 19class RendererProtocol(Protocol):20    __output__: ClassVar[str]21 22    def render(23        self, tokens: Sequence[Token], options: OptionsDict, env: EnvType24    ) -> Any:25        ...26 27 28class RendererHTML(RendererProtocol):29    """Contains render rules for tokens. Can be updated and extended.30 31    Example:32 33    Each rule is called as independent static function with fixed signature:34 35    ::36 37        class Renderer:38            def token_type_name(self, tokens, idx, options, env) {39                # ...40                return renderedHTML41 42    ::43 44        class CustomRenderer(RendererHTML):45            def strong_open(self, tokens, idx, options, env):46                return '<b>'47            def strong_close(self, tokens, idx, options, env):48                return '</b>'49 50        md = MarkdownIt(renderer_cls=CustomRenderer)51 52        result = md.render(...)53 54    See https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js55    for more details and examples.56    """57 58    __output__ = "html"59 60    def __init__(self, parser: Any = None):61        self.rules = {62            k: v63            for k, v in inspect.getmembers(self, predicate=inspect.ismethod)64            if not (k.startswith("render") or k.startswith("_"))65        }66 67    def render(68        self, tokens: Sequence[Token], options: OptionsDict, env: EnvType69    ) -> str:70        """Takes token stream and generates HTML.71 72        :param tokens: list on block tokens to render73        :param options: params of parser instance74        :param env: additional data from parsed input75 76        """77        result = ""78 79        for i, token in enumerate(tokens):80            if token.type == "inline":81                if token.children:82                    result += self.renderInline(token.children, options, env)83            elif token.type in self.rules:84                result += self.rules[token.type](tokens, i, options, env)85            else:86                result += self.renderToken(tokens, i, options, env)87 88        return result89 90    def renderInline(91        self, tokens: Sequence[Token], options: OptionsDict, env: EnvType92    ) -> str:93        """The same as ``render``, but for single token of `inline` type.94 95        :param tokens: list on block tokens to render96        :param options: params of parser instance97        :param env: additional data from parsed input (references, for example)98        """99        result = ""100 101        for i, token in enumerate(tokens):102            if token.type in self.rules:103                result += self.rules[token.type](tokens, i, options, env)104            else:105                result += self.renderToken(tokens, i, options, env)106 107        return result108 109    def renderToken(110        self,111        tokens: Sequence[Token],112        idx: int,113        options: OptionsDict,114        env: EnvType,115    ) -> str:116        """Default token renderer.117 118        Can be overridden by custom function119 120        :param idx: token index to render121        :param options: params of parser instance122        """123        result = ""124        needLf = False125        token = tokens[idx]126 127        # Tight list paragraphs128        if token.hidden:129            return ""130 131        # Insert a newline between hidden paragraph and subsequent opening132        # block-level tag.133        #134        # For example, here we should insert a newline before blockquote:135        #  - a136        #    >137        #138        if token.block and token.nesting != -1 and idx and tokens[idx - 1].hidden:139            result += "\n"140 141        # Add token name, e.g. `<img`142        result += ("</" if token.nesting == -1 else "<") + token.tag143 144        # Encode attributes, e.g. `<img src="foo"`145        result += self.renderAttrs(token)146 147        # Add a slash for self-closing tags, e.g. `<img src="foo" /`148        if token.nesting == 0 and options["xhtmlOut"]:149            result += " /"150 151        # Check if we need to add a newline after this tag152        if token.block:153            needLf = True154 155            if token.nesting == 1 and (idx + 1 < len(tokens)):156                nextToken = tokens[idx + 1]157 158                if nextToken.type == "inline" or nextToken.hidden:  # noqa: SIM114159                    # Block-level tag containing an inline tag.160                    #161                    needLf = False162 163                elif nextToken.nesting == -1 and nextToken.tag == token.tag:164                    # Opening tag + closing tag of the same type. E.g. `<li></li>`.165                    #166                    needLf = False167 168        result += ">\n" if needLf else ">"169 170        return result171 172    @staticmethod173    def renderAttrs(token: Token) -> str:174        """Render token attributes to string."""175        result = ""176 177        for key, value in token.attrItems():178            result += " " + escapeHtml(key) + '="' + escapeHtml(str(value)) + '"'179 180        return result181 182    def renderInlineAsText(183        self,184        tokens: Sequence[Token] | None,185        options: OptionsDict,186        env: EnvType,187    ) -> str:188        """Special kludge for image `alt` attributes to conform CommonMark spec.189 190        Don't try to use it! Spec requires to show `alt` content with stripped markup,191        instead of simple escaping.192 193        :param tokens: list on block tokens to render194        :param options: params of parser instance195        :param env: additional data from parsed input196        """197        result = ""198 199        for token in tokens or []:200            if token.type == "text":201                result += token.content202            elif token.type == "image":203                if token.children:204                    result += self.renderInlineAsText(token.children, options, env)205            elif token.type == "softbreak":206                result += "\n"207 208        return result209 210    ###################################################211 212    def code_inline(213        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType214    ) -> str:215        token = tokens[idx]216        return (217            "<code"218            + self.renderAttrs(token)219            + ">"220            + escapeHtml(tokens[idx].content)221            + "</code>"222        )223 224    def code_block(225        self,226        tokens: Sequence[Token],227        idx: int,228        options: OptionsDict,229        env: EnvType,230    ) -> str:231        token = tokens[idx]232 233        return (234            "<pre"235            + self.renderAttrs(token)236            + "><code>"237            + escapeHtml(tokens[idx].content)238            + "</code></pre>\n"239        )240 241    def fence(242        self,243        tokens: Sequence[Token],244        idx: int,245        options: OptionsDict,246        env: EnvType,247    ) -> str:248        token = tokens[idx]249        info = unescapeAll(token.info).strip() if token.info else ""250        langName = ""251        langAttrs = ""252 253        if info:254            arr = info.split(maxsplit=1)255            langName = arr[0]256            if len(arr) == 2:257                langAttrs = arr[1]258 259        if options.highlight:260            highlighted = options.highlight(261                token.content, langName, langAttrs262            ) or escapeHtml(token.content)263        else:264            highlighted = escapeHtml(token.content)265 266        if highlighted.startswith("<pre"):267            return highlighted + "\n"268 269        # If language exists, inject class gently, without modifying original token.270        # May be, one day we will add .deepClone() for token and simplify this part, but271        # now we prefer to keep things local.272        if info:273            # Fake token just to render attributes274            tmpToken = Token(type="", tag="", nesting=0, attrs=token.attrs.copy())275            tmpToken.attrJoin("class", options.langPrefix + langName)276 277            return (278                "<pre><code"279                + self.renderAttrs(tmpToken)280                + ">"281                + highlighted282                + "</code></pre>\n"283            )284 285        return (286            "<pre><code"287            + self.renderAttrs(token)288            + ">"289            + highlighted290            + "</code></pre>\n"291        )292 293    def image(294        self,295        tokens: Sequence[Token],296        idx: int,297        options: OptionsDict,298        env: EnvType,299    ) -> str:300        token = tokens[idx]301 302        # "alt" attr MUST be set, even if empty. Because it's mandatory and303        # should be placed on proper position for tests.304        if token.children:305            token.attrSet("alt", self.renderInlineAsText(token.children, options, env))306        else:307            token.attrSet("alt", "")308 309        return self.renderToken(tokens, idx, options, env)310 311    def hardbreak(312        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType313    ) -> str:314        return "<br />\n" if options.xhtmlOut else "<br>\n"315 316    def softbreak(317        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType318    ) -> str:319        return (320            ("<br />\n" if options.xhtmlOut else "<br>\n") if options.breaks else "\n"321        )322 323    def text(324        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType325    ) -> str:326        return escapeHtml(tokens[idx].content)327 328    def html_block(329        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType330    ) -> str:331        return tokens[idx].content332 333    def html_inline(334        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType335    ) -> str:336        return tokens[idx].content337 
codekingpro/portable-devtools · Team Ai