Edgewall Software

Version 17 (modified by cmlenz, 18 years ago) (diff)

--

Markup Templates

The most important feature provided by the Markup package is a template engine.

Templates are XML files of some kind (such as XHTML) that include processing directives (elements or attributes identified by a separate namespace) that affect how the template is rendered, and template expressions that are dynamically substituted by variable data.

Table of Contents

  1. Template Context
  2. Template Expressions
  3. Template Directives
    1. py:content
    2. py:replace
    3. py:attrs
    4. py:strip
    5. py:if
    6. py:choose / py:when / py:otherwise
    7. py:for
    8. py:def
    9. py:match
  4. Template Includes

Template Context

Rendering a template boils down to applying one or more template files to some input data. This input data is represented as a Context? object in Markup. A context is basically a stack of dictionaries that manages the namespace scopes during processing: for example, some variable may be able under a given name inside a loop, but not outside of that loop.

Regardless of its stack behavior during templating processing, a context is usually initialized from a single dictionary:

>>> from markup.template import Context
>>> ctxt = Context(title='Hello, world!')
>>> print ctxt
[{'title': 'Hello, world!'}]

This data is then made available for use inside the template:

>>> from markup.template import Template
>>> tmpl = Template('<h1>$title</h1>')
>>> print tmpl.generate(ctxt)
<h1>Hello, world!</h1>

Here the title variable in the context is accessed through a template expression, discussed below.

Template Expressions

Python expressions can be used in text and attribute values. An expression is substituted with the result of its evaluation against the template data. Expressions need to prefixed with a dollar sign ($) and usually enclosed in curly braces ({…}).

If the expression starts with a letter and contains only letters and digits, the curly braces may be omitted. In all other cases, the braces are required so that the template processors knows where the expression ends:

>>> from markup.template import Context, Template
>>> tmpl = Template('<em>${items[0].capitalize()} item</em>')
>>> print tmpl.generate(Context(items=['first', 'second']))
<em>First item</em>

Expressions support the full power of Python. In addition, it is possible to:

  • access items in a dictionary using “dotted notation” (i.e. as if they were attributes)
  • access attributes using item access (i.e. as if they were items in a dictionary)
>>> from markup.template import Context, Template
>>> tmpl = Template('<em>${dict.foo}</em>')
>>> print tmpl.generate(Context(dict={'foo': 'bar'}))
<em>bar</em>

Template Directives

Directives are elements and/or attributes in the template that are identified by the namespace http://markup.edgewall.org/. They can affect how the template is rendered in a number of ways: Markup provides directives for conditionals and looping, among others.

To use directives in a template, the namespace should be declared, which is usually done on the root element:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://markup.edgewall.org/"
      lang="en">
  ...
</html>

In this example, the default namespace is set to the XHTML namespace, and the namespace for Markup directives is bound to the prefix “py”.

All directives can be applied as attributes, and some can also be used as elements. The if directives for conditionals, for example, can be used in both ways:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://markup.edgewall.org/"
      lang="en">
  ...
  <div py:if="foo">
    <p>Bar</p>
  </div>
  ...
</html>

This is basically equivalent to the following:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://markup.edgewall.org/"
      lang="en">
  ...
  <py:if test="foo">
    <div>
      <p>Bar</p>
    </div>
  </py:if>
  ...
</html>

The rationale behind the second form is that directives not always map naturally to elements in the template. In such cases, the py:strip directive can be used to strip off the unwanted element, or the directive can simply be used as an element.

py:content

This directive replaces any nested content with the result of evaluating the expression.

<ul xmlns:py="http://markup.edgewall.org/">
  <li py:content="bar">Hello</li>
</ul>

Given bar='Bye' in the context data, this would produce:

<ul>
  <li>Bye</li>
</ul>

This directive can only be used as an attribute.

py:replace

This directive replaces the element itself with the result of evaluating the expression.

<div xmlns:py="http://markup.edgewall.org/">
  <span py:replace="bar">Hello</span>
</div>

Given bar='Bye' in the context data, this would produce:

<div>
  Bye
</div>

This directive can only be used as an attribute.

py:attrs

This directive adds, modifies or removes attributes from the element.

<ul xmlns:py="http://markup.edgewall.org/">
  <li py:attrs="foo">Bar</li>
</ul>

Given foo={'class': 'collapse'} in the template context, this would produce:

<ul>
  <li class="collapse">Bar</li>
</ul>

This directive can only be used as an attribute.

py:strip

py:if

The element is only rendered if the expression evaluates to a truth value.

py:choose / py:when / py:otherwise

This set of directives provides advanced contional processing: one of several alternatives is rendered depending on which of the py:when directives evaluates to a truth value.

<div xmlns:py="http://markup.edgewall.org/" py:choose="">
  <span py:when="0 == 1">0</span>
  <span py:when="1 == 1">1</span>
  <span py:otherwise="">2</span>
</div>

This would produce the following output:

<div>
  <span>1</span>
</div>

py:for

The element is repeated for every item in an iterable:

<ul xmlns:py="http://markup.edgewall.org/">
  <li py:for="item in items">${item}</li>
</ul>

Given items=[1, 2, 3] in the context data, this would produce:

<ul>
  <li>1</li><li>2</li><li>3</li>
</ul>

This directive can also be used as an element:

<ul xmlns:py="http://markup.edgewall.org/">
  <py:for each="item in items">
    <li>${item}</li>
  </py:for>
</ul>

py:def

py:match

Template Includes

To reuse common snippets of template code, you can include other files using XInclude.

For this, you need to declare the XInclude namespace (commonly bound to the prefix "xi") and use the <xi:include> element where you want the external file to be pulled in:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://markup.edgewall.org/"
      xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include href="base.html" />
  ...
</html>

Include paths are relative to the filename of the template currently being processed. So if the example above was in the file "myapp/index.html" (relative to the template search path), the XInclude processor would look for the included file at "myapp/base.html". You can also use Unix-style relative paths, for example "../base.html" to look in the parent directory.

By default, an error will be raised if an included file is not found. If that's not what you want, you can specify fallback content that should be used if the include fails. For example, to to make the include above fail silently, you'd write:

  <xi:include href="base.html"><xi:fallback /></xi:include>

See the XInclude specification for more about fallback content. Note though that Markup currently only supports a small subset of XInclude.