Home Blog CV Projects Patterns Notes Book Colophon Search

Tags

31 Oct, 2024

Here's some code for rendering HTML in Python fast, and correctly:

from html import escape

class SafelyEscapedHTML(str):
    pass

def _dynamic_escape(s):
    if type(s) is SafelyEscapedHTML:
        return str(s)
    else:
        return escape(s)
 
class Tag:
    def __init__(self, name, needs_closing=True):
        self.name = name
        self.needs_closing = needs_closing
        self.attrs = {}
        self.children = []

    def __call__(self, *class_, **attrs):
        assert not self.children
        new_tag = Tag(self.name, self.needs_closing)
        if class_:
            assert len(class_) == 1
            assert 'class' not in 'attrs'
            new_tag.attrs['class'] = class_[0]
        new_tag.attrs.update(attrs)
        return new_tag

    def __getitem__(self, children):
        new_tag = Tag(self.name, self.needs_closing)
        new_tag.attrs = self.attrs
        if type(children) is tuple:
            for child in children:
                new_tag.children.append(child)
        else:
            new_tag.children = [children]
        return new_tag

    def __repr__(self):
        return f'<{self.name} {repr(self.attrs)} children={len(self.children)} needs_closing={self.needs_closing} id={id(self)}>'

    def __str__(self):
        _dynamic_escaped_name = _dynamic_escape(self.name)
        result = f'<{_dynamic_escaped_name}'
        for name, value in self.attrs.items():
            result += f' {_dynamic_escape(name)}="{_dynamic_escape(value)}"'
        result += '>'
        for child in self.children:
            if isinstance(child, Tag):
                result += str(child)
            else:
                result += _dynamic_escape(child)
        if self.needs_closing:
            result += f'</{_dynamic_escaped_name}>'
        return result

    def template(self):
        strings = []
        placeholders = []

        _dynamic_escaped_name = _dynamic_escape(self.name)
        strings.append(f'<{_dynamic_escaped_name}')
        for name, value in self.attrs.items():
            if isinstance(value, Placeholder):
                strings[-1] += f' {_dynamic_escape(name)}="'
                placeholders.append(value.name)
                strings.append('"')
            else:
                strings[-1] += f' {_dynamic_escape(name)}="{_dynamic_escape(value)}"'
        strings[-1] += '>'
        for child in self.children:
            if isinstance(child, Tag):
                child_strings, child_placeholders = child.template()
                strings[-1] += child_strings[0]
                strings += child_strings[1:]
                placeholders += child_placeholders 
            elif isinstance(child, Placeholder):
                strings.append('')
                placeholders.append(child.name)
            else:
                strings[-1] += _dynamic_escape(child)
        if self.needs_closing:
            strings[-1] += f'</{_dynamic_escaped_name}>'
        return (strings, placeholders)

class Template:
    def __init__(self, page):
        strings, placeholders = page.template()
        self.strings = strings
        self.placeholders = placeholders

    def render(self, **values):
        return ''.join(_render((self.strings, self.placeholders), **values))

class Placeholder:
    def __init__(self, name):
        self.name = name


def _render(template, **values):
    strings, placeholders = template
    replaced = []
    for placeholder_name in template[1]:
        value = ''
        if placeholder_name in values:
            value = values[placeholder_name]
            if type(value) is not SafelyEscapedHTML:
                value = escape(value)
            replaced.append(value)
        else:
            raise Exception(f'Placeholder {placeholder_name} not found.')
    yield strings[0]
    for i in range(len(placeholders)):
        yield replaced[i]
        yield strings[i+1]


def example():
    html = Tag('html')
    head = Tag('head')
    title = Tag('title')
    body = Tag('body')
    p = Tag('p')
    a = Tag('a')
    link = Tag('link', needs_closing=False)
    br = Tag('br', needs_closing=False)
    
    def main(link_, href='here'):
        return html[
            head[
                title['Hello, world!'],
                link(**{'type':"stylesheet"})
            ],
            body[
                p('main')[
                    link_,
                    a(href=href)['This &<> is a link'],
                    '.'
                ],
                p['Hello', br, br],
            ]
        ]
    
    import time

    m = main('th"ere')
    print('Page direct:', str(m))
    num = 100000
    start = time.time()
    for x in range(num):
        str(main('th"ere'))
    duration = time.time() - start
    rps = int(num/duration)
    print('Page direct renders per second:', rps)
    
    template = Template(main(Placeholder('one'), Placeholder('two')))
    print('Via template with placeholders:', template.render(one='o&ne', two=SafelyEscapedHTML('t<b>w</b>o')))
    num = 1000000
    start = time.time()
    for x in range(num):
        template.render(one='o&ne', two=SafelyEscapedHTML('t<b>w</b>o'))
    duration = time.time() - start
    rps = int(num/duration)
    print('Template renders per second:', rps)

if __name__ == '__main__':
    example()

Comments

Be the first to comment.

Add Comment





Copyright James Gardner 1996-2020 All Rights Reserved. Admin.