cmlenz@500: .. -*- mode: rst; encoding: utf-8 -*-
cmlenz@500:
cmlenz@500: ========================
cmlenz@500: Genshi Templating Basics
cmlenz@500: ========================
cmlenz@500:
cmlenz@500: Genshi provides a template engine that can be used for generating either
cmlenz@500: markup (such as HTML_ or XML_) or plain text. While both share some of the
cmlenz@500: syntax (and much of the underlying implementation) they are essentially
cmlenz@500: separate languages.
cmlenz@500:
cmlenz@500: .. _html: http://www.w3.org/html/
cmlenz@500: .. _xml: http://www.w3.org/XML/
cmlenz@500:
cmlenz@500: This document describes the common parts of the template engine and will be most
cmlenz@500: useful as reference to those developing Genshi templates. Templates are XML or
cmlenz@500: plain text files that include processing directives_ that affect how the
cmlenz@500: template is rendered, and template expressions_ that are dynamically substituted
cmlenz@500: by variable data.
cmlenz@500:
cmlenz@500:
cmlenz@500: .. contents:: Contents
cmlenz@500: :depth: 3
cmlenz@500: .. sectnum::
cmlenz@500:
cmlenz@500: --------
cmlenz@500: Synopsis
cmlenz@500: --------
cmlenz@500:
cmlenz@500: A Genshi *markup template* is a well-formed XML document with embedded Python
cmlenz@500: used for control flow and variable substitution. Markup templates should be
cmlenz@500: used to generate any kind of HTML or XML output, as they provide many advantages
cmlenz@500: over simple text-based templates (such as automatic escaping of strings).
cmlenz@500:
cmlenz@820: The following illustrates a very basic Genshi markup template:
cmlenz@820:
cmlenz@820: .. code-block:: genshi
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500: This is replaced.
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500: These are some of my favorite fruits:
cmlenz@500:
cmlenz@500: -
cmlenz@500: I like ${fruit}s
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500: This example shows:
cmlenz@500:
cmlenz@500: (a) a Python code block, using a processing instruction
cmlenz@500: (b) the Genshi namespace declaration
cmlenz@500: (c) usage of templates directives (``py:content`` and ``py:for``)
cmlenz@500: (d) an inline Python expression (``${fruit}``).
cmlenz@500:
cmlenz@820: The template would generate output similar to this:
cmlenz@820:
cmlenz@820: .. code-block:: genshi
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500: A Genshi Template
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500: These are some of my favorite fruits:
cmlenz@500:
cmlenz@500: - I like apples
cmlenz@500: - I like oranges
cmlenz@500: - I like kiwis
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500: A *text template* is a simple plain text document that can also contain embedded
cmlenz@500: Python code. Text templates can be used to generate simple *non-markup* text
cmlenz@820: formats, such as the body of an plain text email. For example:
cmlenz@820:
cmlenz@820: .. code-block:: genshitext
cmlenz@500:
cmlenz@500: Dear $name,
cmlenz@500:
cmlenz@500: These are some of my favorite fruits:
cmlenz@500: #for fruit in fruits
cmlenz@500: * $fruit
cmlenz@500: #end
cmlenz@500:
cmlenz@500:
cmlenz@500: ----------
cmlenz@500: Python API
cmlenz@500: ----------
cmlenz@500:
cmlenz@500: The Python code required for templating with Genshi is generally based on the
cmlenz@500: following pattern:
cmlenz@500:
cmlenz@500: * Attain a ``MarkupTemplate`` or ``TextTemplate`` object from a string or
cmlenz@500: file-like object containing the template source. This can either be done
cmlenz@500: directly, or through a ``TemplateLoader`` instance.
cmlenz@500: * Call the ``generate()`` method of the template, passing any data that should
cmlenz@500: be made available to the template as keyword arguments.
cmlenz@500: * Serialize the resulting stream using its ``render()`` method.
cmlenz@500:
cmlenz@820: For example:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@500:
cmlenz@500: >>> from genshi.template import MarkupTemplate
cmlenz@500: >>> tmpl = MarkupTemplate('Hello, $name!
')
cmlenz@500: >>> stream = tmpl.generate(name='world')
cmlenz@820: >>> print stream.render('xhtml')
cmlenz@500: Hello, world!
cmlenz@500:
cmlenz@820: .. note:: See the Serialization_ section of the `Markup Streams`_ page for
cmlenz@820: information on configuring template output options.
cmlenz@820:
cmlenz@820: Using a text template is similar:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@500:
cmlenz@500: >>> from genshi.template import TextTemplate
cmlenz@500: >>> tmpl = TextTemplate('Hello, $name!')
cmlenz@500: >>> stream = tmpl.generate(name='world')
cmlenz@820: >>> print stream
cmlenz@500: Hello, world!
cmlenz@500:
cmlenz@820: .. note:: If you want to use text templates, you should consider using the
cmlenz@820: ``NewTextTemplate`` class instead of simply ``TextTemplate``. See
cmlenz@820: the `Text Template Language`_ page.
cmlenz@500:
cmlenz@500: .. _serialization: streams.html#serialization
cmlenz@820: .. _`Text Template Language`: text-templates.html
cmlenz@500: .. _`Markup Streams`: streams.html
cmlenz@500:
cmlenz@500: Using a template loader provides the advantage that “compiled” templates are
cmlenz@500: automatically cached, and only parsed again when the template file changes. In
cmlenz@500: addition, it enables the use of a *template search path*, allowing template
cmlenz@500: directories to be spread across different file-system locations. Using a
cmlenz@820: template loader would generally look as follows:
cmlenz@820:
cmlenz@820: .. code-block:: python
cmlenz@500:
cmlenz@500: from genshi.template import TemplateLoader
cmlenz@500: loader = TemplateLoader([templates_dir1, templates_dir2])
cmlenz@500: tmpl = loader.load('test.html')
cmlenz@500: stream = tmpl.generate(title='Hello, world!')
cmlenz@500: print stream.render()
cmlenz@500:
cmlenz@500: See the `API documentation `_ for details on using Genshi via
cmlenz@500: the Python API.
cmlenz@500:
cmlenz@500:
cmlenz@500: .. _`expressions`:
cmlenz@500:
cmlenz@500: ------------------------------------
cmlenz@500: Template Expressions and Code Blocks
cmlenz@500: ------------------------------------
cmlenz@500:
cmlenz@500: Python_ expressions can be used in text and directive arguments. An expression
cmlenz@500: is substituted with the result of its evaluation against the template data.
cmlenz@500: Expressions in text (which includes the values of non-directive attributes) need
cmlenz@500: to prefixed with a dollar sign (``$``) and usually enclosed in curly braces
cmlenz@500: (``{…}``).
cmlenz@500:
cmlenz@500: .. _python: http://www.python.org/
cmlenz@500:
cmlenz@500: If the expression starts with a letter and contains only letters, digits, dots,
cmlenz@500: and underscores, the curly braces may be omitted. In all other cases, the
cmlenz@500: braces are required so that the template processor knows where the expression
cmlenz@820: ends:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@500:
cmlenz@500: >>> from genshi.template import MarkupTemplate
cmlenz@500: >>> tmpl = MarkupTemplate('${items[0].capitalize()} item')
cmlenz@500: >>> print tmpl.generate(items=['first', 'second'])
cmlenz@500: First item
cmlenz@500:
cmlenz@500: Expressions support the full power of Python. In addition, it is possible to
cmlenz@500: access items in a dictionary using “dotted notation” (i.e. as if they were
cmlenz@500: attributes), and vice-versa (i.e. access attributes as if they were items in a
cmlenz@820: dictionary):
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@500:
cmlenz@500: >>> from genshi.template import MarkupTemplate
cmlenz@500: >>> tmpl = MarkupTemplate('${dict.foo}')
cmlenz@500: >>> print tmpl.generate(dict={'foo': 'bar'})
cmlenz@500: bar
cmlenz@500:
cmlenz@500: Because there are two ways to access either attributes or items, expressions
cmlenz@500: do not raise the standard ``AttributeError`` or ``IndexError`` exceptions, but
cmlenz@500: rather an exception of the type ``UndefinedError``. The same kind of error is
cmlenz@500: raised when you try to use a top-level variable that is not in the context data.
cmlenz@500: See `Error Handling`_ below for details on how such errors are handled.
cmlenz@500:
cmlenz@500:
cmlenz@820: Escaping
cmlenz@820: ========
cmlenz@820:
cmlenz@820: If you need to include a literal dollar sign in the output where Genshi would
cmlenz@820: normally detect an expression, you can simply add another dollar sign:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@820:
cmlenz@820: >>> from genshi.template import MarkupTemplate
cmlenz@820: >>> tmpl = MarkupTemplate('$foo') # Wanted "$foo" as literal output
cmlenz@820: >>> print tmpl.generate()
cmlenz@820: Traceback (most recent call last):
cmlenz@820: ...
cmlenz@820: UndefinedError: "foo" not defined
cmlenz@820: >>> tmpl = MarkupTemplate('$$foo')
cmlenz@820: >>> print tmpl.generate()
cmlenz@820: $foo
cmlenz@820:
cmlenz@820: But note that this is not necessary if the characters following the dollar sign
cmlenz@820: do not qualify as an expression. For example, the following needs no escaping:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@820:
cmlenz@820: >>> tmpl = MarkupTemplate('')
cmlenz@820: >>> print tmpl.generate()
cmlenz@820:
cmlenz@820:
cmlenz@820: On the other hand, Genshi will always replace two dollar signs in text with a
cmlenz@820: single dollar sign, so you'll need to use three dollar signs to get two in the
cmlenz@820: output:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@820:
cmlenz@820: >>> tmpl = MarkupTemplate('')
cmlenz@820: >>> print tmpl.generate()
cmlenz@820:
cmlenz@820:
cmlenz@820:
cmlenz@500: .. _`code blocks`:
cmlenz@500:
cmlenz@500: Code Blocks
cmlenz@500: ===========
cmlenz@500:
cmlenz@820: Templates also support full Python code blocks, using the ````
cmlenz@820: processing instruction in XML templates:
cmlenz@820:
cmlenz@820: .. code-block:: genshi
cmlenz@500:
cmlenz@500:
cmlenz@500:
cmlenz@500: ${greeting('world')}
cmlenz@500:
cmlenz@500:
cmlenz@820: This will produce the following output:
cmlenz@820:
cmlenz@820: .. code-block:: xml
cmlenz@500:
cmlenz@500:
cmlenz@500: Hello, world!
cmlenz@500:
cmlenz@500:
cmlenz@820: In text templates (although only those using the new syntax introduced in
cmlenz@820: Genshi 0.5), code blocks use the special ``{% python %}`` directive:
cmlenz@820:
cmlenz@820: .. code-block:: genshitext
cmlenz@820:
cmlenz@820: {% python
cmlenz@820: from genshi.builder import tag
cmlenz@820: def greeting(name):
cmlenz@820: return 'Hello, %s!' % name
cmlenz@820: %}
cmlenz@820: ${greeting('world')}
cmlenz@820:
cmlenz@820: This will produce the following output::
cmlenz@820:
cmlenz@820: Hello, world!
cmlenz@820:
cmlenz@820:
cmlenz@500: Code blocks can import modules, define classes and functions, and basically do
cmlenz@500: anything you can do in normal Python code. What code blocks can *not* do is to
cmlenz@820: produce content that is emitted directly tp the generated output.
cmlenz@500:
cmlenz@500: .. note:: Using the ``print`` statement will print to the standard output
cmlenz@500: stream, just as it does for other Python code in your application.
cmlenz@500:
cmlenz@500: Unlike expressions, Python code in ```` processing instructions can
cmlenz@500: not use item and attribute access in an interchangeable manner. That means that
cmlenz@500: “dotted notation” is always attribute access, and vice-versa.
cmlenz@500:
cmlenz@500: The support for Python code blocks in templates is not supposed to encourage
cmlenz@500: mixing application code into templates, which is generally considered bad
cmlenz@500: design. If you're using many code blocks, that may be a sign that you should
cmlenz@500: move such code into separate Python modules.
cmlenz@500:
cmlenz@820: If you'd rather not allow the use of Python code blocks in templates, you can
cmlenz@820: simply set the ``allow_exec`` parameter (available on the ``Template`` and the
cmlenz@820: ``TemplateLoader`` initializers) to ``False``. In that case Genshi will raise
cmlenz@820: a syntax error when a ```` processing instruction is encountered.
cmlenz@820: But please note that disallowing code blocks in templates does not turn Genshi
cmlenz@820: into a sandboxable template engine; there are sufficient ways to do harm even
cmlenz@820: using plain expressions.
cmlenz@500:
cmlenz@500:
cmlenz@500: .. _`error handling`:
cmlenz@500:
cmlenz@500: Error Handling
cmlenz@500: ==============
cmlenz@500:
cmlenz@820: By default, Genshi raises an ``UndefinedError`` if a template expression
cmlenz@820: attempts to access a variable that is not defined:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@500:
cmlenz@500: >>> from genshi.template import MarkupTemplate
cmlenz@500: >>> tmpl = MarkupTemplate('${doh}
')
cmlenz@820: >>> tmpl.generate().render('xhtml')
cmlenz@820: Traceback (most recent call last):
cmlenz@820: ...
cmlenz@820: UndefinedError: "doh" not defined
cmlenz@820:
cmlenz@820: You can change this behavior by setting the variable lookup mode to "lenient".
cmlenz@820: In that case, accessing undefined variables returns an `Undefined` object,
cmlenz@820: meaning that the expression does not fail immediately. See below for details.
cmlenz@820:
cmlenz@820: If you need to check whether a variable exists in the template context, use the
cmlenz@820: defined_ or the value_of_ function described below. To check for existence of
cmlenz@820: attributes on an object, or keys in a dictionary, use the ``hasattr()``,
cmlenz@820: ``getattr()`` or ``get()`` functions, or the ``in`` operator, just as you would
cmlenz@820: in regular Python code:
cmlenz@820:
cmlenz@820: >>> from genshi.template import MarkupTemplate
cmlenz@820: >>> tmpl = MarkupTemplate('${defined("doh")}
')
cmlenz@820: >>> print tmpl.generate().render('xhtml')
cmlenz@820: False
cmlenz@820:
cmlenz@820: .. note:: Lenient error handling was the default in Genshi prior to version 0.5.
cmlenz@820: Strict mode was introduced in version 0.4, and became the default in
cmlenz@820: 0.5. The reason for this change was that the lenient error handling
cmlenz@820: was masking actual errors in templates, thereby also making it harder
cmlenz@820: to debug some problems.
cmlenz@820:
cmlenz@820:
cmlenz@820: .. _`lenient`:
cmlenz@820:
cmlenz@820: Lenient Mode
cmlenz@820: ------------
cmlenz@820:
cmlenz@820: If you instruct Genshi to use the lenient variable lookup mode, it allows you
cmlenz@820: to access variables that are not defined, without raising an ``UndefinedError``.
cmlenz@820:
cmlenz@820: This mode can be chosen by passing the ``lookup='lenient'`` keyword argument to
cmlenz@820: the template initializer, or by passing the ``variable_lookup='lenient'``
cmlenz@820: keyword argument to the ``TemplateLoader`` initializer:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@820:
cmlenz@820: >>> from genshi.template import MarkupTemplate
cmlenz@820: >>> tmpl = MarkupTemplate('${doh}
', lookup='lenient')
cmlenz@500: >>> print tmpl.generate().render('xhtml')
cmlenz@500:
cmlenz@500:
cmlenz@500: You *will* however get an exception if you try to call an undefined variable, or
cmlenz@820: do anything else with it, such as accessing its attributes:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@500:
cmlenz@500: >>> from genshi.template import MarkupTemplate
cmlenz@820: >>> tmpl = MarkupTemplate('${doh.oops}
', lookup='lenient')
cmlenz@500: >>> print tmpl.generate().render('xhtml')
cmlenz@500: Traceback (most recent call last):
cmlenz@500: ...
cmlenz@500: UndefinedError: "doh" not defined
cmlenz@500:
cmlenz@500: If you need to know whether a variable is defined, you can check its type
cmlenz@820: against the ``Undefined`` class, for example in a conditional directive:
cmlenz@820:
cmlenz@820: .. code-block:: pycon
cmlenz@500:
cmlenz@500: >>> from genshi.template import MarkupTemplate
cmlenz@820: >>> tmpl = MarkupTemplate('${type(doh) is not Undefined}
',
cmlenz@820: ... lookup='lenient')
cmlenz@500: >>> print tmpl.generate().render('xhtml')
cmlenz@500: False
cmlenz@500:
cmlenz@500: Alternatively, the built-in functions defined_ or value_of_ can be used in this
cmlenz@500: case.
cmlenz@500:
cmlenz@500: Custom Modes
cmlenz@500: ------------
cmlenz@500:
cmlenz@500: In addition to the built-in "lenient" and "strict" modes, it is also possible to
cmlenz@500: use a custom error handling mode. For example, you could use lenient error
cmlenz@500: handling in a production environment, while also logging a warning when an
cmlenz@500: undefined variable is referenced.
cmlenz@500:
cmlenz@500: See the API documentation of the ``genshi.template.eval`` module for details.
cmlenz@500:
cmlenz@500:
cmlenz@500: Built-in Functions & Types
cmlenz@500: ==========================
cmlenz@500:
cmlenz@500: The following functions and types are available by default in template code, in
cmlenz@500: addition to the standard built-ins that are available to all Python code.
cmlenz@500:
cmlenz@500: .. _`defined`:
cmlenz@500:
cmlenz@500: ``defined(name)``
cmlenz@500: -----------------
cmlenz@500: This function determines whether a variable of the specified name exists in
cmlenz@500: the context data, and returns ``True`` if it does.
cmlenz@500:
cmlenz@500: .. _`value_of`:
cmlenz@500:
cmlenz@500: ``value_of(name, default=None)``
cmlenz@500: --------------------------------
cmlenz@500: This function returns the value of the variable with the specified name if
cmlenz@500: such a variable is defined, and returns the value of the ``default``
cmlenz@500: parameter if no such variable is defined.
cmlenz@500:
cmlenz@500: .. _`Markup`:
cmlenz@500:
cmlenz@500: ``Markup(text)``
cmlenz@500: ----------------
cmlenz@500: The ``Markup`` type marks a given string as being safe for inclusion in markup,
cmlenz@500: meaning it will *not* be escaped in the serialization stage. Use this with care,
cmlenz@500: as not escaping a user-provided string may allow malicious users to open your
cmlenz@500: web site to cross-site scripting attacks.
cmlenz@500:
cmlenz@500: .. _`Undefined`:
cmlenz@500:
cmlenz@500: ``Undefined``
cmlenz@500: ----------------
cmlenz@500: The ``Undefined`` type can be used to check whether a reference variable is
cmlenz@500: defined, as explained in `error handling`_.
cmlenz@500:
cmlenz@500:
cmlenz@500: .. _`directives`:
cmlenz@500:
cmlenz@500: -------------------
cmlenz@500: Template Directives
cmlenz@500: -------------------
cmlenz@500:
cmlenz@500: Directives provide control flow functionality for templates, such as conditions
cmlenz@500: or iteration. As the syntax for directives depends on whether you're using
cmlenz@500: markup or text templates, refer to the
cmlenz@500: `XML Template Language `_ or
cmlenz@500: `Text Template Language `_ pages for information.