An ElementTree Builder
Fredrik Lundh | November 2006 | Originally posted to online.effbot.org
Here’s a simple helper class for ElementTree, which lets you use a more convenient syntax to construct XML fragment trees:
import xml.etree.cElementTree as ET import functools class _E(object): def __call__(self, tag, *children, **attrib): elem = ET.Element(tag, attrib) for item in children: if isinstance(item, dict): elem.attrib.update(item) elif isinstance(item, basestring): if len(elem): elem[-1].tail = (elem[-1].tail or "") + item else: elem.text = (elem.text or "") + item elif ET.iselement(item): elem.append(item) else: raise TypeError("bad argument: %r" % item) return elem def __getattr__(self, tag): return functools.partial(self, tag) # create factory object E = _E()
This started out as a small tweak to the append helper function, but quickly turned into something more like Oren Tirosh’ ElementBuilder (dead link) (which is one of all those tools that I’ve stumbled upon, found interesting, but never gotten around to use).
Anyway, unlike the ordinary Element factory, the E factory allows you to pass in more than just a tag and some optional attributes; you can also pass in text and other elements. The text is added as either text or tail attributes, and elements are inserted at the right spot. Some small examples:
>>> ET.tostring(E("tag")) '<tag />' >>> ET.tostring(E("tag", "text")) '<tag>text</tag>' >>> ET.tostring(E("tag", "text", key="value")) '<tag key="value">text</tag>' >>> ET.tostring(E("tag", E("subtag", "text"), "tail")) '<tag><subtag>text</subtag>tail</tag>'
For simple tags, the factory also allows you to write “E.tag(…)” instead of “E(‘tag’, …)”:
>>> ET.tostring(E.tag()) '<tag />' >>> ET.tostring(E.tag("text")) '<tag>text</tag>' >>> ET.tostring(E.tag(E.subtag("text"), "tail")) '<tag><subtag>text</subtag>tail</tag>'
Here’s a somewhat larger example; this shows how to generate HTML documents, using a mix of prepared factory functions for inline elements, nested E.tag calls, and embedded XHTML fragments:
# some common inline elements A = E.a I = E.i B = E.b def CLASS(*args): # class is a reserved word, so we use a helper function return {"class": " ".join(args)} page = ( E.html( E.head( E.title("This is a sample document") ), E.body( E.h1("Hello!", CLASS("title")), E.p("This is a paragraph with ", B("bold"), " text in it!"), E.p("This is another paragraph, with a ", A("link", href="http://www.python.org"), "."), E.p("Here are some reservered characters: <spam&egg>."), ET.XML("<p>And finally, here's an embedded XHTML fragment.</p>"), ) ) ) print ET.tostring(page)
Here’s a prettyprinted version of the output from the above script:
<html> <head> <title>This is a sample document</title> </head> <body> <h1 class="title">Hello!</h1> <p>This is a paragraph with <b>bold</b> text in it!</p> <p>This is another paragraph, with a <a href="http://www.python.org">link</a>.</p> <p>Here are some reservered characters: <spam&egg>.</p> <p>And finally, here's an embedded XHTML fragment.</p> </body> </html>
I’m still experimenting with more mechanisms, including support for callables and nested sequences, but the above makes many ElementTree-based generation tasks a lot simpler.
Addendum
Tweaking, tweaking. This one’s pretty neat, I think:
from datetime import datetime RSS = ElementBuilder(typemap={ datetime: lambda e, v: v.strftime("%a, %d %b %Y %H:%M:%S %z") }) item = RSS.item( RSS.title("E=RSS squared"), RSS.link("http://effbot.org"), RSS.description("Yet another E-related example"), RSS.pubDate(datetime.now(UTC())) )
Code will be posted later. Stay tuned.