| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
""" A very compact, XML based templating system. |
|---|
| 7 |
|
|---|
| 8 |
It has only 5 different directives, and doesn't allow any logic inside the |
|---|
| 9 |
templates, instead it expects the application to provide a dict with all |
|---|
| 10 |
the data (as strings or XML nodes) completely prepared. This makes it quite |
|---|
| 11 |
restrictive, and harder to seperately work on design and logic (the |
|---|
| 12 |
template has to very strictly adhere to the format of the dict), but makes |
|---|
| 13 |
it an environment extremely suitable for developers (since all development |
|---|
| 14 |
is done from code rather than from the template, and they don't have to |
|---|
| 15 |
learn a new, quirky templating language) and for projects where HTML and |
|---|
| 16 |
code development are strictly seperated. |
|---|
| 17 |
|
|---|
| 18 |
Main features/advantages compared to other templating languages: |
|---|
| 19 |
|
|---|
| 20 |
* templates are very clean |
|---|
| 21 |
* it's easy to learn, and relatively simple code |
|---|
| 22 |
* templates and results are well-formed XML, so processable |
|---|
| 23 |
* proper unicode support |
|---|
| 24 |
* proper generative rendering (so suitable for async environments) |
|---|
| 25 |
|
|---|
| 26 |
Quick example: |
|---|
| 27 |
|
|---|
| 28 |
>>> from templess.templess import template |
|---|
| 29 |
>>> t = template(''' |
|---|
| 30 |
... <foo xmlns:t="http://johnnydebris.net/xmlns/templess"> |
|---|
| 31 |
... <bar t:content="bar" /> |
|---|
| 32 |
... </foo> |
|---|
| 33 |
... ''') |
|---|
| 34 |
>>> print t.unicode({'bar': 'bar content'}) |
|---|
| 35 |
<foo> |
|---|
| 36 |
<bar>bar content</bar> |
|---|
| 37 |
</foo> |
|---|
| 38 |
""" |
|---|
| 39 |
|
|---|
| 40 |
__appname__ = 'templess' |
|---|
| 41 |
__version__ = '1.0 unreleased' |
|---|
| 42 |
__author__ = 'Guido Wesdorp <johnny@johnnydebris.net>' |
|---|
| 43 |
__last_modified_date__ = \ |
|---|
| 44 |
'$Date$' |
|---|
| 45 |
__last_author__ = '$Author$' |
|---|
| 46 |
__revision__ = '$Revision$' |
|---|
| 47 |
__footer__ = '%s v%s, (c) %s 2005-2009' % ( |
|---|
| 48 |
__appname__, __version__, __author__) |
|---|
| 49 |
|
|---|
| 50 |
|
|---|
| 51 |
class nodebase(object): |
|---|
| 52 |
""" node base |
|---|
| 53 |
|
|---|
| 54 |
very specific to Templess, the nodes contain as little functionality |
|---|
| 55 |
as possible |
|---|
| 56 |
""" |
|---|
| 57 |
|
|---|
| 58 |
|
|---|
| 59 |
|
|---|
| 60 |
class elnode(list, nodebase): |
|---|
| 61 |
""" XML element |
|---|
| 62 |
""" |
|---|
| 63 |
|
|---|
| 64 |
|
|---|
| 65 |
def __init__(self, name, attrs, parent, charset='UTF-8'): |
|---|
| 66 |
self.name = name |
|---|
| 67 |
self.attrs = dict(attrs) |
|---|
| 68 |
self.parent = parent |
|---|
| 69 |
self.charset = charset |
|---|
| 70 |
if parent is not None: |
|---|
| 71 |
parent.append(self) |
|---|
| 72 |
|
|---|
| 73 |
def __repr__(self): |
|---|
| 74 |
return '<%s "%s">' % (self.__class__.__name__, self.name) |
|---|
| 75 |
|
|---|
| 76 |
def find(self, name): |
|---|
| 77 |
buffer = [self] |
|---|
| 78 |
while buffer: |
|---|
| 79 |
current = buffer.pop(0) |
|---|
| 80 |
if not isinstance(current, elnode): |
|---|
| 81 |
continue |
|---|
| 82 |
buffer = list(current) + buffer |
|---|
| 83 |
if current.name == name: |
|---|
| 84 |
yield current |
|---|
| 85 |
|
|---|
| 86 |
@property |
|---|
| 87 |
def children(self): |
|---|
| 88 |
return self.__iter__() |
|---|
| 89 |
|
|---|
| 90 |
|
|---|
| 91 |
class templessnode(elnode): |
|---|
| 92 |
""" templess element node |
|---|
| 93 |
|
|---|
| 94 |
has a special method 'convert' that makes it convert itself to a |
|---|
| 95 |
normal elnode by processing all templess directives (returns a new |
|---|
| 96 |
node, doesn't process in-place) |
|---|
| 97 |
""" |
|---|
| 98 |
|
|---|
| 99 |
|
|---|
| 100 |
|
|---|
| 101 |
def __init__(self, name, attrs, parent, directives, charset='UTF-8'): |
|---|
| 102 |
self.name = name |
|---|
| 103 |
self.attrs = dict(attrs) |
|---|
| 104 |
self.parent = parent |
|---|
| 105 |
self.directives = directives |
|---|
| 106 |
self.charset = charset |
|---|
| 107 |
if parent is not None: |
|---|
| 108 |
parent.append(self) |
|---|
| 109 |
|
|---|
| 110 |
|
|---|
| 111 |
class textnode(nodebase): |
|---|
| 112 |
""" text element |
|---|
| 113 |
""" |
|---|
| 114 |
|
|---|
| 115 |
|
|---|
| 116 |
|
|---|
| 117 |
def __init__(self, text, parent, charset='UTF-8'): |
|---|
| 118 |
if not isinstance(text, unicode): |
|---|
| 119 |
text = unicode(text, charset) |
|---|
| 120 |
self.text = text |
|---|
| 121 |
self.parent = parent |
|---|
| 122 |
self.charset = charset |
|---|
| 123 |
if parent is not None: |
|---|
| 124 |
parent.append(self) |
|---|
| 125 |
|
|---|
| 126 |
def __repr__(self): |
|---|
| 127 |
reprtext = self.text[10:] |
|---|
| 128 |
if reprtext and len(reprtext) < len(self.text): |
|---|
| 129 |
reprtext += '...' |
|---|
| 130 |
return '<%s "%s">' % (self.__class__.__name__, reprtext) |
|---|
| 131 |
|
|---|
| 132 |
|
|---|
| 133 |
class cdatanode(textnode): |
|---|
| 134 |
""" cdata node |
|---|
| 135 |
""" |
|---|
| 136 |
|
|---|
| 137 |
|
|---|
| 138 |
class commentnode(textnode): |
|---|
| 139 |
""" comment node |
|---|
| 140 |
""" |
|---|
| 141 |
|
|---|
| 142 |
|
|---|
| 143 |
class template(object): |
|---|
| 144 |
""" a Templess template |
|---|
| 145 |
|
|---|
| 146 |
initialize takes 3 arguments: |
|---|
| 147 |
|
|---|
| 148 |
* 'data' (mandatory) - the data from which to build the tree, |
|---|
| 149 |
usually XML, 'data' can either be a plain string in UTF-8, or the |
|---|
| 150 |
charset provided as second argument, a unicode object, or a |
|---|
| 151 |
file-like object that contains data formatted using the charset |
|---|
| 152 |
provided as second argument |
|---|
| 153 |
|
|---|
| 154 |
* charset (optional, defaults to UTF-8) - character set used for |
|---|
| 155 |
input (if the first argument is a plain string or a file) and |
|---|
| 156 |
output (if 'render_to_string' is called), and also for converting |
|---|
| 157 |
plain string items in the context dictionary on conversion |
|---|
| 158 |
|
|---|
| 159 |
* parse (optional, defaults to parse.parse_from_xml) - a callable |
|---|
| 160 |
that converts the data provided as first argument, or read from |
|---|
| 161 |
the file provided as first argument, to a templess node tree |
|---|
| 162 |
|
|---|
| 163 |
call 'unicode()' to get a unicode string, 'generate()' to get a |
|---|
| 164 |
generator that yields bits of string, and 'render()' to get a node |
|---|
| 165 |
""" |
|---|
| 166 |
def __init__(self, data, charset='UTF-8', parse=None): |
|---|
| 167 |
if parse is None: |
|---|
| 168 |
from parse import parse_from_xml as parse |
|---|
| 169 |
self.charset = charset |
|---|
| 170 |
if hasattr(data, 'read'): |
|---|
| 171 |
data = data.read() |
|---|
| 172 |
if charset != 'UTF-8': |
|---|
| 173 |
data = unicode(data, charset) |
|---|
| 174 |
if isinstance(data, unicode): |
|---|
| 175 |
data = data.encode('UTF-8') |
|---|
| 176 |
self.tree = parse(data, charset) |
|---|
| 177 |
|
|---|
| 178 |
|
|---|
| 179 |
def render_to_string(self, context, charset='UTF-8', html=False): |
|---|
| 180 |
""" returns a complete string with the rendered template as XML |
|---|
| 181 |
""" |
|---|
| 182 |
|
|---|
| 183 |
return self.unicode(context, html=html).encode(charset) |
|---|
| 184 |
|
|---|
| 185 |
def unicode(self, context, html=False): |
|---|
| 186 |
""" returns a unicode XML rendering of the template |
|---|
| 187 |
""" |
|---|
| 188 |
from convert import xmlserializer |
|---|
| 189 |
s = xmlserializer(self.tree) |
|---|
| 190 |
s.convert(context) |
|---|
| 191 |
return s.unicode(html=html) |
|---|
| 192 |
|
|---|
| 193 |
def generate(self, context, html=False): |
|---|
| 194 |
""" returns a generator that generates bits of XML as unicode |
|---|
| 195 |
""" |
|---|
| 196 |
from convert import xmlgenerator |
|---|
| 197 |
s = xmlgenerator(self.tree) |
|---|
| 198 |
return s.convert_generate(context, html=html) |
|---|
| 199 |
|
|---|
| 200 |
def convert(self, context): |
|---|
| 201 |
""" convert the tree to a 'normal' node |
|---|
| 202 |
|
|---|
| 203 |
processes all templess directives and returns the resulting tree |
|---|
| 204 |
(a fresh copy, doesn't process in-place) |
|---|
| 205 |
""" |
|---|
| 206 |
from convert import convertor |
|---|
| 207 |
s = convertor(self.tree) |
|---|
| 208 |
node = s.convert(context) |
|---|
| 209 |
return node |
|---|
| 210 |
|
|---|
| 211 |
render = convert |
|---|