|
## {{{ 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(' ')*3 ==> <TD> </TD><TD> </TD><TD> </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/ }}} |