µWeb documentation - TemplateParser

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:

First though, to help with understanding the TemplateParser, a minimal size template document:

1Hello [title] [name]

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.

Using TemplateParser inside µWeb

Within the default µWeb PageMaker, there is a parser property, which provides a 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.

An example of TemplateParser to create a complete response:

1import uweb
2import time
3
4class PageMaker(uweb.PageMaker):
5  def VersionPage(self):
6    return self.parser.Parse(
7      'version.utp', year=time.strftime('%Y'), version=uweb.__version__)

The example template for the above file could look something like this:

 1<!DOCTYPE html>
 2<html>
 3  <head>
 4    <title>µWeb version info</title>
 5  </head>
 6  <body>
 7    <p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
 8  </body>
 9</html>

And would result in the following output:

 1<!DOCTYPE html>
 2<html>
 3  <head>
 4    <title>µWeb version info</title>
 5  </head>
 6  <body>
 7    <p>µWeb version 0.12 - Copyright 2010-2012 Underdark</p>
 8  </body>
 9</html>

With these initial small demonstrations behind us, let's explore the TemplateParser further

Template class

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.

Creating a template

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 syntax:

1>>> import templateparser
2>>> template = templateparser.Template('Hello [title] [name]')
3>>> template
4Template([TemplateText('Hello '), TemplateTag('[title]'), TemplateText(' '), TemplateTag('[name]')])

Above can be seen the various parts of the template, which will be combined to output once parsed.

Loading a template from file

The Template class provides a classmethod called FromFile, which loads the template at the path.

Loading a template named example.utp from the current working directory:

1>>> import templateparser
2>>> template = templateparser.Template.FromFile('example.utp')
3>>> template
4Template([TemplateText('Hello '), TemplateTag('[title]'), TemplateText(' '), TemplateTag('[name]')])

Parsing a template

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.

1>>> import templateparser
2>>> template = templateparser.Template('Hello [title] [name]')
3>>> template.Parse(title='sir')
4'Hello sir [name]'

Parser class

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.

Loading templates

Creating a parser object, and loading the 'example.utp' template from the 'templates' directory works like this:

1>>> import templateparser
2>>> # This sets the 'templates' directory as the search path for AddTemplate
3>>> parser = templateparser.Parser('templates')
4>>> # Loads the 'templates/example.utp' and stores it as 'example.utp':
5>>> parser.AddTemplate('example.utp')
6>>> parser.Parse('example.utp', title='mister', name='Bob Dobalina')
7'Hello mister Bob Dobalina'

The AddTemplate method takes a second optional argument, which allows us to give the template a different name in the cache:

1>>> parser = templateparser.Parser('templates')
2>>> parser.AddTemplate('example.utp', name='greeting')
3>>> parser.Parse('greeting', title='mister', name='Bob Dobalina')
4'Hello mister Bob Dobalina'

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.

Template cache and auto-loading

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:

1>>> import templateparser
2>>> parser = templateparser.Parser('templates')
3>>> 'example.utp' in parser
4False       # Since we haven't loaded it, the template it not in the parser
5>>> parser
6Parser({})  # The parser is empty (has no cached templates)

Attempting to parse a template that doesn't exist in the parser cache triggers an automatic load:

1>>> parser['example.utp'].Parse(title='mister', name='Bob Dobalina')
2'Hello mister Bob Dobalina'
3>>> 'example.utp' in parser
4True
5>>> parser
6Parser({'example.utp': Template([TemplateText('Hello '), TemplateTag('[title]'),
7                                 TemplateText(' '), TemplateTag('[name]')])})

If these cannot be found, TemplateReadError is raised:

 1>>> import templateparser
 2>>> parser = templateparser.Parser('templates')
 3>>> parser['bad_template.utp'].Parse(failure='imminent')
 4Traceback (most recent call last):
 5  File "<stdin>", line 1, in <module>
 6  File "/var/lib/underdark/libs/uweb/templateparser.py", line 147, in __getitem__
 7    self.AddTemplate(template)
 8  File "/var/lib/underdark/libs/uweb/templateparser.py", line 171, in AddTemplate
 9    raise TemplateReadError('Could not load template %r' % template_path)
10underdark.libs.uweb.templateparser.TemplateReadError: Could not load template 'templates/bad_template.utp'

Parse and ParseString methods

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:

1>>> import templateparser
2>>> parser = templateparser.Parser('templates')
3>>> parser.Parse('example.utp', title='mister', name='Bob Dobalina')
4'Hello mister Bob Dobalina'
5>>> parser.ParseString('Hello [title] [name]', title='mister', name='Bob Dobalina')
6'Hello mister Bob Dobalina'

Templating language syntax

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: All examples will consist of three parts:
  1. The example template
  2. The python invocation string (the template will be named 'example.utp')
  3. The resulting output (as source, not as parsed HTML)

Simple tags

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

The example below is a repeat of the example how to use TemplateParser inside µWeb, and shows the template result:

 1<!DOCTYPE html>
 2<html>
 3  <head>
 4    <title>µWeb version info</title>
 5  </head>
 6  <body>
 7    <p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
 8    <p>
 9      This [paragraph] is not replaced because there is no
10      paragraph argument provided to the parser.
11    </p>
12  </body>
13</html>
1>>> parser.Parse('version.utp', year=time.strftime('%Y'), version=uweb.__version__)
 1<!DOCTYPE html>
 2<html>
 3  <head>
 4    <title>µWeb version info</title>
 5  </head>
 6  <body>
 7    <p>µWeb version 0.11 - Copyright 2010-212 Underdark</p>
 8    <p>
 9      This [paragraph] is not replaced because there is no
10      paragraph argument provided to the parser.
11    </p>
12  </body>
13</html>

Tag indexing

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 (":"):

