Skip to content

Instantly share code, notes, and snippets.

@dvarrazzo
Last active April 19, 2020 01:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dvarrazzo/ee242f0e87d5a0cf2b3b901f46288183 to your computer and use it in GitHub Desktop.
Save dvarrazzo/ee242f0e87d5a0cf2b3b901f46288183 to your computer and use it in GitHub Desktop.
A simple way to generate valid xhtml in Python (circa 2007)
r"""Implementation of a `stan`_\ -like XML description language.
.. _stan: http://www.kieranholland.com/code/documentation/nevow-stan/
An example XHTML page can be generated by:
.. python::
def items():
return [ T.li['foo'], T.li['bar'], T.li['baz'] ]
def adjective():
return "cruel"
doc = T.html(class_="foo")[ # class is a keyword
T.body[
T.p(id="123")[ # another tag attribute
"Hello, world" # a string is an element
],
T.br, # in [braces] put any sub-element
T.a(href="#")[
"<Goodbye>, ", # special chars are escaped
T.b[ adjective ], # callables can be sub-element
" world",
],
T.ul[ items ], # they can return element iterables too
]
]
print toxhtml(doc)
:copyright: 2007-2020 Daniele Varrazzo
:contact: piro@develer.com
"""
import xml.etree.cElementTree as et
class XmlTagFactory(object):
"""Return a `Tag` for each attribute read."""
def __getattr__(self, name):
return Tag(name)
T = XmlTagFactory()
"""The public `XmlTagFactory` instance."""
class Tag(object):
"""An XML tag description.
- Use the ``()`` syntax to update tag attribute.
- Use the ``[]`` syntax to add children elements.
See the `ministan` module docstring for an example.
"""
def __init__(self, tag):
"""Return a new `Tag` instance."""
self.tag = tag
self.children = []
self.attrs = {}
def __call__(self, **kwargs):
"""Update the tag attributes."""
if "class_" in kwargs:
kwargs["class"] = kwargs.pop("class_")
for k, v in kwargs.items():
if v is None:
continue
if not isinstance(v, str):
v = str(v)
self.attrs[k] = v
return self
def __getitem__(self, idx):
"""Extend element children."""
if isinstance(idx, tuple):
self.children.extend(idx)
else:
self.children.append(idx)
return self
def toelement(self):
"""Return an XML representation of the element with subelements.
:rtype: ``ElementTree.Element``
"""
builder = et.TreeBuilder()
self._toelement(builder)
return builder.close()
def _toelement(self, builder):
builder.start(self.tag, self.attrs)
self._toelement_ch(builder, self.children)
builder.end(self.tag)
def _toelement_ch(self, builder, elem):
if elem is None:
return
elif isinstance(elem, Tag):
elem._toelement(builder)
elif isinstance(elem, str):
builder.data(elem)
elif callable(elem):
self._toelement_ch(builder, elem())
elif hasattr(elem, "__iter__"): # iterable
for child in elem:
self._toelement_ch(builder, child)
else:
builder.data(str(elem))
xhtml_trans_doctype = (
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
)
"""Transitional XHTML doctype."""
def toxhtml(tag):
"""Return the XHTML document represented by a `tag`.
No assumption is done whether `tag` represents a valid XHTML document.
:Parameters:
`tag` : `Tag`
the element to represent
:return: the XHTML representation
:rtype: `str`
"""
doc = tag.toelement()
return xhtml_trans_doctype + et.tostring(doc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment