Skip to content

Instantly share code, notes, and snippets.

@tupy
Created November 19, 2012 13:12
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 tupy/4110566 to your computer and use it in GitHub Desktop.
Save tupy/4110566 to your computer and use it in GitHub Desktop.
HTMLTags - generate HTML in Python (Python recipe)
print A('bar', href="foo") ==> <A href="foo">bar</A>
## {{{ http://code.activestate.com/recipes/366000/ (r11)
"""Classes to generate HTML in Python
The HTMLTags module defines a class for all the valid HTML tags, written in
uppercase letters. To create a piece of HTML, the general syntax is :
t = TAG(inner_HTML, key1=val1,key2=val2,...)
so that "print t" results in :
<TAG key1="val1" key2="val2" ...>inner_HTML</TAG>
For instance :
print A('bar', href="foo") ==> <A href="foo">bar</A>
To generate HTML attributes without value, give them the value True :
print OPTION('foo',SELECTED=True,value=5) ==>
<OPTION value="5" SELECTED>
The inner_HTML argument can be an instance of an HTML class, so that
you can nest tags, like this :
print B(I('foo')) ==> <B><I>foo</I></B>
TAG instances support addition :
print B('bar')+INPUT(name="bar") ==> <B>bar</B><INPUT name="bar">
and repetition :
print TH('&nbsp')*3 ==> <TD>&nbsp;</TD><TD>&nbsp;</TD><TD>&nbsp;</TD>
For complex expressions, a tag can be nested in another using the operator <=
Considering the HTML document as a tree, this means "add child" :
form = FORM(action="foo")
form <= INPUT(name="bar")
form <= INPUT(Type="submit",value="Ok")
If you have a list (or any iterable) of instances, you can't concatenate the
items with sum(instance_list) because sum takes only numbers as arguments. So
there is a function called Sum() which will do the same :
Sum( TR(TD(i)+TD(i*i)) for i in range(100) )
generates the rows of a table showing the squares of integers from 0 to 99
A simple document can be produced by :
print HTML( HEAD(TITLE('Test document')) +
BODY(H1('This is a test document')+
'First line'+BR()+
'Second line'))
This will be rendered as :
<HTML>
<HEAD>
<TITLE>Test document</TITLE>
</HEAD>
<BODY>
<H1>This is a test document</H1>
First line
<BR>
Second line
</BODY>
</HTML>
If the document is more complex it is more readable to create the elements
first, then to print the whole result in one instruction. For example :
head = HEAD()
head <= TITLE('Record collection')
head <= LINK(rel="Stylesheet",href="doc.css")
title = H1('My record collection')
table = TABLE()
table <= TR(TH('Title')+TH('Artist'))
for rec in records:
row = TR()
# note the attribute key Class with leading uppercase
# because "class" is a Python keyword
row <= TD(rec.title,Class="title")+TD(rec.artist,Class="artist")
table <= row
print HTML(head+BODY(title+table))
"""
import cStringIO
class TAG:
"""Generic class for tags"""
def __init__(self, inner_HTML="", **attrs):
self.tag = self.__class__.__name__
self.inner_HTML = inner_HTML
self.attrs = attrs
self.children = []
self.brothers = []
def __str__(self):
res=cStringIO.StringIO()
w=res.write
if self.tag != "TEXT":
w("<%s" %self.tag)
# attributes which will produce arg = "val"
attr1 = [ k for k in self.attrs
if not isinstance(self.attrs[k],bool) ]
w("".join([' %s="%s"'
%(k.replace('_','-'),self.attrs[k]) for k in attr1]))
# attributes with no argument
# if value is False, don't generate anything
attr2 = [ k for k in self.attrs if self.attrs[k] is True ]
w("".join([' %s' %k for k in attr2]))
w(">")
if self.tag in ONE_LINE:
w('\n')
w(str(self.inner_HTML))
for child in self.children:
w(str(child))
if self.tag in CLOSING_TAGS:
w("</%s>" %self.tag)
if self.tag in LINE_BREAK_AFTER:
w('\n')
if hasattr(self,"brothers"):
for brother in self.brothers:
w(str(brother))
return res.getvalue()
def __le__(self,other):
"""Add a child"""
if isinstance(other,str):
other = TEXT(other)
self.children.append(other)
other.parent = self
return self
def __add__(self,other):
"""Return a new instance : concatenation of self and another tag"""
res = TAG()
res.tag = self.tag
res.inner_HTML = self.inner_HTML
res.attrs = self.attrs
res.children = self.children
res.brothers = self.brothers + [other]
return res
def __radd__(self,other):
"""Used to add a tag to a string"""
if isinstance(other,str):
return TEXT(other)+self
else:
raise ValueError,"Can't concatenate %s and instance" %other
def __mul__(self,n):
"""Replicate self n times, with tag first : TAG * n"""
res = TAG()
res.tag = self.tag
res.inner_HTML = self.inner_HTML
res.attrs = self.attrs
for i in range(n-1):
res += self
return res
def __rmul__(self,n):
"""Replicate self n times, with n first : n * TAG"""
return self*n
# list of tags, from the HTML 4.01 specification
CLOSING_TAGS = ['A', 'ABBR', 'ACRONYM', 'ADDRESS', 'APPLET',
'B', 'BDO', 'BIG', 'BLOCKQUOTE', 'BUTTON',
'CAPTION', 'CENTER', 'CITE', 'CODE',
'DEL', 'DFN', 'DIR', 'DIV', 'DL',
'EM', 'FIELDSET', 'FONT', 'FORM', 'FRAMESET',
'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
'I', 'IFRAME', 'INS', 'KBD', 'LABEL', 'LEGEND',
'MAP', 'MENU', 'NOFRAMES', 'NOSCRIPT', 'OBJECT',
'OL', 'OPTGROUP', 'PRE', 'Q', 'S', 'SAMP',
'SCRIPT', 'SELECT', 'SMALL', 'SPAN', 'STRIKE',
'STRONG', 'STYLE', 'SUB', 'SUP', 'TABLE',
'TEXTAREA', 'TITLE', 'TT', 'U', 'UL',
'VAR', 'BODY', 'COLGROUP', 'DD', 'DT', 'HEAD',
'HTML', 'LI', 'P', 'TBODY','OPTION',
'TD', 'TFOOT', 'TH', 'THEAD', 'TR']
NON_CLOSING_TAGS = ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'FRAME',
'HR', 'IMG', 'INPUT', 'ISINDEX', 'LINK',
'META', 'PARAM']
# create the classes
for tag in CLOSING_TAGS + NON_CLOSING_TAGS + ['TEXT']:
exec("class %s(TAG): pass" %tag)
def Sum(iterable):
"""Return the concatenation of the instances in the iterable
Can't use the built-in sum() on non-integers"""
it = [ item for item in iterable ]
if it:
return reduce(lambda x,y:x+y, it)
else:
return ''
# whitespace-insensitive tags, determines pretty-print rendering
LINE_BREAK_AFTER = NON_CLOSING_TAGS + ['HTML','HEAD','BODY',
'FRAMESET','FRAME',
'TITLE','SCRIPT',
'TABLE','TR','TD','TH','SELECT','OPTION',
'FORM',
'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
]
# tags whose opening tag should be alone in its line
ONE_LINE = ['HTML','HEAD','BODY',
'FRAMESET'
'SCRIPT',
'TABLE','TR','TD','TH','SELECT','OPTION',
'FORM',
]
if __name__ == '__main__':
head = HEAD(TITLE('Test document'))
body = BODY()
body <= H1('This is a test document')
body <= 'First line' + BR() + 'Second line'
print HTML(head + body)
## end of http://code.activestate.com/recipes/366000/ }}}
<TAG key1="val1" key2="val2" ...>innerHTML</TAG>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment