.. currentmodule:: minihtml
.. TODO The outline can be improved.
.. _basics:
Basics
======
Installation
------------
Install the `minihtml` package from PyPI:
.. code-block:: console
$ pip install minihtml
or:
.. code-block:: console
$ uv add minihtml
.. _prototypes:
Prototypes
----------
All minihtml HTML elements are created from a *Prototype*. A prototype is
an immutable element factory that produces :ref:`elements`.
You can import the prototypes for all HTML5 elements from :ref:`minihtml.tags `:
>>> from minihtml.tags import html, head, title
>>> html
Converting an element to a string returns its HTML representation. There are
several ways to produce an element from a prototype.
Calling the prototype produces a new element:
>>> elem = html()
>>> elem
>>> print(elem)
Positional arguments add content to the element. Content can be other elements
or strings:
>>> elem = html(head(title("hello, world!")))
>>> print(elem)
hello, world!
Keyword arguments are converted to HTML :ref:`attributes `:
>>> elem = html(lang="en")
>>> print(elem)
Indexing a prototype with a string is a convenient way to set the new element's
``class`` and/or ``id`` attributes:
>>> from minihtml.tags import div
>>> elem = div["#my-id class-a class-b"]
>>> print(elem)
Using a prototype as a context manager creates an *element context*: New
elements created within the context are added as children to the parent element
(the element returned by the context manager):
>>> with html as elem: # creates an element context with parent element `elem` (html)
... with head: # creates a nested element context
... title("hello, world!")
<...>
>>> print(elem)
hello, world!
As you can see in the example above, it does not matter how the elements are
created, from a context manager (``with head:``) or by calling (``title()``),
they are always added to the parent element of the containing context. There is
one important exception: Elements passed as positional arguments to another
element or prototype are exempt from this, so this works as expected:
>>> from minihtml.tags import p, em
>>> with div as elem:
... p(em("this em element is a child of p, not div"))
<...>
>>> print(elem)
this em element is a child of p, not div
Finally, you can create new prototypes with the :func:`make_prototype` factory
function:
>>> from minihtml import make_prototype
>>> custom_element = make_prototype("custom-element")
>>> custom_element
>>> elem = custom_element()
>>> print(elem)
.. _elements:
Elements
--------
Elements provide the same APIs as :ref:`prototypes`, but they are mutable.
Calling, indexing or using an element as a context manager returns the element
itself, so all operations can be chained to modify the element further:
>>> from minihtml.tags import a
>>> elem = a["repo"](href="https://github.com/trendels/minihtml")("minihtml")
>>> print(elem)
minihtml
>>> with html(lang="en") as elem:
... with div["#main"]:
... p("content")
<...>
>>> print(elem)
content
.. _attributes:
Attributes
----------
When setting attributes via keyword arguments, the names of keyword arguments
are modified according to these rules:
- If the name is ``_`` (a single underscore), it is not changed.
- Any trailing underscores are stripped from the name.
- Any remaining underscores are converted to hyphens (``-``).
Examples:
>>> print(div(_="something"))
>>> print(div(foo_bar="x"))
>>> from minihtml.tags import label
>>> print(label(for_="target")("Label"))
>>> print(div(__foo="bar"))
Attribute values are quoted (HTML-escaped) automatically:
>>> print(div(attr='a value with "quotes" and a '))
Boolean attributes
^^^^^^^^^^^^^^^^^^
A boolean HTML attribute is one that does not have a value. For example, to
mark an input field as required, you can write ````. There is
also an alternative syntax, which minihtml uses: ````.
To set a boolean attribute, pass ``True`` as the value. ``False`` is also a
valid value, and causes the attribute to be omitted (this can be useful if you
set the attribute from a variable):
>>> from minihtml.tags import input
>>> print(input(required=True, disabled=False))
.. _content:
Content
-------
We have already seen that elements and strings can be passed to elements as content.
String content is also HTML-escaped automatically. To include strings as HTML,
use the :func:`safe` helper function. Only use this if the content comes from a
trusted source, to prevent `Cross Site Scripting (XSS) vulnerabilities
`_!
>>> print(div("content with a "))
content with a <tag>
>>> from minihtml import safe
>>> print(div(safe("inline html")))
inline html
To add text content to the parent element inside an element context, you can use the
:func:`text` helper function (:func:`safe` can be used in the same way):
>>> from minihtml import text
>>> with div as elem:
... text("text")
... text(" and more text")
<...>
>>> print(elem)
text and more text
Sometimes you might need to pass around a group of elements that do not share a
common parent element. This is called a *Fragment*. Fragments are created using
the :func:`fragment` function:
>>> from minihtml import fragment
>>> f = fragment(p("one"), p("two"), "three")
>>> print(f)
one
two
three
:func:`fragment` can also be used in an element context:
>>> with div as elem:
... fragment(p("one"), p("two"), "three")
<...>
>>> print(elem)