Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
renderer.py357 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"""8 9from __future__ import annotations10 11from collections.abc import Sequence12import inspect13from typing import Any, ClassVar, Protocol14 15from .common.utils import escapeHtml, unescapeAll16from .token import Token17from .utils import EnvType, OptionsDict18 19 20class RendererProtocol(Protocol):21    __output__: ClassVar[str]22 23    def render(24        self, tokens: Sequence[Token], options: OptionsDict, env: EnvType25    ) -> Any: ...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:159                    # 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 list_item_open(213        self,214        tokens: Sequence[Token],215        idx: int,216        options: OptionsDict,217        env: EnvType,218    ) -> str:219        token = tokens[idx]220        result = self.renderToken(tokens, idx, options, env)221        if token.meta and "checked" in token.meta:222            checked_attr = ' checked=""' if token.meta["checked"] else ""223            disabled_attr = (224                "" if options.get("tasklists_editable", False) else ' disabled=""'225            )226            result += (227                '<input class="task-list-item-checkbox"'228                f'{disabled_attr} type="checkbox"{checked_attr}> '229            )230        return result231 232    def code_inline(233        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType234    ) -> str:235        token = tokens[idx]236        return (237            "<code"238            + self.renderAttrs(token)239            + ">"240            + escapeHtml(tokens[idx].content)241            + "</code>"242        )243 244    def code_block(245        self,246        tokens: Sequence[Token],247        idx: int,248        options: OptionsDict,249        env: EnvType,250    ) -> str:251        token = tokens[idx]252 253        return (254            "<pre"255            + self.renderAttrs(token)256            + "><code>"257            + escapeHtml(tokens[idx].content)258            + "</code></pre>\n"259        )260 261    def fence(262        self,263        tokens: Sequence[Token],264        idx: int,265        options: OptionsDict,266        env: EnvType,267    ) -> str:268        token = tokens[idx]269        info = unescapeAll(token.info).strip() if token.info else ""270        langName = ""271        langAttrs = ""272 273        if info:274            arr = info.split(maxsplit=1)275            langName = arr[0]276            if len(arr) == 2:277                langAttrs = arr[1]278 279        if options.highlight:280            highlighted = options.highlight(281                token.content, langName, langAttrs282            ) or escapeHtml(token.content)283        else:284            highlighted = escapeHtml(token.content)285 286        if highlighted.startswith("<pre"):287            return highlighted + "\n"288 289        # If language exists, inject class gently, without modifying original token.290        # May be, one day we will add .deepClone() for token and simplify this part, but291        # now we prefer to keep things local.292        if info:293            # Fake token just to render attributes294            tmpToken = Token(type="", tag="", nesting=0, attrs=token.attrs.copy())295            tmpToken.attrJoin("class", options.langPrefix + langName)296 297            return (298                "<pre><code"299                + self.renderAttrs(tmpToken)300                + ">"301                + highlighted302                + "</code></pre>\n"303            )304 305        return (306            "<pre><code"307            + self.renderAttrs(token)308            + ">"309            + highlighted310            + "</code></pre>\n"311        )312 313    def image(314        self,315        tokens: Sequence[Token],316        idx: int,317        options: OptionsDict,318        env: EnvType,319    ) -> str:320        token = tokens[idx]321 322        # "alt" attr MUST be set, even if empty. Because it's mandatory and323        # should be placed on proper position for tests.324        if token.children:325            token.attrSet("alt", self.renderInlineAsText(token.children, options, env))326        else:327            token.attrSet("alt", "")328 329        return self.renderToken(tokens, idx, options, env)330 331    def hardbreak(332        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType333    ) -> str:334        return "<br />\n" if options.xhtmlOut else "<br>\n"335 336    def softbreak(337        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType338    ) -> str:339        return (340            ("<br />\n" if options.xhtmlOut else "<br>\n") if options.breaks else "\n"341        )342 343    def text(344        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType345    ) -> str:346        return escapeHtml(tokens[idx].content)347 348    def html_block(349        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType350    ) -> str:351        return tokens[idx].content352 353    def html_inline(354        self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType355    ) -> str:356        return tokens[idx].content357 
codekingpro/portable-devtools · Team Ai