Project

General

Profile

TemplateParser » History » Version 46

Elmer de Looff, 2012-06-01 13:51

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