################################################################################## # Copyright (c) 2006 Gerard Flanagan # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # Requires 'elementtree' which can be obtained from: # # http://www.effbot.org/zone/element-index.htm # # The tidywrite() method uses 'tidy-lib': # # http://tidy.sourceforge.net/ # # via the python binding here: # # http://utidylib.berlios.de/ # # To include colourised Python within HTML, the following file is required: # # http://effbot.python-hosting.com/file/stuff/sandbox/pythondoc/colorizer.py # # Thanks to Fredrik Lundh, Bruno Desthuilliers, Steve Juranich and Daniel Nogradi # ################################################################################## HTML4_STRICT = '' HTML4_TRANS = '' HTML4_FRAMES = '' XHTML4_STRICT = '' XHTML4_TRANS = '' XHTML4_FRAMES = '' SELF_CLOSING_TAGS = ['br', 'img', 'hr', 'meta', 'link', 'input'] from elementtree.SimpleXMLWriter import XMLWriter from cStringIO import StringIO try: from tidy import parseString except ImportError: def parseString(text): return text def _iterelement( element ): ''' returns all lists from a list of lists ''' for item in element: yield item if len(item) > 0: for subitem in _iterelement( item ): yield subitem class _HtmlWriter(XMLWriter, object): #The XmlWriter.data method does not call #XmlWriter.__flush. This means that nothing will be #written if an instance of XmlWriter only ever #calls data(), and never calls say, element() or #start(). Consequence: parentless _HtmlTexts #and _HtmlTemplates would render as None - so #override this method to avoid this def data( self, text ): '''write htmlentity-encoded text''' super( _HtmlWriter, self).data(text) self._XMLWriter__flush() #the data() method will escape all HTML entities, #write() will not - used by _HtmlIncludeFile and #_HtmlLiteral for example def write(self, text): '''write raw, ie. unescaped, text''' if self._XMLWriter__open: self._XMLWriter__open = 0 self._XMLWriter__write( '>' ) self._XMLWriter__write(text) class _HtmlElement(list): def __init__(self, tag): self.tag = tag self.innertext = None self.attrib = {} def __call__(self, innertext='', **kwargs): self.innertext = innertext self.attrib = kwargs return self def __repr__(self): return "<_HtmlElement %s at %x>" % (self.tag, id(self)) def append(self, value): super(_HtmlElement,self).append(value) return value def insert(self, index, value): super(_HtmlElement,self).insert(value) return value def __getattr__(self, value): try: elmnt = types[value]() except KeyError: elmnt = _HtmlElement(value) return self.append( elmnt ) def _write(self, writer): #class is a keyword, so can't use it as an attribute name #use one of 'css','class_','cssClass' then replace it here cssClass = None for key in self.attrib.iterkeys(): if key in ['css','class_','cssClass']: cssClass = self.attrib[key] del self.attrib[key] break if cssClass is not None: self.attrib['class'] = cssClass #now write this element if self.tag in SELF_CLOSING_TAGS: writer.element( self.tag, None, self.attrib ) #now write any children for node in self: node._write(writer) else: writer.start(self.tag, self.attrib) if self.innertext is not None: writer.data(self.innertext) #now write any children for node in self: node._write(writer) writer.end() def write(self, outfile ): w = _HtmlWriter(outfile) self._write( w ) def __str__(self): s = StringIO() self.write( s ) ret = s.getvalue() s.close() return ret def tidy(self): return str(parseString( self.__str__() )) def tidywrite(self, outfile): outfile.write( self.tidy() ) def fill( self, searchList ): for t in _iterelement( self ): if hasattr( t, 'fill' ):#isinstance( t, _HtmlPlaceholder ): t.fill( searchList ) def __mod__(self, searchList): self.fill(searchList) return self def __imod__(self, searchList): self.fill(searchList) return self class _HtmlText(_HtmlElement): def __init__(self, innertext=''): self.innertext = innertext def __call__(self, innertext=''): self.innertext = innertext return self def __repr__(self): return "<_HtmlText at %x>" % id(self) def _write(self, writer): writer.data( self.innertext ) for child in self: child._write(writer) class _HtmlLiteral(_HtmlText): def __repr__(self): return "<_HtmlLiteral at %x>" % id(self) def _write(self, writer): writer.write( self.innertext ) for child in self: child._write(writer) class _HtmlComment(_HtmlText): def __repr__(self): return "<_HtmlComment at %x>" % id(self) def _write( self, writer, indent=0 ): writer.write('\n') writer.comment( self.innertext ) writer.write('\n') for child in self: child._write(writer) class _HtmlIncludeFile(_HtmlElement): def __repr__(self): return "<_HtmlIncludeFile %s at %x>" % (self.filepath[10:], id(self)) def __init__(self, path=None): self.filepath = path def __call__( self, path=None ): self.filepath = path return self def _write(self, writer): infile = open(self.filepath, 'r') try: for line in infile: writer.write( line ) finally: infile.close() for child in self: child._write(writer) class _HtmlPlaceholder(_HtmlElement): def __repr__(self): return "<_HtmlPlaceholder %s at %x>" % ( self.name, id(self) ) def __init__(self, name='', format='%s', searchList=None): self.name = name self.format = format self.innertext = None self.rawtext = None if searchList: self.fill( searchList ) def __call__(self, name, format='%s', searchList=None): self.__init__(name, format, searchList) return self def fill( self, searchList ): name = self.name for item in searchList: if isinstance( item, dict ): value = item.get( name ) if value is not None: if isinstance( value, str ): self.innertext = value self.rawtext = None else: #this clause is here to support the possibility of #'filling' a placeholder with another _HtmlElement. #However, the other element's string representation #will already be html-escaped, so don't want to do #it again, hence rawtext not innertext self.rawtext = self.format % str(value) self.innertext = None break #found a value, no need to continue def __mod__(self, searchList): self.fill( searchList ) return self def __imod__(self, searchList): return self.__mod__(searchList) def _write(self, writer): if self.innertext is not None: writer.data(self.innertext) elif self.rawtext is not None: writer.write(self.rawtext) else: s = ''.join( [ ' %(', self.name, ')', self.format[1:], ' ' ] ) writer.data( s ) for child in self: child._write(writer) class _CheetahTemplate(_HtmlPlaceholder): def __repr__(self): return "<_CheetahTemplate at %x>" % id(self) def __init__(self, source=None, searchList=None, file=None, name='CHEETAH'): super(_CheetahTemplate, self).__init__( name, format='%s' ) self.source = source self.file = None if searchList: self.fill( searchList ) def __call__(self, source=None, searchList=None, file=None, name='CHEETAH'): self.__init__(source, searchList, file, name) return self def fill(self, searchList): from Cheetah.Template import Template t = Template( source=self.source, searchList=searchList, file = self.file ) self.rawtext = str( t ) class _HtmlPythonSource(_HtmlIncludeFile): def __repr__(self): return "<_HtmlPythonSource at %x>" % id(self) def _write(self, writer): from colorizer import HtmlColorizer if self.filepath is not None: c = HtmlColorizer(self.filepath, writer) c.colorize() for child in self: child._write(writer) class _HtmlPage(_HtmlElement): def __init__(self, title='', doctype=HTML4_STRICT, styles=[]): self.title = title self.stylesheets = styles self.styleblocks = [] self.doctype = doctype def __repr__(self): return "<_HtmlPage %s at %x>" % (self.title, id(self)) def __call__(self, title='', doctype=HTML4_STRICT, styles=[]): self.__init__( title, doctype, styles ) return self def _write(self, writer): writer.write(self.doctype+'\n') writer.start( "html" ) writer.start( "head" ) writer.element( "title", self.title ) for url in self.stylesheets: writer.element( "link", rel="stylesheet", type="text/css", href="%s" % url ) for style in self.styleblocks: writer.start( "style", type="text/css" ) writer.data( style ) writer.end() writer.end() writer.start("body") for node in self: node._write(writer) writer.end() writer.end() types = { 'text': _HtmlText, 'literal': _HtmlLiteral, 'comment': _HtmlComment, 'include': _HtmlIncludeFile, 'python': _HtmlPythonSource, 'placeholder': _HtmlPlaceholder, 'cheetah': _CheetahTemplate, 'page': _HtmlPage } class _HtmlBuilder(object): '''a factory class for creating _HtmlElements ''' def __getattr__(self, value, *args, **kwargs): try: return types[value]( *args, **kwargs ) except KeyError: return _HtmlElement(value) html = _HtmlBuilder() if __name__ == '__main__': page = html.page('Test Page') page.styleblocks.append( 'p {color:black}\nbody {font-family:courier}' ) page.comment('Begin Header') page.placeholder('HEADER') page.comment('Begin Content') page.placeholder('CONTENT') page.comment('Begin Footer') page.placeholder('FOOTER') header = html.h1( 'header here' ) header.literal('') content = html.div(id='content', css='main') content.h3('Welcome ').placeholder('USER').text('!!') content.hr links = content.ul(css='navbar') links.li.a( 'Home', href='/home.html' ) links.li.a( 'Find Page', href='/FindPage.html' ) links.li.a( 'Recent Changes', href='/RecentChanges.html' ) #content.python("/usr/home/gerard/python/scratch/transform.py") footer = html.div(id='footer') try: from Cheetah.Template import Template footer.cheetah('Cheetah says the date is $DATE') except: footer.text('The date is ').placeholder('DATE') import time ts = time.asctime(time.localtime()) content %= [{ 'USER': 'Arthur Dent' }] footer %= [{ 'DATE': ts }] page %= [{ 'HEADER': header, 'CONTENT': content, 'FOOTER': footer }] print '-' * 80 print 'Using print' print '-' * 80 print page print print '-' * 80 print 'Using write()' print '-' * 80 import sys page.write(sys.stdout) print print '-' * 80 print 'Using tidywrite()' print '-' * 80 page.tidywrite(sys.stdout)