Skip to content

References

kTemplate.tag

Tag

HTML / SVG element generation class

Examples:

- void element, child=None (default)
>>> Tag('br')
<br />

- void Tag w/ attr
>>> Tag("img", src="http://img.url")
<img src="http://img.url" />

- empty string child -> Tag with end tag but no content
>>> Tag("script", child="", src="url")
<script src="url"></script>

- boolean attribute(attribute w/o value)
>>> Tag("option", "a", "selected")
<option selected>a</option>

- mix of boolean attribute and normal attribute
>>> Tag('a', 'foo', 'm-2', 'rounded', 'text-teal-400', href='bar')
<a m-2 rounded text-teal-400 href="bar">foo</a>

- nested Tag
>>> Tag("div", Tag("i", "x"))
<div><i>x</i></div>

Parameters:

Name Type Description Default
tag_name str

tag name

required
child str | Tag | None

inner text or other Tag, default None

None
args list[str]

names of value-less attributes - eg. defer, selected - useful for UnoCSS attributify mode

()
kwargs dict

tag attributes, in form of key="val"

{}
Source code in kTemplate/tag.py
class Tag:
    """HTML / SVG element generation class

    Examples:

        - void element, child=None (default)
        >>> Tag('br')
        <br />

        - void Tag w/ attr
        >>> Tag("img", src="http://img.url")
        <img src="http://img.url" />

        - empty string child -> Tag with end tag but no content
        >>> Tag("script", child="", src="url")
        <script src="url"></script>

        - boolean attribute(attribute w/o value)
        >>> Tag("option", "a", "selected")
        <option selected>a</option>

        - mix of boolean attribute and normal attribute
        >>> Tag('a', 'foo', 'm-2', 'rounded', 'text-teal-400', href='bar')
        <a m-2 rounded text-teal-400 href="bar">foo</a>

        - nested Tag
        >>> Tag("div", Tag("i", "x"))
        <div><i>x</i></div>

    Args:
        tag_name (str): tag name
        child (str | Tag | None): inner text or other Tag, default None
        args (list[str], optional): names of value-less attributes
            - eg. `defer`, `selected`
            - useful for UnoCSS attributify mode
        kwargs (dict): tag attributes, in form of `key="val"`

    """

    tag_name: str
    args: list[str] = []
    kwargs: dict[str, str] = {}
    parent: Self | None = None
    children: list[Self | str] | None = None
    context_stack: list[Self] = []  # context stack for context manager

    def __init__(
        self,
        tag_name: str | None,
        child: str | Self | None = None,
        *args,
        **kwargs,
    ):
        """initialize Tag, append to current context stack if exists"""
        self.tag_name = tag_name
        self.args = args
        self.kwargs = kwargs
        self.children = self.prepare_child(child)

        if Tag.context_stack:
            Tag.context_stack[-1].add(self)

    def prepare_child(self, child: str | Self | None) -> list[Self | str] | None:
        """utils, prepare children for the Tag"""
        if child is None:
            return None
        if isinstance(child, Tag):
            child.parent = self
        return [child]

    def __enter__(self):
        """enter context manager, append self to current context stack

        Examples:
            >>> with Tag("div", "hello") as div: # doctest: +SKIP
            ...     Tag("p", "world")
            >>> div                              # doctest: +SKIP
            <div>hello<p>world</p></div>
        """
        Tag.context_stack.append(self)
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """exit context manager, pop self from current context stack"""
        tag = Tag.context_stack.pop()
        # accept children if their parent is itself only
        self.children = [
            child
            for child in self.children
            if isinstance(child, str) or child.parent is self
        ]

    def __repr__(self):
        """string representation of the Tag"""

        # handle python naming restriction
        correct_keys = [
            (
                key.removesuffix("_")
                if key in ("class_", "for_")
                else key.replace("_", "-")
            )
            for key in self.kwargs.keys()
        ]

        kwargs_tuple = zip(correct_keys, self.kwargs.values())

        kwargs = [f'{key}="{val}"' for key, val in kwargs_tuple]
        attr = [self.tag_name] + list(self.args) + kwargs
        attr_str = " ".join(attr)

        content = None if self.children is None else "".join(map(str, self.children))

        # void element with self closing tag
        if content is None:
            return f"<{attr_str} />"

        return f"<{attr_str}>{content}</{self.tag_name}>"

    def add(self, child: str | Self, *args, **kwargs) -> Self:
        """add child and attributes to current Tag

        Examples:
            >>> opt = Tag('option')
            >>> opt.add('hello', 'selected', value='world')
            <option selected value="world">hello</option>
        """
        tail = self.prepare_child(child)
        self.children = tail if self.children is None else self.children + tail
        self.args += args
        self.kwargs.update(kwargs)
        return self

    def pretty(self, indent="    ") -> str:
        """pretty print the Tag"""
        dom = parseString(str(self))
        return dom.childNodes[0].toprettyxml(indent=indent)

    @staticmethod
    def comment(comment: str) -> str:
        """comment element

        Examples:
            >>> Tag.comment("hello")
            '<!--hello-->'
        """
        return Document().createComment(comment).toxml()

    @staticmethod
    def doctype(kind: str = "html") -> str:
        """DOCTYPE element

        Examples:
            >>> Tag.doctype()
            '<!DOCTYPE html>'
        """
        return f"<!DOCTYPE {kind}>"

__enter__()

enter context manager, append self to current context stack

Examples:

>>> with Tag("div", "hello") as div:
...     Tag("p", "world")
>>> div
<div>hello<p>world</p></div>
Source code in kTemplate/tag.py
def __enter__(self):
    """enter context manager, append self to current context stack

    Examples:
        >>> with Tag("div", "hello") as div: # doctest: +SKIP
        ...     Tag("p", "world")
        >>> div                              # doctest: +SKIP
        <div>hello<p>world</p></div>
    """
    Tag.context_stack.append(self)
    return self

