# Copyright (c) 2005-2009 Guido Wesdorp. All rights reserved.
# This software is distributed under the terms of the Templess
# License. See LICENSE.txt for license text.
# E-mail: johnny@johnnydebris.net
""" A very compact, XML based templating system.
It has only 5 different directives, and doesn't allow any logic inside the
templates, instead it expects the application to provide a dict with all
the data (as strings or XML nodes) completely prepared. This makes it quite
restrictive, and harder to seperately work on design and logic (the
template has to very strictly adhere to the format of the dict), but makes
it an environment extremely suitable for developers (since all development
is done from code rather than from the template, and they don't have to
learn a new, quirky templating language) and for projects where HTML and
code development are strictly seperated.
Main features/advantages compared to other templating languages:
* templates are very clean
* it's easy to learn, and relatively simple code
* templates and results are well-formed XML, so processable
* proper unicode support
* proper generative rendering (so suitable for async environments)
Quick example:
>>> from templess.templess import template
>>> t = template('''
...
...
...
... ''')
>>> print t.unicode({'bar': 'bar content'})
bar content
"""
import nanosax
# this here for backward compatibility - will be removed some day...
from util import xmlstring, objectcontext
__appname__ = 'templess'
__version__ = '1.0 unreleased'
__author__ = 'Guido Wesdorp '
__last_modified_date__ = \
'$Date$'
__last_author__ = '$Author$'
__revision__ = '$Revision$'
__footer__ = '%s v%s, (c) %s 2005-2009' % (
__appname__, __version__, __author__)
# tree stuff
class nodebase(object):
""" node base
very specific to Templess, the nodes contain as little functionality
as possible
"""
# __slots__ = ()
class elnode(list, nodebase):
""" XML element
"""
# __slots__ = ('name', 'attrs', 'parent', 'charset')
def __init__(self, name, attrs, parent, charset='UTF-8'):
self.name = name
self.attrs = dict(attrs)
self.parent = parent
self.charset = charset
if parent is not None:
parent.append(self)
def __repr__(self):
return '<%s "%s">' % (self.__class__.__name__, self.name)
def find(self, name):
buffer = [self]
while buffer:
current = buffer.pop(0)
if not isinstance(current, elnode):
continue
buffer = list(current) + buffer
if current.name == name:
yield current
@property
def children(self):
return self.__iter__()
class templessnode(elnode):
""" templess element node
has a special method 'convert' that makes it convert itself to a
normal elnode by processing all templess directives (returns a new
node, doesn't process in-place)
"""
# __slots__ = ('name', 'attrs', 'parent', 'directives', 'charset')
def __init__(self, name, attrs, parent, directives, charset='UTF-8'):
self.name = name
self.attrs = dict(attrs)
self.parent = parent
self.directives = directives
self.charset = charset
if parent is not None:
parent.append(self)
class textnode(nodebase):
""" text element
"""
# __slots__ = ('text', 'parent', 'charset')
def __init__(self, text, parent, charset='UTF-8'):
if not isinstance(text, unicode):
text = unicode(text, charset)
self.text = text
self.parent = parent
self.charset = charset
if parent is not None:
parent.append(self)
def __repr__(self):
reprtext = self.text[10:]
if reprtext and len(reprtext) < len(self.text):
reprtext += '...'
return '<%s "%s">' % (self.__class__.__name__, reprtext)
class cdatanode(textnode):
""" cdata node
"""
class commentnode(textnode):
""" comment node
"""
class template(object):
""" a Templess template
initialize takes 3 arguments:
* 'data' (mandatory) - the data from which to build the tree,
usually XML, 'data' can either be a plain string in UTF-8, or the
charset provided as second argument, a unicode object, or a
file-like object that contains data formatted using the charset
provided as second argument
* charset (optional, defaults to UTF-8) - character set used for
input (if the first argument is a plain string or a file) and
output (if 'render_to_string' is called), and also for converting
plain string items in the context dictionary on conversion
* parse (optional, defaults to parse.parse_from_xml) - a callable
that converts the data provided as first argument, or read from
the file provided as first argument, to a templess node tree
call 'unicode()' to get a unicode string, 'generate()' to get a
generator that yields bits of string, and 'render()' to get a node
"""
def __init__(self, data, charset='UTF-8', parse=None):
if parse is None:
from parse import parse_from_xml as parse
self.charset = charset
if hasattr(data, 'read'):
data = data.read()
if charset != 'UTF-8':
data = unicode(data, charset)
if isinstance(data, unicode):
data = data.encode('UTF-8')
self.tree = parse(data, charset)
# some convenience methods to quickly render to XML
def render_to_string(self, context, charset='UTF-8', html=False):
""" returns a complete string with the rendered template as XML
"""
# not preferred anymore... better use template.unicode()
return self.unicode(context, html=html).encode(charset)
def unicode(self, context, html=False):
""" returns a unicode XML rendering of the template
"""
from convert import xmlserializer
s = xmlserializer(self.tree)
s.convert(context)
return s.unicode(html=html)
def generate(self, context, html=False):
""" returns a generator that generates bits of XML as unicode
"""
from convert import xmlgenerator
s = xmlgenerator(self.tree)
return s.convert_generate(context, html=html)
def convert(self, context):
""" convert the tree to a 'normal' node
processes all templess directives and returns the resulting tree
(a fresh copy, doesn't process in-place)
"""
from convert import convertor
s = convertor(self.tree)
node = s.convert(context)
return node
# old name, I guess it should be removed
render = convert