Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/turbohtml/build.py: 96%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2A terse builder for constructing HTML trees, the way ``lxml.builder.E`` did.
4This is a thin ergonomic layer over the existing element API: every ``E.<tag>(...)`` call builds a real
5:class:`turbohtml.Element` and appends its children through :meth:`turbohtml.Element.append`, so the result is an
6ordinary turbohtml tree that serializes with :meth:`turbohtml.Element.serialize`. Sorting a call's arguments into the
7attribute mapping and the children runs in C too; this module only names the tags.
9A call takes its arguments in order: a leading mapping is the element's attributes, a string becomes a
10:class:`turbohtml.Text` node, and any other node is appended as-is::
12 from turbohtml.build import E
14 doc = E.div({"class": "card"}, E.h1("Title"), E.p("body"))
15 print(doc.serialize()) # <div class="card"><h1>Title</h1><p>body</p></div>
17Text serializes escaped, except inside ``script``, ``style``, ``xmp``, ``iframe``, ``noembed``, ``noframes`` and
18``plaintext``, whose content the HTML serialization writes as is. Keep untrusted text out of those elements: a
19``</script`` in it ends the element and the rest reads as markup.
20"""
22from __future__ import annotations
24from collections.abc import Iterable, Mapping
25from typing import TYPE_CHECKING, Final, TypeAlias
27from ._html import _build_document, _build_element
29if TYPE_CHECKING:
30 from ._html import Document, Element, Node
32#: Attributes accepted by :class:`turbohtml.Element`: a name maps to a string, a token list, or ``None`` for a bare
33#: boolean attribute such as ``disabled``.
34Attributes: TypeAlias = "Mapping[str, str | list[str] | None]"
36#: A single builder argument: a leading attribute mapping, an existing node, or a string that becomes a
37#: :class:`turbohtml.Text` node.
38Content: TypeAlias = "Attributes | Node | str"
41def _build(tag: str, args: tuple[Content, ...]) -> Element:
42 """
43 Build one element: a leading mapping sets its attributes, and the rest become children in order.
45 :raises ValueError: if the tag or an attribute name carries a character HTML forbids there, or if a
46 void element such as ``br`` or ``img`` is given children.
47 :raises TypeError: if an attribute mapping is not the first argument.
48 """
49 return _build_element(tag, args, Mapping)
52class _TagFactory:
53 """The callable returned by ``E.<tag>``; it remembers the tag name and builds that element when called."""
55 __slots__ = ("_tag",)
57 def __init__(self, tag: str) -> None:
58 """Remember the tag name this factory builds."""
59 self._tag = tag
61 def __call__(self, *args: Content) -> Element:
62 """
63 Build the element from a leading attribute mapping and the child nodes and strings that follow.
65 :raises ValueError: if the tag or an attribute name carries a character HTML forbids there, or if a
66 void element such as ``br`` or ``img`` is given children.
67 :raises TypeError: if an attribute mapping is not the first argument.
68 """
69 return _build(self._tag, args)
72class ElementMaker:
73 """
74 A factory whose attribute access names the tag to build: ``E.div`` returns a callable that builds a ``<div>``.
76 Use the singleton ``E``, or instantiate a private maker. ``E.section(...)`` and ``E("section", ...)`` are
77 equivalent; the call form takes a tag that is not a Python identifier.
78 """
80 def __getattr__(self, tag: str) -> _TagFactory:
81 """Return the factory for ``tag``; dunder lookups fall through so copy and pickle protocols are not hijacked."""
82 if tag.startswith("__") and tag.endswith("__"):
83 raise AttributeError(tag)
84 return _TagFactory(tag)
86 def __call__(self, tag: str, /, *args: Content) -> Element:
87 """
88 Build ``tag`` from a leading attribute mapping and the child nodes and strings that follow.
90 :raises ValueError: if the tag or an attribute name carries a character HTML forbids there, or if a
91 void element such as ``br`` or ``img`` is given children.
92 :raises TypeError: if an attribute mapping is not the first argument.
93 """
94 return _build(tag, args)
97#: The shared builder: ``E.div(...)`` or ``E("div", ...)`` builds a ``<div>`` element.
98E: Final = ElementMaker()
101def document(
102 *,
103 title: str | None = None,
104 lang: str | None = None,
105 charset: str | None = "utf-8",
106 head: Iterable[Node | str] = (),
107 body: Iterable[Node | str] = (),
108) -> Document:
109 """
110 Build a complete HTML5 document around the given head and body content.
112 Where ``E`` builds a bare fragment, this scaffolds the whole page: a ``<!DOCTYPE html>`` followed by ``<html>``
113 holding a ``<head>`` and a ``<body>``. The head leads with a ``<meta charset>`` and a ``<title>`` (when supplied),
114 then the ``head`` content; the body holds the ``body`` content. The result is a :class:`turbohtml.Document`, so
115 ``document(...).serialize()`` emits the doctype and the full shell::
117 from turbohtml.build import E, document
119 page = document(title="Report", lang="en", body=[E.h1("Sales"), E.p("Up 4%")])
120 page.serialize()
121 # <!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Report</title></head>
122 # <body><h1>Sales</h1><p>Up 4%</p></body></html>
124 :param title: the ``<title>`` text; ``None`` omits the element.
125 :param lang: the ``lang`` attribute for ``<html>``; ``None`` omits it.
126 :param charset: the ``<meta charset>`` value; ``None`` omits the meta element (e.g. when the charset is set by an
127 HTTP header instead).
128 :param head: extra ``<head>`` content, appended after the meta and title; a string becomes a
129 :class:`turbohtml.Text` node.
130 :param body: the ``<body>`` content; a string becomes a :class:`turbohtml.Text` node.
131 :returns: the assembled :class:`turbohtml.Document`.
132 :raises TypeError: if ``title``, ``lang``, or ``charset`` is neither a str nor ``None``, or a head or body item is
133 not a node or string.
134 """
135 return _build_document(head, body, title, lang, charset)
138__all__ = [
139 "Attributes",
140 "Content",
141 "E",
142 "ElementMaker",
143 "document",
144]