Project

General

Profile

TemplateParser » History » Version 35

Elmer de Looff, 2012-04-16 15:35
Tag functions documented :D (except for the default tag function.. coming right up)

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 21 Elmer de Looff
* The *[[TemplateParser#template|Template class]]*, used to parse the templating language
7 21 Elmer de Looff
* The *[[TemplateParser#parser|Parser class]]*, which provides template loading and caching
8 21 Elmer de Looff
* *[[TemplateParser#using|Using TemplateParser]]* inside a µWeb PageMaker
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 5 Elmer de Looff
h1(#template). Template class
20 4 Elmer de Looff
21 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.
22 4 Elmer de Looff
23 4 Elmer de Looff
h2. Creating a template
24 4 Elmer de Looff
25 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]]:
26 4 Elmer de Looff
27 4 Elmer de Looff
<pre><code class="python">
28 21 Elmer de Looff
>>> import templateparser
29 4 Elmer de Looff
>>> template = templateparser.Template('Hello [title] [name]')
30 4 Elmer de Looff
>>> template
31 4 Elmer de Looff
Template([TemplateText('Hello '), TemplateTag('[title]'), TemplateText(' '), TemplateTag('[name]')])
32 4 Elmer de Looff
</code></pre>
33 4 Elmer de Looff
34 4 Elmer de Looff
Above can be seen the various parts of the template, which will be combined to output once parsed.
35 4 Elmer de Looff
36 4 Elmer de Looff
h2. Loading a template from file
37 4 Elmer de Looff
38 4 Elmer de Looff
The @Template@ class provides a @classmethod@ called @FromFile@, which loads the template at the path.
39 4 Elmer de Looff
40 4 Elmer de Looff
Loading a template named @example.utp@ from the current working directory:
41 4 Elmer de Looff
42 4 Elmer de Looff
<pre><code class="python">
43 21 Elmer de Looff
>>> import templateparser
44 4 Elmer de Looff
>>> template = templateparser.Template.FromFile('example.utp')
45 4 Elmer de Looff
>>> template
46 4 Elmer de Looff
Template([TemplateText('Hello '), TemplateTag('[title]'), TemplateText(' '), TemplateTag('[name]')])
47 4 Elmer de Looff
</code></pre>
48 4 Elmer de Looff
49 5 Elmer de Looff
h2. Parsing a template
50 4 Elmer de Looff
51 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.
52 4 Elmer de Looff
53 4 Elmer de Looff
<pre><code class="python">
54 21 Elmer de Looff
>>> import templateparser
55 4 Elmer de Looff
>>> template = templateparser.Template('Hello [title] [name]')
56 8 Elmer de Looff
>>> template.Parse(title='sir')
57 8 Elmer de Looff
'Hello sir [name]'
58 4 Elmer de Looff
</code></pre>
59 1 Elmer de Looff
60 1 Elmer de Looff
h1(#parser). Parser class
61 6 Elmer de Looff
62 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.
63 1 Elmer de Looff
64 8 Elmer de Looff
h2. Loading templates
65 8 Elmer de Looff
66 6 Elmer de Looff
Creating a parser object, and loading the 'example.utp' template from the 'templates' directory works like this:
67 6 Elmer de Looff
68 6 Elmer de Looff
<pre><code class="python">
69 21 Elmer de Looff
>>> import templateparser
70 7 Elmer de Looff
>>> # This sets the 'templates' directory as the search path for AddTemplate
71 7 Elmer de Looff
>>> parser = templateparser.Parser('templates')
72 7 Elmer de Looff
>>> # Loads the 'templates/example.utp' and stores it as 'example.utp':
73 26 Elmer de Looff
>>> parser.AddTemplate('example.utp')
74 27 Elmer de Looff
>>> parser.Parse('example.utp', title='mister', name='Bob Dobalina')
75 1 Elmer de Looff
'Hello mister Bob Dobalina'
76 6 Elmer de Looff
</code></pre>
77 1 Elmer de Looff
78 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:
79 29 Elmer de Looff
80 29 Elmer de Looff
<pre><code class="python">
81 29 Elmer de Looff
>>> parser = templateparser.Parser('templates')
82 30 Elmer de Looff
>>> parser.AddTemplate('example.utp', name='greeting')
83 29 Elmer de Looff
>>> parser.Parse('greeting', title='mister', name='Bob Dobalina')
84 29 Elmer de Looff
'Hello mister Bob Dobalina'
85 29 Elmer de Looff
</code></pre>
86 29 Elmer de Looff
87 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.
88 1 Elmer de Looff
89 8 Elmer de Looff
h2. Template cache and auto-loading
90 8 Elmer de Looff
91 8 Elmer de Looff
The @Parser@ object behaves like a slightly modified dictionary to achieve this. Retrieving keys yields the associated template. Keys that are not present in the cache are _automatically_ retrieved from the filesystem:
92 8 Elmer de Looff
93 1 Elmer de Looff
<pre><code class="python">
94 21 Elmer de Looff
>>> import templateparser
95 7 Elmer de Looff
>>> parser = templateparser.Parser('templates')
96 28 Elmer de Looff
>>> 'example.utp' in parser
97 28 Elmer de Looff
False       # Since we haven't loaded it, the template it not in the parser
98 7 Elmer de Looff
>>> parser
99 1 Elmer de Looff
Parser({})  # The parser is empty (has no cached templates)
100 28 Elmer de Looff
</code></pre>
101 28 Elmer de Looff
102 28 Elmer de Looff
Attempting to parse a template that doesn't exist in the parser cache triggers an automatic load:
103 28 Elmer de Looff
104 28 Elmer de Looff
<pre><code class="python">
105 1 Elmer de Looff
>>> parser['example.utp'].Parse(title='mister', name='Bob Dobalina')
106 1 Elmer de Looff
'Hello mister Bob Dobalina'
107 28 Elmer de Looff
>>> 'example.utp' in parser
108 28 Elmer de Looff
True
109 1 Elmer de Looff
>>> parser
110 8 Elmer de Looff
Parser({'example.utp': Template([TemplateText('Hello '), TemplateTag('[title]'),
111 8 Elmer de Looff
                                 TemplateText(' '), TemplateTag('[name]')])})
112 7 Elmer de Looff
</code></pre>
113 7 Elmer de Looff
114 7 Elmer de Looff
If these cannot be found, @TemplateReadError@ is raised:
115 7 Elmer de Looff
116 7 Elmer de Looff
<pre><code class="python">
117 21 Elmer de Looff
>>> import templateparser
118 7 Elmer de Looff
>>> parser = templateparser.Parser('templates')
119 6 Elmer de Looff
>>> parser['bad_template.utp'].Parse(failure='imminent')
120 6 Elmer de Looff
Traceback (most recent call last):
121 6 Elmer de Looff
  File "<stdin>", line 1, in <module>
122 7 Elmer de Looff
  File "/var/lib/underdark/libs/uweb/templateparser.py", line 147, in __getitem__
123 6 Elmer de Looff
    self.AddTemplate(template)
124 1 Elmer de Looff
  File "/var/lib/underdark/libs/uweb/templateparser.py", line 171, in AddTemplate
125 1 Elmer de Looff
    raise TemplateReadError('Could not load template %r' % template_path)
126 1 Elmer de Looff
underdark.libs.uweb.templateparser.TemplateReadError: Could not load template 'templates/bad_template.utp'
127 1 Elmer de Looff
</code></pre>
128 8 Elmer de Looff
129 18 Elmer de Looff
h2. @Parse@ and @ParseString@ methods
130 8 Elmer de Looff
131 8 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:
132 8 Elmer de Looff
133 8 Elmer de Looff
<pre><code class="python">
134 21 Elmer de Looff
>>> import templateparser
135 8 Elmer de Looff
>>> parser = templateparser.Parser('templates')
136 8 Elmer de Looff
>>> parser.Parse('example.utp', title='mister', name='Bob Dobalina')
137 8 Elmer de Looff
'Hello mister Bob Dobalina'
138 8 Elmer de Looff
>>> parser.ParseString('Hello [title] [name]', title='mister', name='Bob Dobalina')
139 8 Elmer de Looff
'Hello mister Bob Dobalina'</code></pre>
140 6 Elmer de Looff
141 1 Elmer de Looff
h1(#using). Using TemplateParser inside µWeb
142 10 Elmer de Looff
143 10 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'@. *N.B.* This path is relative to the file that contains the PageMaker class.
144 10 Elmer de Looff
145 10 Elmer de Looff
An example of TemplateParser to create a complete response:
146 10 Elmer de Looff
<pre><code class="python">
147 31 Jan Klopper
import uweb
148 10 Elmer de Looff
import time
149 10 Elmer de Looff
150 10 Elmer de Looff
class PageMaker(uweb.PageMaker):
151 10 Elmer de Looff
  def VersionPage(self):
152 10 Elmer de Looff
    return self.parser.Parse(
153 10 Elmer de Looff
      'version.utp', year=time.strftime('%Y'), version=uweb.__version__)
154 10 Elmer de Looff
</code></pre>
155 10 Elmer de Looff
156 10 Elmer de Looff
The example template for the above file could look something like this:
157 10 Elmer de Looff
158 10 Elmer de Looff
159 10 Elmer de Looff
<pre><code class="html">
160 10 Elmer de Looff
<!DOCTYPE html>
161 10 Elmer de Looff
<html>
162 10 Elmer de Looff
  <head>
163 10 Elmer de Looff
    <title>µWeb version info</title>
164 10 Elmer de Looff
  </head>
165 10 Elmer de Looff
  <body>
166 10 Elmer de Looff
    <p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
167 10 Elmer de Looff
  </body>
168 10 Elmer de Looff
</html>
169 10 Elmer de Looff
</code></pre>
170 1 Elmer de Looff
171 5 Elmer de Looff
h1(#syntax). Templating language syntax
172 11 Elmer de Looff
173 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:
174 11 Elmer de Looff
* Simple tags (used in various examples above)
175 11 Elmer de Looff
* Tag indexing
176 11 Elmer de Looff
* Tag functions
177 11 Elmer de Looff
* Template language constructs
178 11 Elmer de Looff
179 11 Elmer de Looff
All examples will consist of three parts:
180 11 Elmer de Looff
# The example template
181 11 Elmer de Looff
# The python invocation string (the template will be named 'example.utp')
182 11 Elmer de Looff
# The resulting output (as source, not as parsed HTML)
183 11 Elmer de Looff
184 11 Elmer de Looff
h2. Simple tags
185 11 Elmer de Looff
186 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
187 11 Elmer de Looff
188 11 Elmer de Looff
The example below is a repeat of the example how to use TemplateParser inside µWeb, and shows the template result:
189 11 Elmer de Looff
190 11 Elmer de Looff
<pre><code class="html">
191 11 Elmer de Looff
<!DOCTYPE html>
192 11 Elmer de Looff
<html>
193 11 Elmer de Looff
  <head>
194 11 Elmer de Looff
    <title>µWeb version info</title>
195 11 Elmer de Looff
  </head>
196 11 Elmer de Looff
  <body>
197 11 Elmer de Looff
    <p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
198 11 Elmer de Looff
    <p>
199 11 Elmer de Looff
      This [paragraph] is not replaced because there is no
200 11 Elmer de Looff
      paragraph argument provided to the parser.
201 11 Elmer de Looff
    </p>
202 11 Elmer de Looff
  </body>
203 11 Elmer de Looff
</html>
204 11 Elmer de Looff
</code></pre>
205 11 Elmer de Looff
206 11 Elmer de Looff
<pre><code class="python">
207 11 Elmer de Looff
>>> parser.Parse('version.utp', year=time.strftime('%Y'), version=uweb.__version__)
208 11 Elmer de Looff
</code></pre>
209 11 Elmer de Looff
210 11 Elmer de Looff
<pre><code class="html">
211 11 Elmer de Looff
<!DOCTYPE html>
212 11 Elmer de Looff
<html>
213 11 Elmer de Looff
  <head>
214 11 Elmer de Looff
    <title>µWeb version info</title>
215 11 Elmer de Looff
  </head>
216 11 Elmer de Looff
  <body>
217 11 Elmer de Looff
    <p>µWeb version 0.11 - Copyright 2010-212 Underdark</p>
218 11 Elmer de Looff
    <p>
219 11 Elmer de Looff
      This [paragraph] is not replaced because there is no
220 11 Elmer de Looff
      paragraph argument provided to the parser.
221 11 Elmer de Looff
    </p>
222 11 Elmer de Looff
  </body>
223 11 Elmer de Looff
</html>
224 11 Elmer de Looff
</code></pre>
225 11 Elmer de Looff
226 11 Elmer de Looff
h2. Tag indexing
227 11 Elmer de Looff
228 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_ (":"):
229 32 Elmer de Looff
230 32 Elmer de Looff
231 32 Elmer de Looff
h3. List/tuple index addressing
232 32 Elmer de Looff
233 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.
234 34 Elmer de Looff
235 32 Elmer de Looff
<pre><code class="html">
236 32 Elmer de Looff
This is [var:0] [var:1].
237 32 Elmer de Looff
</code></pre>
238 32 Elmer de Looff
239 32 Elmer de Looff
<pre><code class="python">
240 32 Elmer de Looff
>>> parser.Parse('message.utp', var=('delicious', 'spam'))
241 32 Elmer de Looff
</code></pre>
242 32 Elmer de Looff
243 32 Elmer de Looff
<pre><code class="html">
244 32 Elmer de Looff
This is delicious spam.
245 32 Elmer de Looff
</code></pre>
246 32 Elmer de Looff
247 33 Elmer de Looff
h3. Dictionary key addressing
248 32 Elmer de Looff
249 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.
250 34 Elmer de Looff
251 32 Elmer de Looff
<pre><code class="html">
252 32 Elmer de Looff
This is [var:adjective] [var:noun].
253 32 Elmer de Looff
</code></pre>
254 32 Elmer de Looff
255 32 Elmer de Looff
<pre><code class="python">
256 32 Elmer de Looff
>>> parser.Parse('message.utp', var={'adjective': 'delicious', 'noun': 'spam'})
257 32 Elmer de Looff
</code></pre>
258 32 Elmer de Looff
259 32 Elmer de Looff
<pre><code class="html">
260 32 Elmer de Looff
This is delicious spam.
261 32 Elmer de Looff
</code></pre>
262 32 Elmer de Looff
263 33 Elmer de Looff
h3. Attribute name addressing
264 32 Elmer de Looff
265 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.
266 34 Elmer de Looff
267 32 Elmer de Looff
<pre><code class="html">
268 32 Elmer de Looff
This is [var:adjective] [var:noun].
269 32 Elmer de Looff
</code></pre>
270 32 Elmer de Looff
271 32 Elmer de Looff
<pre><code class="python">
272 32 Elmer de Looff
>>> class Struct(object):
273 32 Elmer de Looff
...   pass
274 32 Elmer de Looff
...
275 32 Elmer de Looff
>>> var = Struct()
276 32 Elmer de Looff
>>> var.adjective = 'delicious'
277 32 Elmer de Looff
>>> var.noun = 'spam'
278 32 Elmer de Looff
>>> parser.Parse('message.utp', var=var)
279 32 Elmer de Looff
</code></pre>
280 32 Elmer de Looff
281 32 Elmer de Looff
<pre><code class="html">
282 32 Elmer de Looff
This is delicious spam.
283 32 Elmer de Looff
</code></pre>
284 32 Elmer de Looff
285 33 Elmer de Looff
h3. Lookup order
286 32 Elmer de Looff
287 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:
288 32 Elmer de Looff
289 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;
290 32 Elmer de Looff
# If the above fails, the @needle@ is assumed to be a string-like mapping key, and this is attempted
291 32 Elmer de Looff
# If the above fails, the @needle@ is used as an attribute name;
292 32 Elmer de Looff
# If all of the above fail, *@TemplateKeyError@* is raised, as the @needle@ could not be found on the object.
293 34 Elmer de Looff
294 34 Elmer de Looff
h3. Nested indexes
295 34 Elmer de Looff
296 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:
297 34 Elmer de Looff
298 34 Elmer de Looff
<pre><code class="html">
299 34 Elmer de Looff
This is a variable from [some:levels:down:1].
300 34 Elmer de Looff
</code></pre>
301 34 Elmer de Looff
302 34 Elmer de Looff
<pre><code class="python">
303 34 Elmer de Looff
>>> class Struct(object):
304 34 Elmer de Looff
...   pass
305 34 Elmer de Looff
...
306 34 Elmer de Looff
>>> var = Struct()
307 34 Elmer de Looff
>>> var.levels = {'down': ('the sky', 'the depths')}
308 34 Elmer de Looff
>>> parser.Parse('message.utp', some=var)
309 34 Elmer de Looff
</code></pre>
310 34 Elmer de Looff
311 34 Elmer de Looff
<pre><code class="html">
312 34 Elmer de Looff
This is a variable from the depths.
313 34 Elmer de Looff
</code></pre>
314 16 Elmer de Looff
315 11 Elmer de Looff
h2. Tag functions
316 11 Elmer de Looff
317 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.
318 15 Elmer de Looff
319 21 Elmer de Looff
h3. Default html escaping
320 1 Elmer de Looff
321 35 Elmer de Looff
322 35 Elmer de Looff
h3. Predefined tag functions
323 35 Elmer de Looff
324 35 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 (*'* and *"*) and the pointy brackets (*<* and *>*) are converted to their respective "character entity references":http://en.wikipedia.org/wiki/Character_entity_reference
325 35 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.
326 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.
327 35 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.
328 35 Elmer de Looff
329 1 Elmer de Looff
h3. Adding custom functions
330 35 Elmer de Looff
331 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:
332 35 Elmer de Looff
333 35 Elmer de Looff
<pre><code class="python">
334 35 Elmer de Looff
>>> from uweb import templateparser
335 35 Elmer de Looff
>>> parser = templateparser.Parser()
336 35 Elmer de Looff
>>> parser.RegisterFunction('len', len)
337 35 Elmer de Looff
>>> template = 'The number of people in this group: [people|len].'
338 35 Elmer de Looff
>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
339 35 Elmer de Looff
'The number of people in this group: 4.'
340 35 Elmer de Looff
</code></pre>
341 35 Elmer de Looff
342 35 Elmer de Looff
h3. Nesting functions
343 35 Elmer de Looff
344 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:
345 35 Elmer de Looff
346 35 Elmer de Looff
Setting up the parser and registering our tag function
347 35 Elmer de Looff
<pre><code class="python">
348 35 Elmer de Looff
>>> from uweb import templateparser
349 35 Elmer de Looff
>>> parser = templateparser.Parser()
350 35 Elmer de Looff
>>> parser.RegisterFunction('first', lambda x: x[0])
351 35 Elmer de Looff
</code></pre>
352 35 Elmer de Looff
353 35 Elmer de Looff
Working just one tag function returns the first element from the list:
354 35 Elmer de Looff
<pre><code class="python">
355 35 Elmer de Looff
>>> template = 'The first element of list: [elements|first].'
356 35 Elmer de Looff
>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
357 35 Elmer de Looff
'The first element of list: Eric.'
358 35 Elmer de Looff
</code></pre>
359 35 Elmer de Looff
360 35 Elmer de Looff
Repeating the function on the string returns the first character from that string:
361 35 Elmer de Looff
<pre><code class="python">
362 35 Elmer de Looff
>>> template = 'The first element of the first element of list: [elements|first|first].'
363 35 Elmer de Looff
>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
364 35 Elmer de Looff
'The first element of the first element of list: E.'
365 35 Elmer de Looff
</code></pre>
366 35 Elmer de Looff
367 11 Elmer de Looff
368 23 Elmer de Looff
h2. TemplateLoop
369 11 Elmer de Looff
370 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).
371 1 Elmer de Looff
372 23 Elmer de Looff
h3. Syntax and properties
373 23 Elmer de Looff
374 23 Elmer de Looff
*Syntax: @{{ for local_var in [collection] }}@*
375 20 Elmer de Looff
* The double accolades (curly braces) indicate the beginning and end of the construct;
376 20 Elmer de Looff
* The @for@ keyword indicates the structure to execute;
377 20 Elmer de Looff
* @local_var@ is the name which references the loop variable;
378 20 Elmer de Looff
* @[collection]@ is the tag that provides the iteratable.
379 20 Elmer de Looff
380 20 Elmer de Looff
*Properties*
381 20 Elmer de Looff
* The local name is stated without brackets (as it's no tag itself)
382 1 Elmer de Looff
* When it needs to be placed in the output, the local name should have brackets (like any other tag)
383 20 Elmer de Looff
* *N.B.* The local variable does _not_ bleed into the outer scope after the loop has completed.
384 20 Elmer de Looff
 It is therefore possible (though not recommended) to name the loop variable after the iterable: @{{ for collection in [collection] }}@.
385 20 Elmer de Looff
386 23 Elmer de Looff
h3. Example of a @TemplateLoop@
387 20 Elmer de Looff
388 20 Elmer de Looff
<pre><code class="html">
389 20 Elmer de Looff
<html>
390 20 Elmer de Looff
  <body>
391 20 Elmer de Looff
    <ul>
392 20 Elmer de Looff
    {{ for name in [presidents] }}
393 20 Elmer de Looff
      <li>President [name]</li>
394 20 Elmer de Looff
    {{ endfor }}
395 20 Elmer de Looff
    </ul>
396 20 Elmer de Looff
  </body>
397 20 Elmer de Looff
</html>
398 20 Elmer de Looff
</code></pre>
399 20 Elmer de Looff
400 20 Elmer de Looff
<pre><code class="python">
401 20 Elmer de Looff
>>> parser.Parse('rushmore.utp', presidents=['Washington', 'Jefferson', 'Roosevelt', 'Lincoln'])
402 20 Elmer de Looff
</code></pre>
403 20 Elmer de Looff
404 20 Elmer de Looff
<pre><code class="html">
405 20 Elmer de Looff
<html>
406 20 Elmer de Looff
  <body>
407 1 Elmer de Looff
    <ul>
408 1 Elmer de Looff
      <li>President Washington</li>
409 1 Elmer de Looff
      <li>President Jefferson</li>
410 1 Elmer de Looff
      <li>President Roosevelt</li>
411 1 Elmer de Looff
      <li>President Lincoln</li>
412 1 Elmer de Looff
    </ul>
413 1 Elmer de Looff
  </body>
414 1 Elmer de Looff
</html>
415 1 Elmer de Looff
</code></pre>
416 1 Elmer de Looff
417 1 Elmer de Looff
h2. Inlining templates
418 21 Elmer de Looff
419 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.
420 21 Elmer de Looff
421 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.
422 21 Elmer de Looff
423 21 Elmer de Looff
First, we will define our inline template, @'inline_hello.utp'@:
424 21 Elmer de Looff
425 21 Elmer de Looff
<pre><code class="html">
426 21 Elmer de Looff
<p>Hello [name]</p>
427 21 Elmer de Looff
</code></pre>
428 21 Elmer de Looff
429 21 Elmer de Looff
Secondly, our main template, @'hello.utp'@:
430 21 Elmer de Looff
431 21 Elmer de Looff
<pre><code class="html">
432 21 Elmer de Looff
<h1>Greetings</h1>
433 21 Elmer de Looff
{{ inline inline_hello.utp }}
434 21 Elmer de Looff
</code></pre>
435 21 Elmer de Looff
436 21 Elmer de Looff
Then we parse the template:
437 21 Elmer de Looff
438 21 Elmer de Looff
<pre><code class="python">
439 21 Elmer de Looff
>>> parser.Parse('hello.utp', name='Dr John')
440 21 Elmer de Looff
</code></pre>
441 21 Elmer de Looff
442 21 Elmer de Looff
<pre><code class="html">
443 21 Elmer de Looff
<h1>Greetings</h1>
444 21 Elmer de Looff
<p>Hello Dr John</p>
445 21 Elmer de Looff
</code></pre>
446 11 Elmer de Looff
447 11 Elmer de Looff
h2. Conditional statements
448 11 Elmer de Looff
449 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:
450 22 Elmer de Looff
451 22 Elmer de Looff
<pre><code class="html">
452 22 Elmer de Looff
<h1>Party attendees</h1>
453 22 Elmer de Looff
{{ if len([attendees]) > 1 }}
454 22 Elmer de Looff
  <ol>
455 22 Elmer de Looff
    {{ for attendee in [attendees] }}
456 22 Elmer de Looff
    <li>[attendee:name]</li>
457 22 Elmer de Looff
    {{ endfor }}
458 22 Elmer de Looff
  </ol>
459 22 Elmer de Looff
{{ elif [attendees] }}
460 22 Elmer de Looff
  <p>only [attendees:0:name] is attending.</p>
461 22 Elmer de Looff
{{ else }}
462 22 Elmer de Looff
  <p>There are no registered attendees yet.</p>
463 22 Elmer de Looff
{{ endif }}
464 22 Elmer de Looff
</code></pre>
465 22 Elmer de Looff
466 22 Elmer de Looff
For the case where there are several attendees:
467 22 Elmer de Looff
468 22 Elmer de Looff
<pre><code class="python">
469 22 Elmer de Looff
>>> parser.Parse('party.utp', attendees=[
470 22 Elmer de Looff
...    {'name': 'Livingstone'},
471 22 Elmer de Looff
...    {'name': 'Cook'},
472 22 Elmer de Looff
...    {'name': 'Drake'}])
473 22 Elmer de Looff
</code></pre>
474 22 Elmer de Looff
475 22 Elmer de Looff
<pre><code class="html">
476 22 Elmer de Looff
<h1>Party attendees</h1>
477 22 Elmer de Looff
<ol>
478 22 Elmer de Looff
  <li>Livingstone</li>
479 22 Elmer de Looff
  <li>Cook</li>
480 22 Elmer de Looff
  <li>Drake</li>
481 22 Elmer de Looff
</ol>
482 22 Elmer de Looff
</code></pre>
483 22 Elmer de Looff
484 22 Elmer de Looff
For the case where there is one attendee:
485 22 Elmer de Looff
486 22 Elmer de Looff
<pre><code class="python">
487 22 Elmer de Looff
>>> parser.Parse('party.utp', attendees=[{'name': 'Johnny'}])
488 22 Elmer de Looff
</code></pre>
489 22 Elmer de Looff
490 22 Elmer de Looff
<pre><code class="html">
491 22 Elmer de Looff
<h1>Party attendees</h1>
492 22 Elmer de Looff
<p>Only Johnny is attending.</p>
493 22 Elmer de Looff
</code></pre>
494 22 Elmer de Looff
495 22 Elmer de Looff
And in the case where there are no attendees:
496 22 Elmer de Looff
497 22 Elmer de Looff
<pre><code class="python">
498 22 Elmer de Looff
>>> parser.Parse('party.utp', attendees=[])
499 22 Elmer de Looff
</code></pre>
500 22 Elmer de Looff
501 22 Elmer de Looff
<pre><code class="html">
502 22 Elmer de Looff
<h1>Party attendees</h1>
503 22 Elmer de Looff
<p>There are no registered attendees yet.</p>
504 22 Elmer de Looff
</code></pre>
505 22 Elmer de Looff
506 22 Elmer de Looff
h3. Properties of conditional statements
507 22 Elmer de Looff
508 22 Elmer de Looff
* *All template keys must be referenced as proper tag*
509 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.
510 22 Elmer de Looff
* *It is possible to index tags in conditional statements*
511 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.
512 22 Elmer de Looff
* *Referencing a tag or index that doesn't exist raises @TemplateNameError*
513 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:
514 22 Elmer de Looff
* *Statement evaluation is lazy*
515 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.
516 22 Elmer de Looff
517 22 Elmer de Looff
518 11 Elmer de Looff
h2. Template unicode handling
519 11 Elmer de Looff
520 11 Elmer de Looff
Any @unicode@ object found while parsing, will automatically be encoded to UTF-8:
521 11 Elmer de Looff
522 11 Elmer de Looff
<pre><code class="python">
523 11 Elmer de Looff
>>> template = 'Underdark [love] [app]'
524 11 Elmer de Looff
>>> output = parser.ParseString(template, love=u'\u2665', app=u'\N{micro sign}Web')
525 11 Elmer de Looff
>>> output
526 12 Elmer de Looff
'Underdark \xe2\x99\xa5 \xc2\xb5Web'  # The output in its raw UTF-8 representation
527 11 Elmer de Looff
>>> output.decode('UTF8')
528 12 Elmer de Looff
u'Underdark \u2665 \xb5Web'           # The output converted to a Unicode object
529 19 Elmer de Looff
>>> print output
530 19 Elmer de Looff
Underdark ♥ µWeb                      # And the printed UTF-8 as we desired it.
531 14 Elmer de Looff
</code></pre>