Generating HTML documents in python

Viewed 117003

In python, what is the most elegant way to generate HTML documents. I currently manually append all of the tags to a giant string, and write that to a file. Is there a more elegant way of doing this?

7 Answers

There is also a nice, modern alternative: airium: https://pypi.org/project/airium/

from airium import Airium

a = Airium()

a('<!DOCTYPE html>')
with a.html(lang="pl"):
    with a.head():
        a.meta(charset="utf-8")
        a.title(_t="Airium example")

    with a.body():
        with a.h3(id="id23409231", klass='main_header'):
            a("Hello World.")

html = str(a) # casting to string extracts the value

print(html)

Prints such a string:

<!DOCTYPE html>
<html lang="pl">
  <head>
    <meta charset="utf-8" />
    <title>Airium example</title>
  </head>
  <body>
    <h3 id="id23409231" class="main_header">
      Hello World.
    </h3>
  </body>
</html>

The greatest advantage of airium is - it has also a reverse translator, that builds python code out of html string. If you wonder how to implement a given html snippet - the translator gives you the answer right away.

Its repository contains tests with example pages translated automatically with airium in: tests/documents. A good starting point (any existing tutorial) - is this one: tests/documents/w3_architects_example_original.html.py

I wrote a simple wrapper for the lxml module (should work fine with xml as well) that makes tags for HTML/XML -esq documents.

Really, I liked the format of the answer by John Smith but I didn't want to install yet another module to accomplishing something that seemed so simple.

Example first, then the wrapper.

Example

from Tag import Tag


with Tag('html') as html:
    with Tag('body'):
        with Tag('div'):
            with Tag('span', attrib={'id': 'foo'}) as span:
                span.text = 'Hello, world!'
            with Tag('span', attrib={'id': 'bar'}) as span:
                span.text = 'This was an example!'

html.write('test_html.html')

Output:

<html><body><div><span id="foo">Hello, world!</span><span id="bar">This was an example!</span></div></body></html>

Output after some manual formatting:

<html>
    <body>
        <div>
            <span id="foo">Hello, world!</span>
            <span id="bar">This was an example!</span>
        </div>
    </body>
</html>

Wrapper

from dataclasses import dataclass, field
from lxml import etree


PARENT_TAG = None


@dataclass
class Tag:
    tag: str
    attrib: dict = field(default_factory=dict)
    parent: object = None
    _text: str = None

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = value
        self.element.text = value

    def __post_init__(self):
        self._make_element()
        self._append_to_parent()

    def write(self, filename):
        etree.ElementTree(self.element).write(filename)

    def _make_element(self):
        self.element = etree.Element(self.tag, attrib=self.attrib)

    def _append_to_parent(self):
        if self.parent is not None:
            self.parent.element.append(self.element)

    def __enter__(self):
        global PARENT_TAG
        if PARENT_TAG is not None:
            self.parent = PARENT_TAG
            self._append_to_parent()
        PARENT_TAG = self
        return self

    def __exit__(self, typ, value, traceback):
        global PARENT_TAG
        if PARENT_TAG is self:
            PARENT_TAG = self.parent

Related