"""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)