skip to navigation
skip to content

katagami 0.3.0

katagami - a very simple xml template engine.

Downloads ↓

katagami - a very simple xml template engine.
=============================================


setup...skip to next chapter:

    >>> def renderString_(t):
    ...     return renderString('<html xmlns:py="http://pypi.python.org/pypi/katagami" py:feature="strip-space"><body>%s</body></html>' % t)
    >>> def echo(name, body):
    ...     with open(os.path.join(tmpdir, name), 'w') as fp:
    ...         fp.write(body)
    >>> def echoxml(name, body):
    ...     echo(name, '<html xmlns:py="http://pypi.python.org/pypi/katagami" py:feature="strip-space"><body>%s</body></html>' % body)


Pythonic evaluation
-------------------

scriping
~~~~~~~~

`CDATA` is required and use `print`:

    >>> renderString_('''
    ... <py:script><![CDATA[
    ...     print '<p>hello, world</p>'
    ... ]]></py:script>
    ... ''')
    '<html><body><p>hello, world</p>\n</body></html>'


Include python script file:

    >>> echo('sub-script.py', '''print "hello, world"''')
    >>> echoxml('template.html', '''
    ...     <p><py:script src="sub-script.py"/></p>
    ... ''')
    >>> renderFile(os.path.join(tmpdir, 'template.html'))
    '<html><body><p>hello, world\n</p></body></html>'


evaluate XML attributes, textContent, innerXML
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

XML attributes (attribute value starts with XML namespace):

    >>> renderString_('''<p class="py:'python-expr'">hello, world</p>''')
    '<html><body><p class="python-expr">hello, world</p></body></html>'

textContent:

    >>> renderString_('''<p py:text="'hello, world'"/>''')
    '<html><body><p>hello, world</p></body></html>'

innerXML:

    >>> renderString_('''<div py:content="'hello, world&lt;hr/&gt;'"/>''')
    '<html><body><div>hello, world<hr/></div></body></html>'


Python syntax as XML attributes
-------------------------------

`if`, `elif`, `else` statements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    >>> renderString_('''
    ... <p py:if="0"/>
    ... <p py:elif="0"/>
    ... <p py:else="">output here</p>
    ... ''')
    '<html><body><p>output here</p></body></html>'


loop statements
~~~~~~~~~~~~~~~

`for` statement (attribute value is Pythonic `for` style):

    >>> renderString_('''<p py:for="i, j in enumerate(range(3))" py:text="i, j"/>''')
    '<html><body><p>(0, 0)</p><p>(1, 1)</p><p>(2, 2)</p></body></html>'

`for`'s `else` is not supported.


`while` statement:

    >>> renderString_('''
    ... <py:script><![CDATA[ i = [1, 2, 3] ]]></py:script>
    ... <p py:while="i">
    ...     <py:_ py:text="i[0]"/>
    ...     <py:script><![CDATA[ i = i[1:] ]]></py:script>
    ... </p>
    ... ''')
    '<html><body><p>1</p><p>2</p><p>3</p></body></html>'

`while`'s `else` is not supported.


And there is special variable named `__loop__`, it is loop counter:

    >>> renderString_('''
    ... <p py:for="i in range(0)" py:text="i"/>
    ... <p py:if="not __loop__">no for loop</p>
    ... <p py:while="0"/>
    ... <p py:if="not __loop__">no while loop</p>
    ... ''')
    '<html><body><p>no for loop</p><p>no while loop</p></body></html>'


`try` statement
~~~~~~~~~~~~~~~~~~

    >>> renderString_('''
    ... <p py:try="" py:text="not_found"/>
    ... <p py:except="NameError as e" py:text="e"/>
    ... <p py:try="" py:text="'try'"/>
    ... <p py:except="" py:text="e"/>
    ... <p py:else="">no error</p>
    ... ''')
    "<html><body><p>name 'not_found' is not defined</p><p>try</p><p>no error</p></body></html>"


`with` statement
~~~~~~~~~~~~~~~~

    >>> echo('msg.txt', 'hello, world')
    >>> renderString_('''
    ... <py:_ py:with="open(r'%s') as fp">
    ...     <p py:text="fp.read()"/>
    ...     <p py:text="fp.closed"/>
    ... </py:_>
    ... <p py:text="fp.closed"/>
    ... ''' % os.path.join(tmpdir, 'msg.txt'))
    '<html><body><p>hello, world</p><p>False</p><p>True</p></body></html>'

