Last active
April 19, 2020 01:00
-
-
Save dvarrazzo/ee242f0e87d5a0cf2b3b901f46288183 to your computer and use it in GitHub Desktop.
A simple way to generate valid xhtml in Python (circa 2007)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: [email protected] | |
""" | |
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