Kid Language Specification
Author: | Ryan Tomayko |
---|---|
Contact: | rtomayko@gmail.com |
Revision: | 5 |
Date: | 2006-06-10 |
Copyright: | 2005, Ryan Tomayko |
Other Formats: | Text |
Kid is a simple XML based template language that uses embedded Python to do cool stuff. The syntax was inspired by a number of existing template languages, namely XSLT, TAL, and PHP.
This document describes the template language and will be most useful as reference to those developing Kid templates. For information about using templates from Python, the command line, or in web environments, see the User's Guide.
- 1 Synopsis
- 2 The Kid Namespace
- 3 Embedding Code Blocks (<?python?>)
- 4 Content Producing Constructs
- 5 Python Expression Substitution (${expr})
- 6 Default Imports
- 7 Attribute Language
- 7.1 Repetition/Iteration (py:for)
- 7.2 Conditionals (py:if)
- 7.3 Dynamic Content (py:content)
- 7.4 Replacing Content (py:replace)
- 7.5 Stripping Tags (py:strip)
- 7.6 Dynamic Attributes (py:attrs)
- 7.7 Named Template Functions (py:def)
- 7.8 Match Templates (py:match)
- 7.9 Template Reuse (py:extends)
- 7.10 Layout Templates (py:layout)
- 8 Processing Order
- 9 XML Comments
- 10 Revision History
1 Synopsis
<?python title = "A Kid Test Document" fruits = ["apple", "orange", "kiwi", "M&M"] from platform import system ?> <html xmlns:py="http://purl.org/kid/ns#"> <head> <title py:content="title">This is replaced.</title> </head> <body> <p>These are some of my favorite fruits:</p> <ul> <li py:for="fruit in fruits"> I like ${fruit}s </li> </ul> <p py:if="system() == 'Linux'"> Good for you! </p> </body> </html>
Yields something like this:
<?xml version="1.0" encoding="utf-8"?> <html> <head> <title>A Kid Test Document</title> </head> <body> <p>These are some of my favorite fruits:</p> <ul> <li>I like apples</li> <li>I like oranges</li> <li>I like kiwis</li> <li>I like M&Ms</li> </ul> <p> Good for you! </p> </body> </html>
2 The Kid Namespace
All attributes described in this document must belong to the following namespace:
http://purl.org/kid/ns#
The namespace prefix py is used throughout this document to indicate that an item belongs to the Kid/Python namespace.
3 Embedding Code Blocks (<?python?>)
The <?python?> processing instruction (PI) contains Python code and MAY occur anywhere that is legal for processing instructions to occur in an XML document.
The rules for executing code found in a <?python?> PI is as follows:
- <?python?> PIs located outside of the document element (e.g. root element) contain Document Level code. This code SHOULD be executed in a global, shared scope for the document. The code SHOULD be executed once when the template is loaded and shared between multiple invocations of the template.
- <?python?> PIs located within the document element contain Local Level code. This code is executed each time the document is processed with a local scope specific to the invocation and the shared document level global scope.
Document Level and Local Level code work exactly like Module Level and Function Level code in normal Python modules. For example, the following Kid template:
<?python x = 0 y = 0 ?> <html xmlns:py="http://purl.org/kid/ns#"> <?python x = 1 if x == 1: x = 10 ?> <p py:content="x"/> <?python global y y = 30 ?> <p py:content="y"/> </html>
May be considered equivalent to the following Python module:
x = 0 y = 0 def expand(handler): handler.startDocument() handler.startElement('html') x = 1 if x == 1: x = 10 handler.element('p', content=x) # output p element with x as value global y y = 30 handler.element('p', content=y) # output p element with value of y handler.endElement('html') handler.endDocument()
<?python?> PIs may contain any legal Python language construct including functions, classes, lamda forms, etc.
<?python class Adder: def __init__(self, x, y): self.x = x self.y = y def doit(self): return self.x + self.y foo = Adder(x=400, y=20) x = foo.doit() ?>
Single line <?python?> PIs are okay too:
<?python x = 10 ?>
4 Content Producing Constructs
There are multiple methods of generating content output from a template: py:content, py:replace, py:attrs, and ${} substitution. Each of these syntaxes have the same rules for what types of objects may result from the Python expression they contain.
- str, unicode
- The string is inserted as XML CDATA. That is, it is non-parsable character data that does not contain markup. The following characters are encoded as XML entities when serialized: '<', '&'. Attribute values containing content also encode the quote character: '"'.
- ElementTree.Element
When an ElementTree.Element is referenced from a content producing construct, the item is inserted into the document literally, i.e. it is not encoded as text, but becomes part of the output structure.
The XML() and document() functions can be used to turn a string into structured content and to retrieve an XML document from a URL, respectively.
Note that attribute values MUST NOT reference structured content. This applies to py:attrs and using ${} substitution in attribute values.
- sequence
- If a sequence type (list, tuple, or other iterable) is referenced, the rules are applied to each of the items in the sequence. For example, you could reference a list containing an Element and a string.
- Other
- If the result of evaluating the expression is any other type, an attempt is made to coerce the value to unicode as if by calling unicode(expr) and processing continues as if the object were a string or unicode object initially.
5 Python Expression Substitution (${expr})
Attributes not belonging to the Kid namespace and text content MAY embed Python expressions by enclosing the expression within a dollar sign followed by curly braces: ${expr}. The result of evaluating the expression(s) is substituted with the rest of the attribute value or text content following rules defined for Content Producing Constructs.
<?python verb = 'ran' noun = 'store' ?> <a title="I ${verb} to the ${noun}">...
... would result in:
<a title="I ran to the store">...
If an attribute value consists purely of substitution expressions and all expressions evaluate to None, the attribute is removed. This can be avoided by using expr or '' to force a zero length string to be returned instead of None. For example:
<?python # set something to None x = None ?> <a title="${x}">...
... would result in:
<a>...
However, this:
<?python x = None?> <a title="${x or ''}">...
... results in:
<a title="">...
5.1 Identifier Shortcut ($name)
For simple expressions consisting entirely variable names and object access operators (.), the curly braces may be omitted:
<a href="http://example.com/$page" title="$title"> Dots are allowed too: $object.another.attribute </a>
However, it is good practice to use the curly brace form as it sets the substitution off from the other text a bit more providing a stronger visual clue as to what's going on.
5.2 Escaping ($$)
$$ is an escape. $${bla} will output ${bla}.
6 Default Imports
All templates have a few default imports for convenience.
6.1 XML() function
Python Expression substitution, py:content, and py:replace encode strings as text. That is, text is encoded according to the rules of the XML specification, which includes, among other things, replacing the literal characters < and & with their encoded counterparts (< &). If you have XML stored as a string and want it to be output as XML and not encoded text, you need to pass the string to the XML function.
For example, let's say there is a function, hello, that returns XML data that should be embedded in template output (let's say it returns <hello>world</hello>). Consider the following:
<p>${hello()}</p>
The result would be:
<p><hello>world<hello></p>
Calling the XML function would have given us the result we intended:
<p>${XML(hello())}</p>
<p><hello>world</hello></p>
The xmlns keyword parameter can be used to set the default XML namespace for the top-level elements in the supplied content:
<p>${XML(hello(), xmlns="cid:hello_example")}</p>
<p><hello xmlns="cid:hello_example">world</hello></p>
This is especially useful when including XHTML content that lacks an XML namespace declaration.
6.2 document() function
The document function loads an XML document from a file or URL allowing it to be embedded in template output:
<div py:content="document('header.html')"></div>
The document function resolves paths relative to the current template file (if the template location is available).
6.3 defined(name) and value_of(name, default) functions
defined returns True if the name exists in the Kid namespace. value_of returns either the named value or the optional default value if that name doesn't exist in the namespace. These two simple convenience functions may be more intuitive to many than hasattr(self, name) and getattr(self, name, default) which are exactly equivalent.
7 Attribute Language
7.1 Repetition/Iteration (py:for)
<element py:for="target_list in expression_list" />
Works exactly like the Python for statement.
The py:for attribute may appear on any element to signify that the element should be processed multiple times, once for each value in the sequence specified:
<?python bottles = range(1, 101) bottles.reverse() ?> <p py:for="num in bottles"> <span py:content="num">X</span> bottles of beer on the wall, <span py:content="num">X</span> bottles of beer on the wall, take one down, pass it around, <span py:content="num - 1">X - 1</span> bottles of beer on the wall. </p>
The py:for attribute is the first attribute to be processed if present. All other py: attributes are processed for each iteration of the loop.
7.2 Conditionals (py:if)
<element py:if="expr" />
The py:if attribute may appear on any element to signify that the element and its decendant items should be output only if the boolean expression specified evaluates to true in Python:
<p py:if="5 * 5 == 25"> Python seems to be handling multiplication okay. </p>
The py:if attribute is processed after the py:for attribute and is evaluated for each iteration. If the result of evaluating expr as a boolean expression is false in Python, no further py: attributes are processed for the current iteration or, if not in a py:for, at all.
Note
Evaluated as a boolean expression in Python, None, False, [], (), {}, 0, and '' are all considered to be false.
7.3 Dynamic Content (py:content)
<element py:content="expr" />
This attribute MAY appear on any element to signify that the decendant items of the element are to be replaced with the result of evaluating expr.
<p py:content="time.strftime('%C %c')">The Time</p>
... results in:
<p>Tues, Jun 26, 2004 02:03:53 AM</p>
py:content is a Content Producing Construct and can output both character and structured data.
If expr evaluates to None the element will be output as an empty element.
<p py:content="None">i go away</p>
... results in:
<p />
7.4 Replacing Content (py:replace)
<element py:replace='expr' />
py:replace is shorthand for specifying a py:content and a py:strip="True" on the same element:
<?python x = 10 ?> <p><span py:replace="x">...</span></p>
... results in:
<p>10</p>
... and is equivelant to specifying:
<?python # x = 10 ?> <p><span py:strip="" py:content="x">...</span></p>
The py:replace attribute is processed after the py:for and py:if attributes. py:strip and py:content attributes are not processed and are discarded.
py:replace is a Content Producing Construct and can output both character and structured data.
If expr evaluates to None the element will be output as an empty element.
<test><p py:replace="None"><span>i go away</span></p></test>
... results in:
<test />
7.5 Stripping Tags (py:strip)
<element py:strip="expr" />
The py:strip attribute may apppear on any element to signify that the containing element should not be output. If the attribute value is blank (no expr at all) or if the result expr is a boolean expression that evaluates to true, the element is not output, but all descendant elements are processed normally. If expr is not blank and the result of evaluating expr as a boolean expression is false, processing continues as if the attribute did not exist.
The py:strip attribute MAY appear on an element with any other kid attribute. However, if both a py:replace and a py:strip exist on the same element, the py:strip attribute is ignored and discarded.
The py:strip attribute is processed after the py:for and py:if attributes. If omission is eminent, the py:content attribute is processed normally but attribute interpolation does not occur.
7.6 Dynamic Attributes (py:attrs)
<element py:attrs="expr" />
The py:attrs attribute may appear on any element to specify a set of attributes that should be set on the element when it is processed. The expression specified MUST evaluate to one of the following types of values:
- dict
- A dictionary with keys specifying attribute names and values specifying attribute values. These are added to the attributes of the current element by calling element.attrib.update(mapping), where element is an ElementTree Element object and mapping is the dictionary returned from the expression. Outer curly braces are not necessary to write down.
- list
- A list of tuples of the form (name, value) is also acceptable. Each item of the list is added to the current set of attributes by iterating over the list and calling element.set(name, value).
- keyword arguments
- The attributes can also be specified as comma separated keyword arguments of the form name=value.
The following lines:
<elem py:attrs="{'a':1, 'ns:b':2}" /> <elem py:attrs="'a':1, 'ns:b':2" /> <elem py:attrs="(('a',1), ('ns:b',2))" /> <elem py:attrs="a=1, ns:b=2" />
will all produce the same output:
<elem a="1" ns:b="2" />
Note that attributes whose values are None will be removed. If a blank attribute is desired, an empty string should be used.
If the expression specified is an empty dictionary or an empty list, the attributes are not modified in any way.
py:attrs is a Content Producing Construct, but can output only character data.
7.7 Named Template Functions (py:def)
<element py:def="template_name(arg_list)" />
The py:def attribute may appear on any element to create a "Named Template Function". Markup contained within an py:def element is not output during normal template expansion but can be referenced from other Content Producing Constructs to insert the markup at the point referenced.
Like normal Python functions, Named Template Functions have an optional argument list that may use all of the jazzy features of Python argument lists like variable and keyword arguments.
Named Template Functions are invoked exactly like normal Python functions. They are generally invoked from Content Producing Constructs like py:content or ${} substitution.
Here we will define two Named Template Functions: display_list and display_dict. The first function takes a sequence and the second a mapping. We can invoke these functions from the same template by invoking them from a content producing construct:
<html xmlns:py="http://purl.org/kid/ns#"> <body> <ul py:def="display_list(seq)"> <li py:for="item in seq" py:content="item" /> </ul> <table py:def="display_dict(mapping)"> <tr> <th>Key</th> <th>Value</th> </tr> <tr py:for="key, value in mapping.items()"> <td py:content="key" /> <td py:content="value" /> </tr> </table> ${display_list(['apple', 'orange', 'kiwi'])} <div py:replace="display_dict({'x' : 'y', 'p' : 'q'})"> Key/Value Table replaces this text </div> </body> </html>
Serialized as XML this becomes something like:
<?xml version="1.0" encoding="utf-8"?> <html> <body> <ul> <li>apple</li><li>orange</li><li>kiwi</li> </ul> <table> <tr> <th>Key</th> <th>Value</th> </tr> <tr> <td>x</td> <td>y</td> </tr> <tr> <td>p</td> <td>q</td> </tr> </table> </body> </html>
7.8 Match Templates (py:match)
<element py:match="expr" />
The py:match attribute may appear on any element to create a "Match Template". Markup contained within a Match Template element is not output during normal template expansion. Instead, these constructs set up filters for expansion output that are capable of transforming content as it is generated.
Match Templates are generally used to insert content dynamically based on patterns in template expansion or to provide "custom tag" functionality similar to that found in JSP taglibs or XSLT.
A Match Template has two parts: the match expression part (expr) and the body part (the element and it's descendants).
Match Templates are processed as follows:
- Each element that is output from a template goes through the Match Template Filter.
- The Match Template Filter visits each of the Match Templates defined in the current template and the templates the current template extends in the order that they are defined and evaluates the associated match expression.
- If the match expression returns true as a boolean expression, the match template's body is expanded and replaces the original element and all of its descendants.
In both the match expression and in the match template's body, the item name is bound to the Element that is being output. However, there are some limitations to what can be accessed at each phase:
- During match expression evaluation, only the item Element and none of its descendants are available. This means that match expressions are limited to testing matches based on the immediate Element's tag and attributes [1].
- During match template expansion (that is, when the match expression is true), the element's descendants are available and may be referenced from Content Producing Constructs to output bits and pieces of the matched items structure.
When matching the item.tag you need to keep your namespaces in mind. A common problem is to use the expression item.tag == 'body' when the document has a default namespace declared. When the default namespace is declared the XML parser will rename the tags using clark notation. So the correct expression would be something like item.tag == '{http://www.w3.org/1999/xhtml}body'.
[1] | This is due to the streaming nature of the Kid processor. During normal template expansion, the entire tree is never fully retained in memory. |
7.8.1 Example
The following simple example shows how to create a custom tag <greeting> that outputs one of two provided values based on the time of day the template is expanded:
<?xml version="1.0" encoding="utf-8"?> <?python from time import localtime def timeofday(): """Get time of day ('am' or 'pm')""" return localtime().tm_hour < 12 and 'am' or 'pm' ?> <html xmlns:py="http://purl.org/kid/ns#"> <!-- define the greeting match template --> <span py:match="item.tag == 'greeting'" py:replace="item.get(timeofday())"> </span> <head> <title>Time of day demo</title> </head> <body> <p> Good <greeting am="Morning!" pm="Afternoon" /> </p> </body> </html>
An important thing to note is that the py:match expression and the match template body have access to the <greeting> element via the variable item. The item.get(timeofday()) bit retrieves the value of the am attribute or the pm attribute based on what is returned from the timeofday function.
At 9:00 AM, output from this template would look like this:
<html> <head> <title>Time of day demo</title> </head> <body> <p> Good Morning! </p> </body> </html>
The obvious question at this point is how to reuse Match Templates? The example above demonstrates the use of a Match Template from the same main template but it is often desirable to have "libraries" of Match Templates that could be used by multiple individual templates. The answer is to have the main template extend a common template containing the Match Templates needed. We can rewrite the above example as two separate templates: main.kid and common.kid.
The common template would look like this:
<?xml version="1.0" encoding="utf-8"?> <?python from time import localtime def timeofday(): """Get time of day ('am' or 'pm')""" return localtime().tm_hour < 12 and 'am' or 'pm' ?> <html xmlns:py="http://purl.org/kid/ns#"> <!-- define the greeting match template --> <span py:match="item.tag == 'greeting'" py:replace="item.get(timeofday())"> </span> </html>
And the main template would look like this:
<?xml version="1.0" encoding="utf-8"?> <html py:extends="'common.kid'"> <head> <title>Time of day demo</title> </head> <body> <p> Good <greeting am="Morning!" pm="Afternoon" /> </p> </body> </html>
When a template extends another template (or set of templates), all of the Match Templates and Named Template Functions of the extended templates are available as if they were defined locally.
Warning
Match templates are an experimental feature. Syntax and semantics may change significantly or be removed entirely in future release. Actually, this statement applies to many aspects of Kid but this one is especially unstable.
7.9 Template Reuse (py:extends)
<root py:extends="template1, template2, ...">
The py:extends attribute may appear on the root element to specify that the template should inherit the Named Template Functions and Match Templates defined in another template (or set of templates). If a py:extends attribute is specified, it MUST be on the root element of the document.
The py:extends may contain a list of Python expressions separated by commas that reference templates. The rules for what types of values may be specified are:
- string
The name of a template file, relative to the current template file.
Example:
<html py:extends="'common.kid'" />
- module or Template class
The py:extends variable references a module or a Template class. If a module is referenced, an attempt is made to find a class named Template belonging to the that module.
Example:
<?python import common ?> <html py:extends="common" ...
Multiple templates may be referenced by separating each by a comma. The following example references templates common and forms, imported using the import hooks and a template filename named other.kid:
<?python import common, forms ?> <html py:extends="common, forms, 'other.kid'" ...
7.9.1 Example
For example, there is a template named common.kid that defines a template function, display_errors, and a match template that converts <b> elements to <strong> elements with uppercase content:
<html xmlns:py="http://purl.org/kid/ns#"> <ul py:def="display_errors(errors)"> <li py:for="error in errors" py:content="error" /> </ul> <strong py:match="item.tag == 'b'" py:content="item.text.upper()" /> </html>
The functions and match templates may be imported into another template by referencing them with py:extends:
<html py:extends="'common.kid'" xmlns:py="http://purl.org/kid/ns#"> <head> <title>Errors</title> </head> <body> <p>The following <b>errors</b> were found:</p> ${ display_errors(["Field is required", "Must be phone number.."]) } </body> </html>
The <b>errors</b> item is transformed to <strong>ERRORS</strong> and the error list is displayed. Both the match template and the named template function are available in the derived template as if they were defined locally.
7.10 Layout Templates (py:layout)
<root py:layout="base_layout">
The py:layout attribute may appear on the root element to specify that a content template should use the referenced template as a layout template. This allows separation of individual page content from site-wide layout elements such as headers, menus, footers, etc. Match Templates, Named Template Functions, and layout parameters defined in a content template will be applied to the layout template. If a py:layout attribute is specified, it MUST be on the root element of the document.
The py:layout attribute may contain a single Python expression that references a layout template. The rules for what types of values may be specified are:
- string
The name of a template file, relative to the current template file.
Example:
<html py:layout="'base_layout.kid'" />
- module or Template class
The py:layout variable references a module or a Template class. If a module is referenced, an attempt is made to find a class named Template belonging to the that module.
Example:
<?python import base_layout ?> <html py:extends="base_layout" ...
7.10.1 Example
For example, here is a base template named base_layout.kid that contains header, content, and footer sections:
<html xmlns:py="http://purl.org/kid/ns#"> <head> <title>App Name - ${page_title}</title> <link href="layout.css" type="text/css" rel="stylesheet" /> ${page_specific_css()} </head> <body> <h1>Now viewing: ${page_title} of App Name</h1> <content>Default content</content> <div class="footer">Page Footer Text</div> </body> </html>
Note how some sections were left to be filled in by the content template. Page-specific functions, match templates, and layout parameters may be defined in a separate template and then applied to the base template with py:layout:
<?python layout_params['page_title'] = "Content Page 1 of 10" ?> <html py:layout="'base_layout.kid'" xmlns:py="http://purl.org/kid/ns#"> <link py:def="page_specific_css()" href="layout.css" type="text/css" rel="stylesheet" /> <div py:match="item.tag == 'content'"> <ul> <li>Content Item 1</li> <li>Content Item 2</li> <li>Content Item 3</li> </ul> </div> </html>
Both the match template and the named template function are applied to the base layout template when the content template is rendered. Also note how the page_title is inserted into the layout_params dict. Items placed in layout_params are passed to the base layout template along with template keyword arguments. The layout_params dict can only be modified in the outer python block before the root element in the page.
8 Processing Order
The order that py: attributes are processed is as follows:
- py:def
- py:match
- py:for
- py:if
- py:replace
- py:strip
- py:attrs
- py:content
Attribute substitution occurs after all other attributes are processed and MUST NOT be processed for py: attributes.
9 XML Comments
Kid templates may contain XML comments. The comments will appear in the serialized output of a template, but will not be processed by the Python Expression Substitution rules.
<!--this is a comment--> <!-- and so is this -->
Sometimes it is useful to have comments in a template that will not appear when the template is serialized. To do this simply prefix the comment text with a ! character.
<!--!this will not be serialized--> <!-- !this will also not be serialized -->
10 Revision History
See the Release Notes for a list of changes between versions.