__exit__(exc_type, exc_value, traceback)

exit context manager, pop self from current context stack

Source code in kTemplate/tag.py
def __exit__(self, exc_type, exc_value, traceback):
    """exit context manager, pop self from current context stack"""
    tag = Tag.context_stack.pop()
    # accept children if their parent is itself only
    self.children = [
        child
        for child in self.children
        if isinstance(child, str) or child.parent is self
    ]

__init__(tag_name: str | None, child: str | Self | None = None, *args, **kwargs)

initialize Tag, append to current context stack if exists

Source code in kTemplate/tag.py
def __init__(
    self,
    tag_name: str | None,
    child: str | Self | None = None,
    *args,
    **kwargs,
):
    """initialize Tag, append to current context stack if exists"""
    self.tag_name = tag_name
    self.args = args
    self.kwargs = kwargs
    self.children = self.prepare_child(child)

    if Tag.context_stack:
        Tag.context_stack[-1].add(self)

__repr__()

string representation of the Tag

Source code in kTemplate/tag.py
def __repr__(self):
    """string representation of the Tag"""

    # handle python naming restriction
    correct_keys = [
        (
            key.removesuffix("_")
            if key in ("class_", "for_")
            else key.replace("_", "-")
        )
        for key in self.kwargs.keys()
    ]

    kwargs_tuple = zip(correct_keys, self.kwargs.values())

    kwargs = [f'{key}="{val}"' for key, val in kwargs_tuple]
    attr = [self.tag_name] + list(self.args) + kwargs
    attr_str = " ".join(attr)

    content = None if self.children is None else "".join(map(str, self.children))

    # void element with self closing tag
    if content is None:
        return f"<{attr_str} />"

    return f"<{attr_str}>{content}</{self.tag_name}>"

add(child: str | Self, *args, **kwargs) -> Self

add child and attributes to current Tag

Examples:

>>> opt = Tag('option')
>>> opt.add('hello', 'selected', value='world')
<option selected value="world">hello</option>
Source code in kTemplate/tag.py
def add(self, child: str | Self, *args, **kwargs) -> Self:
    """add child and attributes to current Tag

    Examples:
        >>> opt = Tag('option')
        >>> opt.add('hello', 'selected', value='world')
        <option selected value="world">hello</option>
    """
    tail = self.prepare_child(child)
    self.children = tail if self.children is None else self.children + tail
    self.args += args
    self.kwargs.update(kwargs)
    return self

comment(comment: str) -> str staticmethod

comment element

Examples:

>>> Tag.comment("hello")
'<!--hello-->'
Source code in kTemplate/tag.py
@staticmethod
def comment(comment: str) -> str:
    """comment element

    Examples:
        >>> Tag.comment("hello")
        '<!--hello-->'
    """
    return Document().createComment(comment).toxml()

doctype(kind: str = 'html') -> str staticmethod

DOCTYPE element

Examples:

>>> Tag.doctype()
'<!DOCTYPE html>'
Source code in kTemplate/tag.py
@staticmethod
def doctype(kind: str = "html") -> str:
    """DOCTYPE element

    Examples:
        >>> Tag.doctype()
        '<!DOCTYPE html>'
    """
    return f"<!DOCTYPE {kind}>"

prepare_child(child: str | Self | None) -> list[Self | str] | None

utils, prepare children for the Tag

Source code in kTemplate/tag.py
def prepare_child(self, child: str | Self | None) -> list[Self | str] | None:
    """utils, prepare children for the Tag"""
    if child is None:
        return None
    if isinstance(child, Tag):
        child.parent = self
    return [child]

pretty(indent=' ') -> str

pretty print the Tag

Source code in kTemplate/tag.py
def pretty(self, indent="    ") -> str:
    """pretty print the Tag"""
    dom = parseString(str(self))
    return dom.childNodes[0].toprettyxml(indent=indent)

kTemplate.gen_elements

Pre-generate HTML and SVG element to kTemplate/elements.py

We take pre-generating approach because Pylance cannot infer the type of partial functions dynamically, failing attempts included:

  • update local variable with locals().update()
  • use exec() to create partial functions.

html_tags = 'a abbr acronym address applet area article aside audio b base basefont bdi bdo big blockquote body br button canvas caption center cite code col colgroup data datalist dd del details dfn dialog dir div dl dt em embed fieldset figcaption figure font footer form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd label legend li link main map mark menu meta meter nav noframes noscript object ol optgroup option output p param picture pre progress q rp rt ruby s samp script search section select small source span strike strong style sub summary sup svg table tbody td template textarea tfoot th thead time title tr track tt u ul var video wbr' module-attribute

source: https://www.w3schools.com/tags/

svg_tags = 'a animate animateMotion animateTransform circle clipPath defs desc ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect script set stop style svg switch symbol text textPath title tspan use view' module-attribute

source: https://developer.mozilla.org/en-US/docs/Web/SVG/Element

generate_elements()

generate and write HTML and SVG elements to elements.py

Source code in kTemplate/gen_elements.py
def generate_elements():
    """generate and write HTML and SVG elements to `elements.py`"""
    with open("kTemplate/elements.py", "w") as f:
        f.write(f"from .tag import Tag\n")
        f.write(f"from .{Path(__file__).stem} import {typed_partial.__name__}\n\n")
        for tag in all_tags:
            # handle special cases, del is a keyword
            alias = tag + "_" if tag in ["del"] else tag
            f.write(f"{alias} = {typed_partial.__name__}(Tag, '{tag}')\n")