Multi items are supported (ex. 'with a, b: pass').

    >>> echo('msg2.txt', 'hello, world')
    >>> renderString_('''
    ... <py:_ py:with="open(r'%s') as fp, open(r'%s') as fp2">
    ...     <p py:text="fp.read()"/>
    ...     <p py:text="fp2.read()"/>
    ... </py:_>
    ... ''' % (os.path.join(tmpdir, 'msg.txt'), os.path.join(tmpdir, 'msg2.txt')))
    '<html><body><p>hello, world</p><p>hello, world</p></body></html>'


`def` statement
~~~~~~~~~~~~~~~

Give the context by keyword arguments:

    >>> renderString_('''
    ... <p py:def="myfunc">hello, <py:_ py:text="msg"/></p>
    ... <py:_ py:content="myfunc(msg='world')"/>
    ... ''')
    '<html><body><p>hello,world</p></body></html>'


Include another template
------------------------

Simply, include all elements:

    >>> echoxml('sub-template.html', '<p>hello, world</p>')
    >>> echoxml('template.html', '<py:include src="sub-template.html"/>')
    >>> renderFile(os.path.join(tmpdir, 'template.html'))
    '<html><body><html><body><p>hello, world</p></body></html></body></html>'

XUL like XML overlay:

    >>> echoxml('sub-template.html', '''
    ... <p py:insertbefore="myid">before</p>
    ... <p py:replace="myid">hello, world</p>
    ... <p py:insertafter="myid">after</p>
    ... ''')
    >>> echoxml('template.html', '''
    ...     <py:overlay src="sub-template.html"/>
    ...     <p id="myid"/>
    ... ''')
    >>> renderFile(os.path.join(tmpdir, 'template.html'))
    '<html><body><p>before</p><p id="myid">hello, world</p><p>after</p></body></html>'

`id` attribute is automatically set from `replace` attribute's value.

And special variable named `__file__` means that pathname of template file:

    >>> echoxml('sub-template.html', '<p py:replace="fragment" py:text="os.path.basename(__file__)"/>')
    >>> echoxml('template.html', '''
    ... <py:script><![CDATA[ import os ]]></py:script>
    ... <p py:text="os.path.basename(__file__)"/>
    ... <py:overlay src="sub-template.html"/>
    ... <p id="fragment"/>
    ... ''')
    >>> renderFile(os.path.join(tmpdir, 'template.html'))
    '<html><body><p>template.html</p><p id="fragment">sub-template.html</p></body></html>'


namespace (scope)
-----------------

The namespace is flat like python module and nested in function:

    >>> renderString_('''
    ... <py:script><![CDATA[ a = b = 0 ]]></py:script>
    ... <py: py:def="myfunc">
    ...     <py:script><![CDATA[
    ...         global a
    ...         print 'a=%d,' % a
    ...         print 'b=%d,' % b
    ...         a = b = 1
    ...     ]]></py:script>
    ... </py:>
    ... <py: py:text="myfunc()"/>
    ... (a, b)=<py: py:text="a, b"/>
    ... ''')
    '<html><body>a=0,\nb=0,\n(a, b)=(1, 0)</body></html>'


In included script file:

    >>> echo('sub-script.py', '''
    ... global msg
    ... msg = 'hello, world'
    ... msg2 = 'hello, world'
    ... global myfunc
    ... def myfunc(name):
    ...     return 'hello, ' + name
    ... ''')
    >>> echoxml('template.html', '''
    ...     <py:script src="sub-script.py"/>
    ...     <p py:text="msg"/>
    ...     <p py:try="" py:text="msg2"/>
    ...     <p py:except="NameError as e" py:text="e"/>
    ...     <p py:text="myfunc('world')"/>
    ... ''')
    >>> renderFile(os.path.join(tmpdir, 'template.html'))
    "<html><body><p>hello, world</p><p>name 'msg2' is not defined</p><p>hello, world</p></body></html>"


In included template file:

    >>> echoxml('sub-template.html', '''
    ...     <p py:replace="myid">hello, world</p>
    ...     <py:script><![CDATA[
    ...         global msg
    ...         msg = 'hello, world'
    ...     ]]></py:script>
    ...     <p py:def="global myfunc" py:text="text"/>
    ... ''')
    >>> echoxml('template.html', '''
    ...     <py:overlay src="sub-template.html"/>
    ...     <p id="myid"/>
    ...     <p py:text="msg"/>
    ...     <py: py:content="myfunc(text='hello, world')"/>
    ... ''')
    >>> renderFile(os.path.join(tmpdir, 'template.html'))
    '<html><body><p id="myid">hello, world</p><p>hello, world</p><p>hello, world</p></body></html>'


