TemplateParser » History » Version 43
Elmer de Looff, 2012-06-01 13:27
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 | 38 | 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 | 42 | Elmer de Looff | h3. Tag 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: "Hello" |
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: "Hello" |
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* – 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 |
391 | 1 | Elmer de Looff | * _default_ – 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* – 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* – 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 | 23 | Elmer de Looff | h2. TemplateLoop |
436 | 11 | Elmer de Looff | |
437 | 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). |
438 | 1 | Elmer de Looff | |
439 | 23 | Elmer de Looff | h3. Syntax and properties |
440 | 23 | Elmer de Looff | |
441 | 23 | Elmer de Looff | *Syntax: @{{ for local_var in [collection] }}@* |
442 | 20 | Elmer de Looff | * The double accolades (curly braces) indicate the beginning and end of the construct; |
443 | 20 | Elmer de Looff | * The @for@ keyword indicates the structure to execute; |
444 | 20 | Elmer de Looff | * @local_var@ is the name which references the loop variable; |
445 | 20 | Elmer de Looff | * @[collection]@ is the tag that provides the iteratable. |
446 | 20 | Elmer de Looff | |
447 | 20 | Elmer de Looff | *Properties* |
448 | 20 | Elmer de Looff | * The local name is stated without brackets (as it's no tag itself) |
449 | 1 | Elmer de Looff | * When it needs to be placed in the output, the local name should have brackets (like any other tag) |
450 | 20 | Elmer de Looff | * *N.B.* The local variable does _not_ bleed into the outer scope after the loop has completed. |
451 | 20 | Elmer de Looff | It is therefore possible (though not recommended) to name the loop variable after the iterable: @{{ for collection in [collection] }}@. |
452 | 20 | Elmer de Looff | |
453 | 23 | Elmer de Looff | h3. Example of a @TemplateLoop@ |
454 | 20 | Elmer de Looff | |
455 | 20 | Elmer de Looff | <pre><code class="html"> |
456 | 20 | Elmer de Looff | <html> |
457 | 20 | Elmer de Looff | <body> |
458 | 20 | Elmer de Looff | <ul> |
459 | 20 | Elmer de Looff | {{ for name in [presidents] }} |
460 | 20 | Elmer de Looff | <li>President [name]</li> |
461 | 20 | Elmer de Looff | {{ endfor }} |
462 | 20 | Elmer de Looff | </ul> |
463 | 20 | Elmer de Looff | </body> |
464 | 20 | Elmer de Looff | </html> |
465 | 20 | Elmer de Looff | </code></pre> |
466 | 20 | Elmer de Looff | |
467 | 20 | Elmer de Looff | <pre><code class="python"> |
468 | 20 | Elmer de Looff | >>> parser.Parse('rushmore.utp', presidents=['Washington', 'Jefferson', 'Roosevelt', 'Lincoln']) |
469 | 20 | Elmer de Looff | </code></pre> |
470 | 20 | Elmer de Looff | |
471 | 20 | Elmer de Looff | <pre><code class="html"> |
472 | 20 | Elmer de Looff | <html> |
473 | 20 | Elmer de Looff | <body> |
474 | 1 | Elmer de Looff | <ul> |
475 | 1 | Elmer de Looff | <li>President Washington</li> |
476 | 1 | Elmer de Looff | <li>President Jefferson</li> |
477 | 1 | Elmer de Looff | <li>President Roosevelt</li> |
478 | 1 | Elmer de Looff | <li>President Lincoln</li> |
479 | 1 | Elmer de Looff | </ul> |
480 | 1 | Elmer de Looff | </body> |
481 | 1 | Elmer de Looff | </html> |
482 | 1 | Elmer de Looff | </code></pre> |
483 | 1 | Elmer de Looff | |
484 | 1 | Elmer de Looff | h2. Inlining templates |
485 | 21 | Elmer de Looff | |
486 | 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. |
487 | 21 | Elmer de Looff | |
488 | 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. |
489 | 21 | Elmer de Looff | |
490 | 21 | Elmer de Looff | First, we will define our inline template, @'inline_hello.utp'@: |
491 | 21 | Elmer de Looff | |
492 | 21 | Elmer de Looff | <pre><code class="html"> |
493 | 21 | Elmer de Looff | <p>Hello [name]</p> |
494 | 21 | Elmer de Looff | </code></pre> |
495 | 21 | Elmer de Looff | |
496 | 21 | Elmer de Looff | Secondly, our main template, @'hello.utp'@: |
497 | 21 | Elmer de Looff | |
498 | 21 | Elmer de Looff | <pre><code class="html"> |
499 | 21 | Elmer de Looff | <h1>Greetings</h1> |
500 | 21 | Elmer de Looff | {{ inline inline_hello.utp }} |
501 | 21 | Elmer de Looff | </code></pre> |
502 | 21 | Elmer de Looff | |
503 | 21 | Elmer de Looff | Then we parse the template: |
504 | 21 | Elmer de Looff | |
505 | 21 | Elmer de Looff | <pre><code class="python"> |
506 | 21 | Elmer de Looff | >>> parser.Parse('hello.utp', name='Dr John') |
507 | 21 | Elmer de Looff | </code></pre> |
508 | 21 | Elmer de Looff | |
509 | 21 | Elmer de Looff | <pre><code class="html"> |
510 | 21 | Elmer de Looff | <h1>Greetings</h1> |
511 | 21 | Elmer de Looff | <p>Hello Dr John</p> |
512 | 21 | Elmer de Looff | </code></pre> |
513 | 11 | Elmer de Looff | |
514 | 11 | Elmer de Looff | h2. Conditional statements |
515 | 11 | Elmer de Looff | |
516 | 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: |
517 | 22 | Elmer de Looff | |
518 | 22 | Elmer de Looff | <pre><code class="html"> |
519 | 22 | Elmer de Looff | <h1>Party attendees</h1> |
520 | 22 | Elmer de Looff | {{ if len([attendees]) > 1 }} |
521 | 22 | Elmer de Looff | <ol> |
522 | 22 | Elmer de Looff | {{ for attendee in [attendees] }} |
523 | 22 | Elmer de Looff | <li>[attendee:name]</li> |
524 | 22 | Elmer de Looff | {{ endfor }} |
525 | 22 | Elmer de Looff | </ol> |
526 | 22 | Elmer de Looff | {{ elif [attendees] }} |
527 | 22 | Elmer de Looff | <p>only [attendees:0:name] is attending.</p> |
528 | 22 | Elmer de Looff | {{ else }} |
529 | 22 | Elmer de Looff | <p>There are no registered attendees yet.</p> |
530 | 22 | Elmer de Looff | {{ endif }} |
531 | 22 | Elmer de Looff | </code></pre> |
532 | 22 | Elmer de Looff | |
533 | 22 | Elmer de Looff | For the case where there are several attendees: |
534 | 22 | Elmer de Looff | |
535 | 22 | Elmer de Looff | <pre><code class="python"> |
536 | 22 | Elmer de Looff | >>> parser.Parse('party.utp', attendees=[ |
537 | 22 | Elmer de Looff | ... {'name': 'Livingstone'}, |
538 | 22 | Elmer de Looff | ... {'name': 'Cook'}, |
539 | 22 | Elmer de Looff | ... {'name': 'Drake'}]) |
540 | 22 | Elmer de Looff | </code></pre> |
541 | 22 | Elmer de Looff | |
542 | 22 | Elmer de Looff | <pre><code class="html"> |
543 | 22 | Elmer de Looff | <h1>Party attendees</h1> |
544 | 22 | Elmer de Looff | <ol> |
545 | 22 | Elmer de Looff | <li>Livingstone</li> |
546 | 22 | Elmer de Looff | <li>Cook</li> |
547 | 22 | Elmer de Looff | <li>Drake</li> |
548 | 22 | Elmer de Looff | </ol> |
549 | 22 | Elmer de Looff | </code></pre> |
550 | 22 | Elmer de Looff | |
551 | 22 | Elmer de Looff | For the case where there is one attendee: |
552 | 22 | Elmer de Looff | |
553 | 22 | Elmer de Looff | <pre><code class="python"> |
554 | 22 | Elmer de Looff | >>> parser.Parse('party.utp', attendees=[{'name': 'Johnny'}]) |
555 | 22 | Elmer de Looff | </code></pre> |
556 | 22 | Elmer de Looff | |
557 | 22 | Elmer de Looff | <pre><code class="html"> |
558 | 22 | Elmer de Looff | <h1>Party attendees</h1> |
559 | 22 | Elmer de Looff | <p>Only Johnny is attending.</p> |
560 | 22 | Elmer de Looff | </code></pre> |
561 | 22 | Elmer de Looff | |
562 | 22 | Elmer de Looff | And in the case where there are no attendees: |
563 | 22 | Elmer de Looff | |
564 | 22 | Elmer de Looff | <pre><code class="python"> |
565 | 22 | Elmer de Looff | >>> parser.Parse('party.utp', attendees=[]) |
566 | 22 | Elmer de Looff | </code></pre> |
567 | 22 | Elmer de Looff | |
568 | 22 | Elmer de Looff | <pre><code class="html"> |
569 | 22 | Elmer de Looff | <h1>Party attendees</h1> |
570 | 22 | Elmer de Looff | <p>There are no registered attendees yet.</p> |
571 | 22 | Elmer de Looff | </code></pre> |
572 | 22 | Elmer de Looff | |
573 | 22 | Elmer de Looff | h3. Properties of conditional statements |
574 | 22 | Elmer de Looff | |
575 | 22 | Elmer de Looff | * *All template keys must be referenced as proper tag* |
576 | 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. |
577 | 22 | Elmer de Looff | * *It is possible to index tags in conditional statements* |
578 | 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. |
579 | 22 | Elmer de Looff | * *Referencing a tag or index that doesn't exist raises @TemplateNameError* |
580 | 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: |
581 | 22 | Elmer de Looff | * *Statement evaluation is lazy* |
582 | 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. |
583 | 22 | Elmer de Looff | |
584 | 22 | Elmer de Looff | |
585 | 11 | Elmer de Looff | h2. Template unicode handling |
586 | 11 | Elmer de Looff | |
587 | 11 | Elmer de Looff | Any @unicode@ object found while parsing, will automatically be encoded to UTF-8: |
588 | 11 | Elmer de Looff | |
589 | 11 | Elmer de Looff | <pre><code class="python"> |
590 | 11 | Elmer de Looff | >>> template = 'Underdark [love] [app]' |
591 | 11 | Elmer de Looff | >>> output = parser.ParseString(template, love=u'\u2665', app=u'\N{micro sign}Web') |
592 | 11 | Elmer de Looff | >>> output |
593 | 12 | Elmer de Looff | 'Underdark \xe2\x99\xa5 \xc2\xb5Web' # The output in its raw UTF-8 representation |
594 | 11 | Elmer de Looff | >>> output.decode('UTF8') |
595 | 12 | Elmer de Looff | u'Underdark \u2665 \xb5Web' # The output converted to a Unicode object |
596 | 19 | Elmer de Looff | >>> print output |
597 | 19 | Elmer de Looff | Underdark ♥ µWeb # And the printed UTF-8 as we desired it. |
598 | 14 | Elmer de Looff | </code></pre> |