Tips and tricks
Avoid naming conflicts between nodes
If two or more nodes have the same name, as long as each has a different parent they will all appear within the template object model independent of one other. For example, the following template defines three different nodes all named 'link':
<p><a node="con:link">LINK</a></p>
<ul node="-con:navbar">
<li node="rep:link"><a node="con:link">LINK</a></li>
</ul>
While this may result in some confusing-looking controller code, it is a valid HTML template and will parse without problem.
However, if two or more identically named nodes have the same parent then a ParseError
will occur as sibling nodes can never share the same name. For example, the following template markup will be rejected as it contains three sibling nodes all named 'item':
<ul>
<li node="rep:item">ITEM</li>
<li node="rep:item">ITEM</li>
<li node="rep:item">ITEM</li>
</ul>
If the duplicate elements have been included solely for preview purposes, just add Deleted (del
) directives to each one to prevent them appearing in either the template object model or the rendered output:
<ul>
<li node="rep:item">ITEM</li>
<li node="del:">ITEM</li>
<li node="del:">ITEM</li>
</ul>
The following template will also be rejected as it contains two sibling nodes both named 'title':
<html>
<head>
...
<title node="con:title">TITLE</title>
</head>
<body>
<h1 node="con:title">TITLE</title>
...
</body>
</html>
Since the title
and h1
elements should both appear in the rendered output, you will have to rename one or both to avoid confusion (remember to update the corresponding controller code as well):
<html>
<head>
...
<title node="con:title1">TITLE</title>
</head>
<body>
<h1 node="con:title2">TITLE</title>
...
</body>
</html>
This restriction does not apply to Separators, of course, since a sep
directive does not create a new node but merely indicates a section of markup that should appear between repeated instances of an existing Repeater node. In fact, a ParseError
will occur if the Separator's name does not exactly match the name of a preceding Repeater node. For example, this Separator is valid:
<a node="rep:link">LINK</a> <span node="-sep:link">/</a>
but these are not:
<a node="rep:link1">LINK</a> <span node="-sep:link2">/</a>
<span node="-sep:link">/</a> <a node="rep:link">LINK</a>
Modifing part of a node's content
There may be times when you want modify only part of an element's content, for example, to replace just the [NAME]
part of <title>About [NAME]</title>
. There are a couple of ways to do this.
One solution is to wrap the part of the element's content you want to modify in a span
element and add a con
directive to that:
<title>About <span node="-con:name">[NAME]</span></title>
While this particular template may not be standards-compilant HTML, as long as the con
directive is prefixed an 'omit tags' modifier (-con:...
), the <span>
and </span>
tags will be omitted when the template is rendered, ensuring valid output.
The other option is to add a con
directive to the <title>
element, then modify the node's existing content using (e.g.) Python's standard string substitution mechanism:
<title node="con:title">About {name}</title>
node.title.text = node.title.text.format(name=somevalue)
If you need to preserve existing HTML tags within the original and/or new content, you will have to use the html
property instead of the text
property, so take care to ensure any new data is properly sanitized before insertion. For example:
<h1 node="con:title"><em>About</em> {name}</h1>
name = 'Smith & Jones'
node.content.html = node.content.html.format(name=encodeentity(name))
will ensure any reserved characters (&
, <
, >
, "
) are properly escaped as HTML entities:
<h1><em>About</em> Smith & Jones</h1>
htmltemplate's encodeentity
function is sufficient to replace reserved characters with their HTML entity equivalents, though if your data requires heavier sanitation (e.g. to remove ASCII control characters or to strip <script>
tags from untrusted HTML strings) you will have to implement this yourself.
Modifying vs replacing nodes
When setting a node's content, take care to write:
node.foo.text = somevalue
or:
node.foo.html = somevalue
These assignments will replace the node's content to a new plain text or HTML string, which is usually what you want.
If you omit the text
/html
property name and write the following instead, this does not replace the node's content but instead replaces the node itself:
node.foo = somevalue
This feature can be useful if you wish to reuse a previously rendered node or replace part of one template's object model with part or all of another (component-based templating). For example, the docgen.py
script contains two navigation bars at the top and bottom of the page. Rather than render the same information twice, the template's controller function renders the topnav
node then grafts it over the unrendered bottomnav
node as well:
def render_page(node, title, heading, content, navlinks):
...
node.topnav.link.repeat(page_navlink, navlinks)
node.bottomnav = node.topnav
Note that the new value must be a subclass of Node
(i.e. Container
, Repeater
, Template
or a third-party subclass), otherwise a TypeError
will be raised. (This helps to catch errors where the text
/html
property name is omitted by accident.)
Customizing the rendering process
Copying a Template
object before manipulating and rendering the copy allows the original object to be reused any number of times. While not essential, this is more efficient than re-compiling the template HTML every time. The simplest way to do this is by passing a callback function as the Template.render()
method's first argument: the render
method will automatically copy the original Template
object and pass it to the controller function to manipulate before rendering the result.
If greater flexibility is required, each of these steps – copy, manipulate, render – can be performed separately:
Create a copy of the original template object:
node = template.copy()
Call the controller function, passing it the copied template object and values to insert:
render_template(node, title, links)
Render the copied template object:
print node.render()
See the demo1alt_quote.py
and demo8_multi_step_rendering.py
scripts in the sample
folder for examples. In particular, the multi-step rendering script demonstrates how splitting these steps can improve efficiency when generating a series of pages that share some common elements such as navigation bars.