Features
--------

strip-space
~~~~~~~~~~~

    >>> renderString('''<html xmlns:py="http://pypi.python.org/pypi/katagami"
    ...                       py:feature="strip-space"><body>
    ...     <p> spaces after tag or before tag are stripped. </p>
    ... </body></html>''')
    '<html><body><p>spaces after tag or before tag are stripped.</p></body></html>'


strip-comment
~~~~~~~~~~~~~

    >>> renderString('''<html xmlns:py="http://pypi.python.org/pypi/katagami"
    ...                       py:feature="strip-space strip-comment"><body>
    ...     <!-- comment will be strippd. -->
    ... </body></html>''')
    '<html><body></body></html>'


entity-variable
~~~~~~~~~~~~~~~

Expand entity starts with special xmlns prefix:

    >>> renderString('''<html xmlns:py="http://pypi.python.org/pypi/katagami"
    ...                       py:feature="strip-space entity-variable"><body>
    ...     <py:script><![CDATA[
    ...         msg = 'hello,world'
    ...     ]]></py:script>
    ...     &py:msg;
    ... </body></html>''')
    '<html><body>hello,world</body></html>'


compile-coffeescript
~~~~~~~~~~~~~~~~~~~~

If CoffeeScrip compiler (coffee) is installed and `compile-cofeescript`
feature flag is set, template engine converts
'<script type="text/coffeescript"/>' to
'<script type="application/javascript"/>' by coffee. But `src` attributes is
not supported.


compile-scss
~~~~~~~~~~~~

If pyScss (http://pypi.python.org/pypi/pyScss) is installed and `compile-scss`
feature flag is set, template engine converts '<style type="text/scss"/>' to
'<style type="text/css"/>' by pyScss.


Encoding
--------

Template engine detects file encoding and encodes to unicode for inner use,
returns unicode or decoded str. Python script file encoding is PEP 0263 style,
XML file encoding is XML header ('<?xml encoding="NAME"?>').


Techniques and notices
----------------------

This module is wrote under assuming that sys.setdefaultencoding('utf-8').


The attribute order is important:

    >>> renderString_('''
    ... <p py:if="0" py:for="i in range(2)" py:text="i"/>
    ... <p py:for="i in range(2)" py:if="i > 0" py:text="i"/>
    ... ''')
    '<html><body><p>1</p></body></html>'


If you need closing tag, then write below (This trick is not required for
`html`'s `textarea`):

    >>> renderString_('''
    ... <textarea></textarea>
    ... <div></div>
    ... <div><py:/></div>
    ... ''')
    '<html><body><textarea></textarea><div/><div></div></body></html>'


Entities will not be expanded:

    >>> renderString_('''&nbsp;&unknown_entity;''')
    '<html><body>&nbsp;&unknown_entity;</body></html>'


All unsupported tags will be stripped:

    >>> renderString_('''<py:unknownTag/><py:>not strip</py:>''')
    '<html><body>not strip</body></html>'

But unsupported attributes will occur exception:

    >>> renderString_('''<p py:unknown_attr_0123456789=""/>''')
    Traceback (most recent call last):
    ...
    SyntaxError: unknown statement unknown_attr_0123456789


Special variables are available in some cases:
 * __file__ = str -> path of file (template or script)
 * __noloop__ = bool -> whether loop statements executed
 * _ = object -> temporary value when extracting variables

Special utility functions are available, see default_namespace.

Special string codecs:
 * percent, uri - known as encodeURIComponent, decodeURIComponent
 * xml - escape '<', '>', '&'

For more information, see `Element` class implementation.

History
-------
* 0.3.0 change specification (required xmlns, for_ -> for, remove python tag,
        add script tag, add include tag, add text attribute, add content
        attribute, add overlay tag, add insertbefore attribute, add
        insertafter attribute, add replace attribute), this version is not
        compatible with version 0.2.0
* 0.2.0 change exception handling, fix encoding handling
* 0.1.2 fix encoding handling, add new commandline handling
* 0.1.1 update document for PyPI
* 0.1.0 first release
 
File Type Py Version Uploaded on Size # downloads
katagami-0.3.0.zip (md5) Source 2011-11-18 17KB 190