katagami - a very simple xml template engine.
Project description
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<hr/>'"/>''')
'<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_(''' &unknown_entity;''')
'<html><body> &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
=============================================
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<hr/>'"/>''')
'<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_(''' &unknown_entity;''')
'<html><body> &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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
katagami-0.3.0.zip
(18.2 kB
view hashes)