Project

General

Profile

TemplateParser » History » Version 56

Erwin Hager, 2019-12-31 11:16

1 1 Elmer de Looff
h1. TemplateParser
2 25 Elmer de Looff
3 25 Elmer de Looff
{{>toc}}
4 1 Elmer de Looff
5 1 Elmer de Looff
The µWeb TemplateParser is a in-house developed templating engine that provides tag replacement, tag-functions and template control functions. This document will describe the following:
6 38 Elmer de Looff
* *[[TemplateParser#using|Using TemplateParser]]* inside a µWeb PageMaker
7 21 Elmer de Looff
* The *[[TemplateParser#template|Template class]]*, used to parse the templating language
8 21 Elmer de Looff
* The *[[TemplateParser#parser|Parser class]]*, which provides template loading and caching
9 21 Elmer de Looff
* *[[TemplateParser#syntax|Template syntax]]*, an overview of the language's constructs and behaviors
10 1 Elmer de Looff
11 1 Elmer de Looff
First though, to help with understanding the TemplateParser, a minimal size template document:
12 1 Elmer de Looff
13 1 Elmer de Looff
<pre><code class="html">
14 1 Elmer de Looff
Hello [title] [name]
15 1 Elmer de Looff
</code></pre>
16 4 Elmer de Looff
17 1 Elmer de Looff
The above document contains two simple template tags. These tags are delimited by square brackets, and they will be replaced by the named argument provided during parsing. If this name is not present, then the literal presentation of the tag will remain in the output.
18 1 Elmer de Looff
19 38 Elmer de Looff
h1(#using). Using TemplateParser inside µWeb
20 38 Elmer de Looff
21 48 Elmer de Looff
Within the default µWeb [[PageMaker]], there is a @parser@ property, which provides a [[TemplateParser#parser|Parser]] object. The class constant @TEMPLATE_DIR@ provides the template search directory. The default template directory is @'templates'@. 
22 48 Elmer de Looff
23 48 Elmer de Looff
*N.B.:* This path is relative to the file that contains the PageMaker class.
24 38 Elmer de Looff
25 38 Elmer de Looff
An example of TemplateParser to create a complete response:
26 38 Elmer de Looff
<pre><code class="python">
27 38 Elmer de Looff
import uweb
28 38 Elmer de Looff
import time
29 38 Elmer de Looff
30 38 Elmer de Looff
class PageMaker(uweb.PageMaker):
31 38 Elmer de Looff
  def VersionPage(self):
32 38 Elmer de Looff
    return self.parser.Parse(
33 38 Elmer de Looff
      'version.utp', year=time.strftime('%Y'), version=uweb.__version__)
34 38 Elmer de Looff
</code></pre>
35 38 Elmer de Looff
36 38 Elmer de Looff
The example template for the above file could look something like this:
37 38 Elmer de Looff
38 38 Elmer de Looff
<pre><code class="html">
39 38 Elmer de Looff
<!DOCTYPE html>
40 38 Elmer de Looff
<html>
41 38 Elmer de Looff
  <head>
42 38 Elmer de Looff
    <title>µWeb version info</title>
43 38 Elmer de Looff
  </head>
44 38 Elmer de Looff
  <body>
45 38 Elmer de Looff
    <p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
46 38 Elmer de Looff
  </body>
47 38 Elmer de Looff
</html>
48 38 Elmer de Looff
</code></pre>
49 38 Elmer de Looff
50 38 Elmer de Looff
And would result in the following output:
51 38 Elmer de Looff
52 38 Elmer de Looff
<pre><code class="html">
53 38 Elmer de Looff
<!DOCTYPE html>
54 38 Elmer de Looff
<html>
55 38 Elmer de Looff
  <head>
56 38 Elmer de Looff
    <title>µWeb version info</title>
57 38 Elmer de Looff
  </head>
58 38 Elmer de Looff
  <body>
59 38 Elmer de Looff
    <p>µWeb version 0.12 - Copyright 2010-2012 Underdark</p>
60 38 Elmer de Looff
  </body>
61 38 Elmer de Looff
</html>
62 38 Elmer de Looff
</code></pre>
63 38 Elmer de Looff
64 38 Elmer de Looff
With these initial small demonstrations behind us, let's explore the @TemplateParser@ further
65 38 Elmer de Looff
66 5 Elmer de Looff
h1(#template). Template class
67 4 Elmer de Looff
68 4 Elmer de Looff
The @Template@ class provides the interface for pre-parsing templates, loading them from files and parsing single templates to completion. During pre-parsing, constructs such as loops and conditional statements are converted to @TemplateLoop@ and @TemplateConditional@ objects, and their scopes nested appropriately in the @Template@. Tags are replaced by @TemplateTag@ instances, and text is captured in @TemplateText@. All of these provide @Parse@ methods, which together result in the combined parsed template output.
69 4 Elmer de Looff
70 4 Elmer de Looff
h2. Creating a template
71 4 Elmer de Looff
72 4 Elmer de Looff
A template is created simple by providing a string input to the @Template@'s constructor. This will return a valid Template instance (or raise an error if there is a problem with the [[TemplateParser#syntax|syntax]]:
73 4 Elmer de Looff
74 4 Elmer de Looff
<pre><code class="python">
75 21 Elmer de Looff
>>> import templateparser
76 4 Elmer de Looff
>>> template = templateparser.Template('Hello [title] [name]')
77 4 Elmer de Looff
>>> template
78 4 Elmer de Looff
Template([TemplateText('Hello '), TemplateTag('[title]'), TemplateText(' '), TemplateTag('[name]')])
79 4 Elmer de Looff
</code></pre>
80 4 Elmer de Looff
81 4 Elmer de Looff
Above can be seen the various parts of the template, which will be combined to output once parsed.
82 4 Elmer de Looff
83 4 Elmer de Looff
h2. Loading a template from file
84 4 Elmer de Looff
85 4 Elmer de Looff
The @Template@ class provides a @classmethod@ called @FromFile@, which loads the template at the path.
86 4 Elmer de Looff
87 4 Elmer de Looff
Loading a template named @example.utp@ from the current working directory:
88 4 Elmer de Looff
89 4 Elmer de Looff
<pre><code class="python">
90 21 Elmer de Looff
>>> import templateparser
91 4 Elmer de Looff
>>> template = templateparser.Template.FromFile('example.utp')
92 4 Elmer de Looff
>>> template
93 4 Elmer de Looff
Template([TemplateText('Hello '), TemplateTag('[title]'), TemplateText(' '), TemplateTag('[name]')])
94 4 Elmer de Looff
</code></pre>
95 4 Elmer de Looff
96 5 Elmer de Looff
h2. Parsing a template
97 4 Elmer de Looff
98 4 Elmer de Looff
Parsing a template can be done by calling the @Template@'s @Parse@ method. The keyword arguments provided to this call will from the replacement mapping for the template. In the following example, we will provide one such keyword, and leave the other undefined to show the (basic) behavior of the @Template.Parse@ method.
99 4 Elmer de Looff
100 4 Elmer de Looff
<pre><code class="python">
101 21 Elmer de Looff
>>> import templateparser
102 4 Elmer de Looff
>>> template = templateparser.Template('Hello [title] [name]')
103 8 Elmer de Looff
>>> template.Parse(title='sir')
104 8 Elmer de Looff
'Hello sir [name]'
105 4 Elmer de Looff
</code></pre>
106 1 Elmer de Looff
107 1 Elmer de Looff
h1(#parser). Parser class
108 6 Elmer de Looff
109 1 Elmer de Looff
The @Parser@ class provides simple management of multiple @Template@ objects. It is mainly used to load templates from disk. When initiating a @Parser@, the first argument provides the search path from where templates should be loaded (the default is the current working directory). An optional second argument can be provided to preload the template cache: a mapping of names and @Template@ objects.
110 1 Elmer de Looff
111 8 Elmer de Looff
h2. Loading templates
112 8 Elmer de Looff
113 6 Elmer de Looff
Creating a parser object, and loading the 'example.utp' template from the 'templates' directory works like this:
114 6 Elmer de Looff
115 6 Elmer de Looff
<pre><code class="python">
116 21 Elmer de Looff
>>> import templateparser
117 7 Elmer de Looff
>>> # This sets the 'templates' directory as the search path for AddTemplate
118 7 Elmer de Looff
>>> parser = templateparser.Parser('templates')
119 7 Elmer de Looff
>>> # Loads the 'templates/example.utp' and stores it as 'example.utp':
120 26 Elmer de Looff
>>> parser.AddTemplate('example.utp')
121 27 Elmer de Looff
>>> parser.Parse('example.utp', title='mister', name='Bob Dobalina')
122 1 Elmer de Looff
'Hello mister Bob Dobalina'
123 6 Elmer de Looff
</code></pre>
124 1 Elmer de Looff
125 29 Elmer de Looff
The @AddTemplate@ method takes a second optional argument, which allows us to give the template a different name in the cache:
126 29 Elmer de Looff
127 29 Elmer de Looff
<pre><code class="python">
128 29 Elmer de Looff
>>> parser = templateparser.Parser('templates')
129 30 Elmer de Looff
>>> parser.AddTemplate('example.utp', name='greeting')
130 29 Elmer de Looff
>>> parser.Parse('greeting', title='mister', name='Bob Dobalina')
131 29 Elmer de Looff
'Hello mister Bob Dobalina'
132 29 Elmer de Looff
</code></pre>
133 29 Elmer de Looff
134 29 Elmer de Looff
As you can see, the name of the template in the cache is not necessarily the same as the one on disk. Often though, this is not necessary to change, so @AddTemplate@ need only be called with one argument. Or not at all, as the following section will show.
135 1 Elmer de Looff
136 47 Elmer de Looff
h2. Template cache, reloading, and auto-loading
137 8 Elmer de Looff
138 47 Elmer de Looff
The @Parser@ at heart is a dictionary that maps the names of templates to @Template@ instances. When they are loaded from disk they are pre-parsed, checked and cached. Subsequent uses of the same template will therefore be faster, as the initial parsing will not have to be repeated.
139 1 Elmer de Looff
140 47 Elmer de Looff
Templates loaded from a file keep track of the modification time (@mtime@) of the originating template file. Upon each parse, the source file is checked, and if the modification time is newer than when it was loaded, the template is read from disk and then parsed. This way, templates are never out of date.
141 47 Elmer de Looff
142 47 Elmer de Looff
Whenever the @Parser@ is requested to @Parse@ or return a template that it doesn't have loaded already, the auto-loading mechanism triggers. This searches for the given template name in the configured template directory. If a filename matches (exactly), it is automatically loaded and used to fulfill the Parse request. If no matching file is found, an error is triggered.
143 47 Elmer de Looff
144 47 Elmer de Looff
Below follows an example of auto-loading:
145 47 Elmer de Looff
146 8 Elmer de Looff
<pre><code class="python">
147 1 Elmer de Looff
>>> import templateparser
148 8 Elmer de Looff
>>> parser = templateparser.Parser('templates')
149 6 Elmer de Looff
>>> 'example.utp' in parser
150 47 Elmer de Looff
False       # Since we haven't loaded it, the template is not in the parser storage
151 10 Elmer de Looff
>>> parser
152 10 Elmer de Looff
Parser({})  # The parser is empty (has no cached templates)
153 10 Elmer de Looff
</code></pre>
154 1 Elmer de Looff
155 10 Elmer de Looff
Attempting to parse a template that doesn't exist in the parser cache triggers an automatic load:
156 10 Elmer de Looff
157 31 Jan Klopper
<pre><code class="python">
158 47 Elmer de Looff
>>> parser.Parse('example.utp', title='mister', name='Bob Dobalina')
159 10 Elmer de Looff
'Hello mister Bob Dobalina'
160 10 Elmer de Looff
>>> 'example.utp' in parser
161 10 Elmer de Looff
True
162 10 Elmer de Looff
>>> parser
163 10 Elmer de Looff
Parser({'example.utp': Template([TemplateText('Hello '), TemplateTag('[title]'),
164 10 Elmer de Looff
                                 TemplateText(' '), TemplateTag('[name]')])})
165 10 Elmer de Looff
</code></pre>
166 10 Elmer de Looff
167 1 Elmer de Looff
If these cannot be found, @TemplateReadError@ is raised:
168 10 Elmer de Looff
169 10 Elmer de Looff
<pre><code class="python">
170 10 Elmer de Looff
>>> import templateparser
171 10 Elmer de Looff
>>> parser = templateparser.Parser('templates')
172 47 Elmer de Looff
>>> parser.Parse('bad_template.utp', failure='imminent')
173 10 Elmer de Looff
Traceback (most recent call last):
174 10 Elmer de Looff
  File "<stdin>", line 1, in <module>
175 1 Elmer de Looff
  File "/var/lib/underdark/libs/uweb/templateparser.py", line 147, in __getitem__
176 37 Elmer de Looff
    self.AddTemplate(template)
177 37 Elmer de Looff
  File "/var/lib/underdark/libs/uweb/templateparser.py", line 171, in AddTemplate
178 37 Elmer de Looff
    raise TemplateReadError('Could not load template %r' % template_path)
179 37 Elmer de Looff
underdark.libs.uweb.templateparser.TemplateReadError: Could not load template 'templates/bad_template.utp'
180 37 Elmer de Looff
</code></pre>
181 37 Elmer de Looff
182 37 Elmer de Looff
h2. @Parse@ and @ParseString@ methods
183 37 Elmer de Looff
184 37 Elmer de Looff
For convencience and consistency, the @Parser@ comes with two handy methods to provide parsing of @Template@ objects, one from its cache, one from raw template strings. It is recommended to use these over the previously shown direct key-based access:
185 37 Elmer de Looff
186 37 Elmer de Looff
<pre><code class="python">
187 37 Elmer de Looff
>>> import templateparser
188 37 Elmer de Looff
>>> parser = templateparser.Parser('templates')
189 37 Elmer de Looff
>>> parser.Parse('example.utp', title='mister', name='Bob Dobalina')
190 10 Elmer de Looff
'Hello mister Bob Dobalina'
191 10 Elmer de Looff
>>> parser.ParseString('Hello [title] [name]', title='mister', name='Bob Dobalina')
192 10 Elmer de Looff
'Hello mister Bob Dobalina'</code></pre>
193 1 Elmer de Looff
194 5 Elmer de Looff
h1(#syntax). Templating language syntax
195 11 Elmer de Looff
196 11 Elmer de Looff
The templating syntax is relatively limited, but with the limited syntax it provides a flexible and rich system to create templates. Covered in these examples are:
197 11 Elmer de Looff
* Simple tags (used in various examples above)
198 11 Elmer de Looff
* Tag indexing
199 11 Elmer de Looff
* Tag functions
200 11 Elmer de Looff
* Template language constructs
201 11 Elmer de Looff
202 11 Elmer de Looff
All examples will consist of three parts:
203 11 Elmer de Looff
# The example template
204 11 Elmer de Looff
# The python invocation string (the template will be named 'example.utp')
205 11 Elmer de Looff
# The resulting output (as source, not as parsed HTML)
206 11 Elmer de Looff
207 11 Elmer de Looff
h2. Simple tags
208 11 Elmer de Looff
209 11 Elmer de Looff
This is an example for the most basic form of template tags. The tag is enclosed by square brackets as such: @[tag]@. Tags that match a provided argument to the Parse call get replaced. If there is no argument that matches the tag name, it is returned in the output verbatim. This is also demonstrated in the below example
210 11 Elmer de Looff
211 11 Elmer de Looff
The example below is a repeat of the example how to use TemplateParser inside µWeb, and shows the template result:
212 11 Elmer de Looff
213 11 Elmer de Looff
<pre><code class="html">
214 11 Elmer de Looff
<!DOCTYPE html>
215 11 Elmer de Looff
<html>
216 11 Elmer de Looff
  <head>
217 11 Elmer de Looff
    <title>µWeb version info</title>
218 11 Elmer de Looff
  </head>
219 11 Elmer de Looff
  <body>
220 11 Elmer de Looff
    <p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
221 11 Elmer de Looff
    <p>
222 11 Elmer de Looff
      This [paragraph] is not replaced because there is no
223 11 Elmer de Looff
      paragraph argument provided to the parser.
224 11 Elmer de Looff
    </p>
225 11 Elmer de Looff
  </body>
226 11 Elmer de Looff
</html>
227 11 Elmer de Looff
</code></pre>
228 11 Elmer de Looff
229 11 Elmer de Looff
<pre><code class="python">
230 11 Elmer de Looff
>>> parser.Parse('version.utp', year=time.strftime('%Y'), version=uweb.__version__)
231 11 Elmer de Looff
</code></pre>
232 11 Elmer de Looff
233 11 Elmer de Looff
<pre><code class="html">
234 11 Elmer de Looff
<!DOCTYPE html>
235 11 Elmer de Looff
<html>
236 11 Elmer de Looff
  <head>
237 11 Elmer de Looff
    <title>µWeb version info</title>
238 11 Elmer de Looff
  </head>
239 11 Elmer de Looff
  <body>
240 11 Elmer de Looff
    <p>µWeb version 0.11 - Copyright 2010-212 Underdark</p>
241 11 Elmer de Looff
    <p>
242 11 Elmer de Looff
      This [paragraph] is not replaced because there is no
243 11 Elmer de Looff
      paragraph argument provided to the parser.
244 11 Elmer de Looff
    </p>
245 11 Elmer de Looff
  </body>
246 11 Elmer de Looff
</html>
247 11 Elmer de Looff
</code></pre>
248 11 Elmer de Looff
249 45 Elmer de Looff
h3. Valid tag name characters
250 42 Elmer de Looff
251 42 Elmer de Looff
Tag names are created from the same characters as valid Python variable names. This means they can contain upper and lower case letters, numbers and underscores. In regex terms, a tag should match @\w+@.
252 42 Elmer de Looff
253 42 Elmer de Looff
*N.B.:* Some names are illegal in Python as variable names but valid as tag names (tag names may start with a number). You can use these and pass the replacements as a dictionary using ** if you have a need for it.
254 42 Elmer de Looff
255 11 Elmer de Looff
h2. Tag indexing
256 11 Elmer de Looff
257 32 Elmer de Looff
In addition to simple (re)placement of strings using the @TemplateParser@, you can also provide it with a @list@, @dictionary@, or other indexable object, and from it, fetch various @indices@, @keys@ or @attributes@. The separation character between the _tagname_ and the _index_ is the _colon_ (":"):
258 32 Elmer de Looff
259 32 Elmer de Looff
260 32 Elmer de Looff
h3. List/tuple index addressing
261 32 Elmer de Looff
262 34 Elmer de Looff
This works for lists and tuples, but also for any other object that supports indexing. That is, every object that accepts integers on its @__getitem__@ method.
263 34 Elmer de Looff
264 32 Elmer de Looff
<pre><code class="html">
265 32 Elmer de Looff
This is [var:0] [var:1].
266 32 Elmer de Looff
</code></pre>
267 32 Elmer de Looff
268 32 Elmer de Looff
<pre><code class="python">
269 32 Elmer de Looff
>>> parser.Parse('message.utp', var=('delicious', 'spam'))
270 32 Elmer de Looff
</code></pre>
271 32 Elmer de Looff
272 32 Elmer de Looff
<pre><code class="html">
273 32 Elmer de Looff
This is delicious spam.
274 32 Elmer de Looff
</code></pre>
275 32 Elmer de Looff
276 33 Elmer de Looff
h3. Dictionary key addressing
277 32 Elmer de Looff
278 34 Elmer de Looff
This works for dictionaries, but also for any other object that behaves like a key-value mapping. That is, every object that accepts strings on its @__getitem__@ method.
279 34 Elmer de Looff
280 32 Elmer de Looff
<pre><code class="html">
281 32 Elmer de Looff
This is [var:adjective] [var:noun].
282 32 Elmer de Looff
</code></pre>
283 32 Elmer de Looff
284 32 Elmer de Looff
<pre><code class="python">
285 32 Elmer de Looff
>>> parser.Parse('message.utp', var={'adjective': 'delicious', 'noun': 'spam'})
286 32 Elmer de Looff
</code></pre>
287 32 Elmer de Looff
288 32 Elmer de Looff
<pre><code class="html">
289 32 Elmer de Looff
This is delicious spam.
290 32 Elmer de Looff
</code></pre>
291 32 Elmer de Looff
292 33 Elmer de Looff
h3. Attribute name addressing
293 32 Elmer de Looff
294 34 Elmer de Looff
This works for any object that has named attributes. If the attribute is a method, it will *not* be executed automatically, the return value will simply be the (un)bound method itself.
295 34 Elmer de Looff
296 32 Elmer de Looff
<pre><code class="html">
297 32 Elmer de Looff
This is [var:adjective] [var:noun].
298 32 Elmer de Looff
</code></pre>
299 32 Elmer de Looff
300 32 Elmer de Looff
<pre><code class="python">
301 32 Elmer de Looff
>>> class Struct(object):
302 32 Elmer de Looff
...   pass
303 32 Elmer de Looff
...
304 32 Elmer de Looff
>>> var = Struct()
305 32 Elmer de Looff
>>> var.adjective = 'delicious'
306 32 Elmer de Looff
>>> var.noun = 'spam'
307 32 Elmer de Looff
>>> parser.Parse('message.utp', var=var)
308 32 Elmer de Looff
</code></pre>
309 32 Elmer de Looff
310 32 Elmer de Looff
<pre><code class="html">
311 32 Elmer de Looff
This is delicious spam.
312 32 Elmer de Looff
</code></pre>
313 32 Elmer de Looff
314 33 Elmer de Looff
h3. Lookup order
315 32 Elmer de Looff
316 32 Elmer de Looff
For objects and constructs that provide multiple ways of looking up information, the lookup order can be very important. For any of the first three steps, if they are successful, the retrieved value is returned, and no further attempts are made:
317 32 Elmer de Looff
318 32 Elmer de Looff
# If the @needle@ is parseable as integer, it will first be used as an index. This will also work for mappings with numeric keys;
319 32 Elmer de Looff
# If the above fails, the @needle@ is assumed to be a string-like mapping key, and this is attempted
320 32 Elmer de Looff
# If the above fails, the @needle@ is used as an attribute name;
321 32 Elmer de Looff
# If all of the above fail, *@TemplateKeyError@* is raised, as the @needle@ could not be found on the object.
322 34 Elmer de Looff
323 34 Elmer de Looff
h3. Nested indexes
324 34 Elmer de Looff
325 34 Elmer de Looff
There may be cases where the value you need is not at the top-level index of an object. This is not a problem, since TemplateParser supports arbitrary-depth nested structures in its index-lookup:
326 34 Elmer de Looff
327 34 Elmer de Looff
<pre><code class="html">
328 34 Elmer de Looff
This is a variable from [some:levels:down:1].
329 34 Elmer de Looff
</code></pre>
330 34 Elmer de Looff
331 34 Elmer de Looff
<pre><code class="python">
332 34 Elmer de Looff
>>> class Struct(object):
333 34 Elmer de Looff
...   pass
334 34 Elmer de Looff
...
335 34 Elmer de Looff
>>> var = Struct()
336 34 Elmer de Looff
>>> var.levels = {'down': ('the sky', 'the depths')}
337 34 Elmer de Looff
>>> parser.Parse('message.utp', some=var)
338 34 Elmer de Looff
</code></pre>
339 34 Elmer de Looff
340 34 Elmer de Looff
<pre><code class="html">
341 34 Elmer de Looff
This is a variable from the depths.
342 34 Elmer de Looff
</code></pre>
343 16 Elmer de Looff
344 43 Elmer de Looff
h3. Valid index characters
345 43 Elmer de Looff
346 43 Elmer de Looff
Indexes may be constructed from upper and lower case letters, numbers, underscores and dashes. There are no restrictions on first character, only a minimum length of one. Regex-wise, they need to match @[\w-]+@
347 43 Elmer de Looff
348 51 Jan Klopper
h3. Checking for presence
349 51 Jan Klopper
350 51 Jan Klopper
The templateparser will raise an error when it stumbles upon an indexError or keyError when resolving requested tags. To avoid this the user can check wether or not all variables exists by using the ifpresent tag.
351 51 Jan Klopper
352 51 Jan Klopper
<pre><code class="html">
353 51 Jan Klopper
{{ ifpresent [uweb:version]}}
354 51 Jan Klopper
  <p>µWeb version [version]</p>
355 51 Jan Klopper
{ {else }}
356 51 Jan Klopper
  <p>µWeb unkown version</p>
357 51 Jan Klopper
{{ endif }}
358 51 Jan Klopper
</code></pre>
359 51 Jan Klopper
360 11 Elmer de Looff
h2. Tag functions
361 11 Elmer de Looff
362 35 Elmer de Looff
Once you arrive at the tag/value you want, there's often some things that need to happen before the resulting template is sent to the requesting client (browser). HTML escaping is an obvious one, but url quoting of single arguments may also be helpful, as well as uppercasing, printing the length of a list (instead of the raw list) and various other uses.
363 15 Elmer de Looff
364 21 Elmer de Looff
h3. Default html escaping
365 1 Elmer de Looff
366 36 Elmer de Looff
Using a tag function is a fairly straightforward process, just add the name of the function after the tagname, separated by a pipe ( | ):
367 35 Elmer de Looff
368 36 Elmer de Looff
<pre><code class="html">
369 36 Elmer de Looff
And he said: [message|html]
370 36 Elmer de Looff
</code></pre>
371 36 Elmer de Looff
372 36 Elmer de Looff
<pre><code class="python">
373 36 Elmer de Looff
>>> parser.Parse('message.utp', message='"Hello"')
374 36 Elmer de Looff
</code></pre>
375 36 Elmer de Looff
376 36 Elmer de Looff
<pre><code class="html">
377 36 Elmer de Looff
And he said: &quot;Hello&quot;
378 36 Elmer de Looff
</code></pre>
379 36 Elmer de Looff
380 39 Elmer de Looff
Using the *html* tag function makes the tag value safe for printing in an HTML document. Because we believe this is _really_ important, the html escaping tag function is always applied when no other tag function is applied:
381 36 Elmer de Looff
382 36 Elmer de Looff
<pre><code class="html">
383 36 Elmer de Looff
And he said: [message]
384 36 Elmer de Looff
</code></pre>
385 36 Elmer de Looff
386 36 Elmer de Looff
<pre><code class="python">
387 36 Elmer de Looff
>>> parser.Parse('message.utp', message='"Hello"')
388 36 Elmer de Looff
</code></pre>
389 36 Elmer de Looff
390 36 Elmer de Looff
<pre><code class="html">
391 36 Elmer de Looff
And he said: &quot;Hello&quot;
392 36 Elmer de Looff
</code></pre>
393 36 Elmer de Looff
394 36 Elmer de Looff
Only when you use another tag function, or specifically tell @TemplateParser@ to push the _raw_ tag value into the output, are the quotes allowed through unchanged:
395 36 Elmer de Looff
396 36 Elmer de Looff
<pre><code class="html">
397 36 Elmer de Looff
And he said: [message|raw]
398 36 Elmer de Looff
</code></pre>
399 36 Elmer de Looff
400 36 Elmer de Looff
<pre><code class="python">
401 36 Elmer de Looff
>>> parser.Parse('message.utp', message='"Hello"')
402 36 Elmer de Looff
</code></pre>
403 36 Elmer de Looff
404 36 Elmer de Looff
<pre><code class="html">
405 36 Elmer de Looff
And he said: "Hello"
406 36 Elmer de Looff
</code></pre>
407 36 Elmer de Looff
408 35 Elmer de Looff
h3. Predefined tag functions
409 1 Elmer de Looff
410 36 Elmer de Looff
* *html* &ndash; This tag function escapes content to be safe for inclusion in HTML pages. This means that the ampersand ( & ), single and double quotes ( '  &nbsp;and&nbsp; " ) and the pointy brackets ( < &nbsp;and&nbsp; > ) are converted to their respective "character entity references":http://en.wikipedia.org/wiki/Character_entity_reference
411 1 Elmer de Looff
* _default_ &ndash; This is the tag function that will be executed when no other tag functions have been specified for a tag. By default, this will do the same as the *html* tag function. This can be adjusted by assigning another tag function to this name.
412 35 Elmer de Looff
* *raw* &ndash; This tag function passes the tag through without change. This is the function to use when you have no tag function to apply, but do not want the tag to be HTML-escaped.
413 36 Elmer de Looff
* *url* &ndash; This tag function prepares the tag for use in URLs. Space are converted to plus-signs ( + ), and other characters that are considered unsafe for URLs are converted to "percent-notation":http://en.wikipedia.org/wiki/Percent-encoding.
414 50 Jan Klopper
* *values* &ndash; This tag function can be used in conjunction with [[TemplateParser#TemplateLoop|TemplateLoops]] to provide the values instead of the keys when iterating over dictionaries.
415 1 Elmer de Looff
416 35 Elmer de Looff
h3. Adding custom functions
417 35 Elmer de Looff
418 35 Elmer de Looff
Custom methods can be added to a @Parser@ object using the method @RegisterFunction@. This takes a name, and a single-argument function. When this function is encountered in a tag, it will be given the current tag value, and its result will be output to the template, or passed into the next function:
419 35 Elmer de Looff
420 35 Elmer de Looff
<pre><code class="python">
421 35 Elmer de Looff
>>> from uweb import templateparser
422 35 Elmer de Looff
>>> parser = templateparser.Parser()
423 35 Elmer de Looff
>>> parser.RegisterFunction('len', len)
424 35 Elmer de Looff
>>> template = 'The number of people in this group: [people|len].'
425 35 Elmer de Looff
>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
426 35 Elmer de Looff
'The number of people in this group: 4.'
427 35 Elmer de Looff
</code></pre>
428 35 Elmer de Looff
429 40 Elmer de Looff
*N.B.:* Using custom functions (or in fact any function other than _html_ or no function) will suppress HTML escaping. If your content is still user-driven, or not otherwise made safe for output, *it is strongly recommended you apply html escaping*. This can be achieved by chaining functions, as explained below.
430 40 Elmer de Looff
431 41 Elmer de Looff
h3. Function chaining
432 35 Elmer de Looff
433 35 Elmer de Looff
Multiple function calls can be chained after one another. The functions are processed left to right, and the result of each function is passed into the next, without any intermediate editing or changes:
434 35 Elmer de Looff
435 49 Elmer de Looff
Setting up the parser and registering our tag function:
436 35 Elmer de Looff
<pre><code class="python">
437 35 Elmer de Looff
>>> from uweb import templateparser
438 35 Elmer de Looff
>>> parser = templateparser.Parser()
439 35 Elmer de Looff
>>> parser.RegisterFunction('first', lambda x: x[0])
440 35 Elmer de Looff
</code></pre>
441 35 Elmer de Looff
442 35 Elmer de Looff
Working just one tag function returns the first element from the list:
443 35 Elmer de Looff
<pre><code class="python">
444 35 Elmer de Looff
>>> template = 'The first element of list: [elements|first].'
445 35 Elmer de Looff
>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
446 35 Elmer de Looff
'The first element of list: Eric.'
447 35 Elmer de Looff
</code></pre>
448 35 Elmer de Looff
449 35 Elmer de Looff
Repeating the function on the string returns the first character from that string:
450 35 Elmer de Looff
<pre><code class="python">
451 35 Elmer de Looff
>>> template = 'The first element of the first element of list: [elements|first|first].'
452 35 Elmer de Looff
>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
453 35 Elmer de Looff
'The first element of the first element of list: E.'
454 35 Elmer de Looff
</code></pre>
455 11 Elmer de Looff
456 44 Elmer de Looff
h3. Valid function name characters
457 44 Elmer de Looff
458 1 Elmer de Looff
Tag function names may be constructed from upper and lower case letters, numbers, underscores and dashes. There are no restrictions on first character, only a minimum length of one. Regex-wise, they need to match @[\w-]+@
459 49 Elmer de Looff
460 52 Jan Klopper
h3. Functions with arguments:
461 52 Jan Klopper
462 52 Jan Klopper
You can support functions with arguments by creating them as such:
463 52 Jan Klopper
464 52 Jan Klopper
<pre><code class="python">
465 52 Jan Klopper
466 52 Jan Klopper
class PageMaker(uweb.PageMaker):
467 52 Jan Klopper
468 52 Jan Klopper
  @staticmethod
469 52 Jan Klopper
  def Limit(length=80):
470 52 Jan Klopper
    """Returns a closure that limits input to a number of chars/elements."""
471 52 Jan Klopper
    return lambda string: string[:length]
472 52 Jan Klopper
473 52 Jan Klopper
  @staticmethod
474 52 Jan Klopper
  def LimitString(length=80, endchar='...'):
475 52 Jan Klopper
    """Limits input to `length` chars and appends `endchar` if it was longer."""
476 52 Jan Klopper
    def _Limit(string, length=length, endchar=endchar):
477 52 Jan Klopper
      if len(string) > length:
478 52 Jan Klopper
        return string[:length] + endchar
479 52 Jan Klopper
      return string
480 52 Jan Klopper
    return _Limit
481 52 Jan Klopper
482 52 Jan Klopper
  def __init__(self, *args, **kwds):
483 52 Jan Klopper
    """Overwrites the default init to add extra templateparser functions."""
484 52 Jan Klopper
    super(PageMaker, self).__init__(*args, **kwds)
485 52 Jan Klopper
    self.parser.RegisterFunction('limit', self.Limit)
486 52 Jan Klopper
    self.parser.RegisterFunction('strlimit', self.LimitString)
487 52 Jan Klopper
488 52 Jan Klopper
</code></pre>
489 52 Jan Klopper
490 52 Jan Klopper
The syntax to be used in the templates is as follows:
491 52 Jan Klopper
492 52 Jan Klopper
<pre><code class="html">
493 52 Jan Klopper
[input|limit()] 
494 52 Jan Klopper
[input|limit(20)] 
495 52 Jan Klopper
[input|strlimit(20)] 
496 52 Jan Klopper
[input|strlimit(30, "&ndash;")] 
497 52 Jan Klopper
</code></pre>
498 52 Jan Klopper
499 52 Jan Klopper
These functions can still be chained as you would expect.
500 52 Jan Klopper
501 54 Erwin Hager
h3. Limitations
502 54 Erwin Hager
503 56 Erwin Hager
Tag functions cannot be used inside loops or conditionals.
504 54 Erwin Hager
505 49 Elmer de Looff
h2. Tag closures
506 49 Elmer de Looff
507 49 Elmer de Looff
Closures in TemplateParser could be succinctly described as 'functions with arguments'. In the tag syntax, they are very similar to functions, and they can be freely mixed with functions.
508 49 Elmer de Looff
509 49 Elmer de Looff
An example tag closure looks like this: @[text|maxlen(20)]@. Here, the template tag @text@ is limited to 20 characters in length. The same functionality could be achieved with a plain function @maxlen20@, but that would require a separate @maxlen@ function for each length you want to limit the input to.
510 49 Elmer de Looff
511 49 Elmer de Looff
THe @maxlen@ closure is achieved by registering a function 'maxlen' which takes a single argument (the maximum length) and returns a function (a closure) which performs this action. The template tag is passed to that closure, and the return value is used as the tag value. This principe is explained in more detail below:
512 49 Elmer de Looff
513 49 Elmer de Looff
Setting up the parser and registering our @maxlen@ function:
514 49 Elmer de Looff
<pre><code class="python">
515 49 Elmer de Looff
>>> from uweb import templateparser
516 49 Elmer de Looff
>>> def MaxLength(length):
517 49 Elmer de Looff
...   def _MaxLen(tag_value, length=int(length)):
518 49 Elmer de Looff
...     return tag_value[:length]
519 49 Elmer de Looff
...   return _MaxLen
520 49 Elmer de Looff
...
521 49 Elmer de Looff
>>> parser = templateparser.Parser()
522 49 Elmer de Looff
>>> parser.RegisterFunction('maxlen', MaxLength)
523 49 Elmer de Looff
</code></pre>
524 49 Elmer de Looff
525 49 Elmer de Looff
Applying the tag closure on a simple template:
526 49 Elmer de Looff
<pre><code class="python">
527 49 Elmer de Looff
>>> template = 'Small blurb: "[text|maxlen(20)]".'
528 49 Elmer de Looff
>>> parser.ParseString(template, text="Python is a general-purpose, high-level programming language.")
529 49 Elmer de Looff
'Small blurb: "Python is a general-".'
530 49 Elmer de Looff
</code></pre>
531 49 Elmer de Looff
532 49 Elmer de Looff
h3. Arguments
533 49 Elmer de Looff
534 49 Elmer de Looff
Closures accept multiple arguments. The example above could be extended to contain a 'suffix' that should be appended to truncated strings. In that case there would be a length check, and dependent on the outcome, the string would be truncated and a suffix placed. Or a different function where only the beginning and end would show, and the middle truncated.
535 49 Elmer de Looff
536 49 Elmer de Looff
h3. Valid closure name/argument characters
537 49 Elmer de Looff
538 49 Elmer de Looff
Tag closure names have the same restrictions as tag function names (alphanumeric, underscores, dashes; regex @[\w-]+@). Closure arguments should form a legal Python tuple, and may contain any characters _except_ for parentheses. A closure may have zero arguments (they are defined by a pair of parentheses). The regex for this is simply: @[^()]*@.
539 49 Elmer de Looff
540 49 Elmer de Looff
h3. Limitations
541 49 Elmer de Looff
542 49 Elmer de Looff
Currently, closure arguments can only be positional. That is, there is no support for keyword arguments in the tag closure. This limits the possibilities somewhat, but 
543 44 Elmer de Looff
544 23 Elmer de Looff
h2. TemplateLoop
545 11 Elmer de Looff
546 23 Elmer de Looff
As a language construct, TemplateParser has an understanding of iteration. The @TemplateLoop@ can be compared to the Python @for@-loop, or the @foreach@ construct in other languages (lazy iteration over the values of an iterable).
547 1 Elmer de Looff
548 23 Elmer de Looff
h3. Syntax and properties
549 23 Elmer de Looff
550 23 Elmer de Looff
*Syntax: @{{ for local_var in [collection] }}@*
551 20 Elmer de Looff
* The double accolades (curly braces) indicate the beginning and end of the construct;
552 20 Elmer de Looff
* The @for@ keyword indicates the structure to execute;
553 20 Elmer de Looff
* @local_var@ is the name which references the loop variable;
554 20 Elmer de Looff
* @[collection]@ is the tag that provides the iteratable.
555 20 Elmer de Looff
556 20 Elmer de Looff
*Properties*
557 20 Elmer de Looff
* The local name is stated without brackets (as it's no tag itself)
558 1 Elmer de Looff
* When it needs to be placed in the output, the local name should have brackets (like any other tag)
559 20 Elmer de Looff
* *N.B.* The local variable does _not_ bleed into the outer scope after the loop has completed.
560 20 Elmer de Looff
 It is therefore possible (though not recommended) to name the loop variable after the iterable: @{{ for collection in [collection] }}@.
561 20 Elmer de Looff
562 23 Elmer de Looff
h3. Example of a @TemplateLoop@
563 20 Elmer de Looff
564 20 Elmer de Looff
<pre><code class="html">
565 20 Elmer de Looff
<html>
566 20 Elmer de Looff
  <body>
567 20 Elmer de Looff
    <ul>
568 20 Elmer de Looff
    {{ for name in [presidents] }}
569 20 Elmer de Looff
      <li>President [name]</li>
570 20 Elmer de Looff
    {{ endfor }}
571 20 Elmer de Looff
    </ul>
572 20 Elmer de Looff
  </body>
573 20 Elmer de Looff
</html>
574 20 Elmer de Looff
</code></pre>
575 20 Elmer de Looff
576 20 Elmer de Looff
<pre><code class="python">
577 20 Elmer de Looff
>>> parser.Parse('rushmore.utp', presidents=['Washington', 'Jefferson', 'Roosevelt', 'Lincoln'])
578 20 Elmer de Looff
</code></pre>
579 20 Elmer de Looff
580 20 Elmer de Looff
<pre><code class="html">
581 20 Elmer de Looff
<html>
582 20 Elmer de Looff
  <body>
583 1 Elmer de Looff
    <ul>
584 1 Elmer de Looff
      <li>President Washington</li>
585 1 Elmer de Looff
      <li>President Jefferson</li>
586 1 Elmer de Looff
      <li>President Roosevelt</li>
587 1 Elmer de Looff
      <li>President Lincoln</li>
588 1 Elmer de Looff
    </ul>
589 1 Elmer de Looff
  </body>
590 1 Elmer de Looff
</html>
591 1 Elmer de Looff
</code></pre>
592 1 Elmer de Looff
593 1 Elmer de Looff
h2. Inlining templates
594 21 Elmer de Looff
595 21 Elmer de Looff
Often, there will be snippets of a template that will see a lot of reuse. Page headers and footers are often the same on many pages, and having several redundant copies means that changes will have to be replicated to each of these occurrances. To reduce the need for this, TemplateParser has an @inline@ statement. Using this you can specify a template that is available in the @[[TemplateParser#Parser]]@ instance and the statement will be replaced by the template.
596 21 Elmer de Looff
597 21 Elmer de Looff
Of course, if the inlined template is not already in the @Parser@ instance, the autoloading mechanism will trigger, and the named template will be search for in the @Parser@'s template directory.
598 21 Elmer de Looff
599 21 Elmer de Looff
First, we will define our inline template, @'inline_hello.utp'@:
600 21 Elmer de Looff
601 21 Elmer de Looff
<pre><code class="html">
602 21 Elmer de Looff
<p>Hello [name]</p>
603 21 Elmer de Looff
</code></pre>
604 21 Elmer de Looff
605 21 Elmer de Looff
Secondly, our main template, @'hello.utp'@:
606 21 Elmer de Looff
607 21 Elmer de Looff
<pre><code class="html">
608 21 Elmer de Looff
<h1>Greetings</h1>
609 21 Elmer de Looff
{{ inline inline_hello.utp }}
610 21 Elmer de Looff
</code></pre>
611 21 Elmer de Looff
612 21 Elmer de Looff
Then we parse the template:
613 21 Elmer de Looff
614 21 Elmer de Looff
<pre><code class="python">
615 21 Elmer de Looff
>>> parser.Parse('hello.utp', name='Dr John')
616 21 Elmer de Looff
</code></pre>
617 21 Elmer de Looff
618 21 Elmer de Looff
<pre><code class="html">
619 21 Elmer de Looff
<h1>Greetings</h1>
620 21 Elmer de Looff
<p>Hello Dr John</p>
621 21 Elmer de Looff
</code></pre>
622 11 Elmer de Looff
623 11 Elmer de Looff
h2. Conditional statements
624 11 Elmer de Looff
625 22 Elmer de Looff
Often, you'll want the output of your template to be dependent on the value, presence, or boolean value of another tag. For instance, we may want a print a list of attendees to a party. We start the @if@ conditional by checking the boolean value of the @attendees@ tag. If this list if not-empty, we will print the attendee names, but if it's empty (or contains only a single entry), we'll tell the user in more intelligent ways than giving them a list with zero entries:
626 22 Elmer de Looff
627 22 Elmer de Looff
<pre><code class="html">
628 22 Elmer de Looff
<h1>Party attendees</h1>
629 22 Elmer de Looff
{{ if len([attendees]) > 1 }}
630 22 Elmer de Looff
  <ol>
631 22 Elmer de Looff
    {{ for attendee in [attendees] }}
632 22 Elmer de Looff
    <li>[attendee:name]</li>
633 22 Elmer de Looff
    {{ endfor }}
634 22 Elmer de Looff
  </ol>
635 22 Elmer de Looff
{{ elif [attendees] }}
636 22 Elmer de Looff
  <p>only [attendees:0:name] is attending.</p>
637 22 Elmer de Looff
{{ else }}
638 22 Elmer de Looff
  <p>There are no registered attendees yet.</p>
639 22 Elmer de Looff
{{ endif }}
640 22 Elmer de Looff
</code></pre>
641 22 Elmer de Looff
642 22 Elmer de Looff
For the case where there are several attendees:
643 22 Elmer de Looff
644 22 Elmer de Looff
<pre><code class="python">
645 22 Elmer de Looff
>>> parser.Parse('party.utp', attendees=[
646 22 Elmer de Looff
...    {'name': 'Livingstone'},
647 22 Elmer de Looff
...    {'name': 'Cook'},
648 22 Elmer de Looff
...    {'name': 'Drake'}])
649 22 Elmer de Looff
</code></pre>
650 22 Elmer de Looff
651 22 Elmer de Looff
<pre><code class="html">
652 22 Elmer de Looff
<h1>Party attendees</h1>
653 22 Elmer de Looff
<ol>
654 22 Elmer de Looff
  <li>Livingstone</li>
655 22 Elmer de Looff
  <li>Cook</li>
656 22 Elmer de Looff
  <li>Drake</li>
657 22 Elmer de Looff
</ol>
658 22 Elmer de Looff
</code></pre>
659 22 Elmer de Looff
660 22 Elmer de Looff
For the case where there is one attendee:
661 22 Elmer de Looff
662 22 Elmer de Looff
<pre><code class="python">
663 22 Elmer de Looff
>>> parser.Parse('party.utp', attendees=[{'name': 'Johnny'}])
664 22 Elmer de Looff
</code></pre>
665 22 Elmer de Looff
666 22 Elmer de Looff
<pre><code class="html">
667 22 Elmer de Looff
<h1>Party attendees</h1>
668 22 Elmer de Looff
<p>Only Johnny is attending.</p>
669 22 Elmer de Looff
</code></pre>
670 22 Elmer de Looff
671 22 Elmer de Looff
And in the case where there are no attendees:
672 22 Elmer de Looff
673 22 Elmer de Looff
<pre><code class="python">
674 22 Elmer de Looff
>>> parser.Parse('party.utp', attendees=[])
675 22 Elmer de Looff
</code></pre>
676 22 Elmer de Looff
677 22 Elmer de Looff
<pre><code class="html">
678 22 Elmer de Looff
<h1>Party attendees</h1>
679 22 Elmer de Looff
<p>There are no registered attendees yet.</p>
680 22 Elmer de Looff
</code></pre>
681 22 Elmer de Looff
682 22 Elmer de Looff
h3. Properties of conditional statements
683 22 Elmer de Looff
684 22 Elmer de Looff
* *All template keys must be referenced as proper tag*
685 22 Elmer de Looff
 This is to prevent mixing of the template variables with the functions and reserved names of Python itself. Conditional expressions are evaluated using @eval()@, and proper tags are replaced by temporary names, the values of which are stored in a retrieve-on-demand dictionary. This makes them perfectly safe with regard to the value of template replacements, but some care should be taken with the writing of the conditional expressions.
686 22 Elmer de Looff
* *It is possible to index tags in conditional statements*
687 22 Elmer de Looff
 This allows for decisions based on the values in those indexes/keys. For instance, @Person@ objects can be checked for gender, so that the correct gender-based icon can be displayed next to them.
688 22 Elmer de Looff
* *Referencing a tag or index that doesn't exist raises @TemplateNameError*
689 22 Elmer de Looff
 Unlike in regular template text, there is no suitable fallback value for a tag or index that cannot be retrieved. However, in most cases this can be prevented by making use of the following property:
690 22 Elmer de Looff
* *Statement evaluation is lazy*
691 22 Elmer de Looff
 Template conditions are processed left to right, and short-circuited where possible. If the first member of an @or@ group succeeds, the return value is already known. Similarly, if the first member of an @and@ group fails, the second part need not be evaluated. This way @TemplateNameErrors@ can often be prevented, as in most cases, presence of indexes can be confirmed before accessing.
692 22 Elmer de Looff
693 22 Elmer de Looff
694 11 Elmer de Looff
h2. Template unicode handling
695 11 Elmer de Looff
696 11 Elmer de Looff
Any @unicode@ object found while parsing, will automatically be encoded to UTF-8:
697 11 Elmer de Looff
698 11 Elmer de Looff
<pre><code class="python">
699 11 Elmer de Looff
>>> template = 'Underdark [love] [app]'
700 11 Elmer de Looff
>>> output = parser.ParseString(template, love=u'\u2665', app=u'\N{micro sign}Web')
701 11 Elmer de Looff
>>> output
702 12 Elmer de Looff
'Underdark \xe2\x99\xa5 \xc2\xb5Web'  # The output in its raw UTF-8 representation
703 11 Elmer de Looff
>>> output.decode('UTF8')
704 12 Elmer de Looff
u'Underdark \u2665 \xb5Web'           # The output converted to a Unicode object
705 19 Elmer de Looff
>>> print output
706 19 Elmer de Looff
Underdark ♥ µWeb                      # And the printed UTF-8 as we desired it.
707 14 Elmer de Looff
</code></pre>