List/tuple index addressing

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.

1This is [var:0] [var:1].
1>>> parser.Parse('message.utp', var=('delicious', 'spam'))
1This is delicious spam.

Dictionary key addressing

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.

1This is [var:adjective] [var:noun].
1>>> parser.Parse('message.utp', var={'adjective': 'delicious', 'noun': 'spam'})
1This is delicious spam.

Attribute name addressing

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.

1This is [var:adjective] [var:noun].
1>>> class Struct(object):
2...   pass
3...
4>>> var = Struct()
5>>> var.adjective = 'delicious'
6>>> var.noun = 'spam'
7>>> parser.Parse('message.utp', var=var)
1This is delicious spam.

Lookup order

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:

  1. If the needle is parseable as integer, it will first be used as an index. This will also work for mappings with numeric keys;
  2. If the above fails, the needle is assumed to be a string-like mapping key, and this is attempted
  3. If the above fails, the needle is used as an attribute name;
  4. If all of the above fail, TemplateKeyError is raised, as the needle could not be found on the object.

Nested indexes

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:

1This is a variable from [some:levels:down:1].
1>>> class Struct(object):
2...   pass
3...
4>>> var = Struct()
5>>> var.levels = {'down': ('the sky', 'the depths')}
6>>> parser.Parse('message.utp', some=var)
1This is a variable from the depths.

Tag functions

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.

Default html escaping

Using a tag function is a fairly straightforward process, just add the name of the function after the tagname, separated by a pipe ( | ):

1And he said: [message|html]
1>>> parser.Parse('message.utp', message='"Hello"')
1And he said: &quot;Hello&quot;

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:

1And he said: [message]
1>>> parser.Parse('message.utp', message='"Hello"')
1And he said: &quot;Hello&quot;

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:

1And he said: [message|raw]
1>>> parser.Parse('message.utp', message='"Hello"')
1And he said: "Hello"

Predefined tag functions

Adding custom functions

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:

1>>> from uweb import templateparser
2>>> parser = templateparser.Parser()
3>>> parser.RegisterFunction('len', len)
4>>> template = 'The number of people in this group: [people|len].'
5>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
6'The number of people in this group: 4.'

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.

Function chaining

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:

Setting up the parser and registering our tag function

1>>> from uweb import templateparser
2>>> parser = templateparser.Parser()
3>>> parser.RegisterFunction('first', lambda x: x[0])

Working just one tag function returns the first element from the list:

1>>> template = 'The first element of list: [elements|first].'
2>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
3'The first element of list: Eric.'

Repeating the function on the string returns the first character from that string:

1>>> template = 'The first element of the first element of list: [elements|first|first].'
2>>> parser.ParseString(template, elements=['Eric', 'Michael', 'John', 'Terry'])
3'The first element of the first element of list: E.'

TemplateLoop

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).

Syntax and properties

Syntax: {{ for local_var in [collection] }} Properties

Example of a TemplateLoop

 1<html>
 2  <body>
 3    <ul>
 4    {{ for name in [presidents] }}
 5      <li>President [name]</li>
 6    {{ endfor }}
 7    </ul>
 8  </body>
 9</html>
1>>> parser.Parse('rushmore.utp', presidents=['Washington', 'Jefferson', 'Roosevelt', 'Lincoln'])
 1<html>
 2  <body>
 3    <ul>
 4      <li>President Washington</li>
 5      <li>President Jefferson</li>
 6      <li>President Roosevelt</li>
 7      <li>President Lincoln</li>
 8    </ul>
 9  </body>
10</html>

Inlining templates

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.

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.

First, we will define our inline template, 'inline_hello.utp':

1<p>Hello [name]</p>

Secondly, our main template, 'hello.utp':

1<h1>Greetings</h1>
2{{ inline inline_hello.utp }}

Then we parse the template:

1>>> parser.Parse('hello.utp', name='Dr John')
1<h1>Greetings</h1>
2<p>Hello Dr John</p>

Conditional statements

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:

 1<h1>Party attendees</h1>
 2{{ if len([attendees]) > 1 }}
 3  <ol>
 4    {{ for attendee in [attendees] }}
 5    <li>[attendee:name]</li>
 6    {{ endfor }}
 7  </ol>
 8{{ elif [attendees] }}
 9  <p>only [attendees:0:name] is attending.</p>
10{{ else }}
11  <p>There are no registered attendees yet.</p>
12{{ endif }}

For the case where there are several attendees:

1>>> parser.Parse('party.utp', attendees=[
2...    {'name': 'Livingstone'},
3...    {'name': 'Cook'},
4...    {'name': 'Drake'}])
1<h1>Party attendees</h1>
2<ol>
3  <li>Livingstone</li>
4  <li>Cook</li>
5  <li>Drake</li>
6</ol>

For the case where there is one attendee:

1>>> parser.Parse('party.utp', attendees=[{'name': 'Johnny'}])
1<h1>Party attendees</h1>
2<p>Only Johnny is attending.</p>

And in the case where there are no attendees:

1>>> parser.Parse('party.utp', attendees=[])
1<h1>Party attendees</h1>
2<p>There are no registered attendees yet.</p>

Properties of conditional statements

Template unicode handling

Any unicode object found while parsing, will automatically be encoded to UTF-8:

1>>> template = 'Underdark [love] [app]'
2>>> output = parser.ParseString(template, love=u'\u2665', app=u'\N{micro sign}Web')
3>>> output
4'Underdark \xe2\x99\xa5 \xc2\xb5Web'  # The output in its raw UTF-8 representation
5>>> output.decode('UTF8')
6u'Underdark \u2665 \xb5Web'           # The output converted to a Unicode object
7>>> print output
8Underdark  µWeb                      # And the printed UTF-8 as we desired it.
blog comments powered by Disqus