Skip to main content

katagami - a very simple xml template engine.

Project description

setup…skip to next chapter:

>>> def testfile(file):
...     if not hasattr(file, 'read'):
...         with open(file) as fp:
...             result = render(fp)
...     else:
...         result = render(file)
...     if sys.platform != 'cli': # IronPython doesn't implement unicode.
...         result = result.encode()
...     return result
>>> def teststr(t):
...     return testfile(StringIO('<html><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><body>%s</body></html>' % body)

Pythonic evaluation

Dump the value (call eval with attribute value):

>>> teststr('''<python value="'hello, world'"/>''')
'<html><body>hello, world</body></html>'

By the default, xml entities are escaped in value attribute of python element:

>>> teststr('''<python value="'&lt;p&gt;hello, world&lt;/p&gt;'" escape="false"/>''')
'<html><body><p>hello, world</p></body></html>'

By the default, exceptions are suppressed:

>>> teststr('''<python value="not_found"/>''')
'<html><body></body></html>'

Use __mode__ for dump exceptions (and see a helpful traceback):

>>> teststr('''<python __mode__="strict" value="not_found"/>''') # fails in IronPython
'<html><pre>Traceback (most recent call last):\n  File "&lt;string&gt;", line 1, in Element "body"\n  File "&lt;string&gt;", line 1, in Element "python"\n  File "&lt;string&gt;", line 1, in Element "python" at line 1-1\n    not_found\nNameError: name \'not_found\' is not defined\n</pre></html>'

Attribute evaluation (attribute value starts with ‘python:’):

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

Pythonic statements

All statements are available in all elements.

if, elif, else statements:

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

for statement (attribute value is Pythonic for style):

>>> teststr('''<p for_="i, j in enumerate(range(3))"><python value="i, j"/></p>''')
'<html><body><p>(0, 0)</p><p>(1, 1)</p><p>(2, 2)</p></body></html>'

while statement:

>>> teststr('''
... <python><![CDATA[ i = [1, 2, 3] ]]></python>
... <p while="i">
...     <python value="i[0]"/>
...     <python><![CDATA[ i = i[1:] ]]></python>
... </p>
... ''')
'<html><body><p>1</p><p>2</p><p>3</p></body></html>'

except statement:

>>> teststr('''
... <python except="StandardError as e"><![CDATA[ not_found ]]></python>
... <python value="e"/>
... <python except="StandardError"><![CDATA[ not_found ]]></python>
... ''')
"<html><body>name 'not_found' is not defined</body></html>"

with statement:

>>> echo('msg.txt', 'hello, world')
>>> teststr('''
... <python with="open(r'%s') as fp">
...     <p><python value="fp.read()"/></p>
...     <p><python value="fp.closed"/></p>
... </python>
... <p><python value="fp.closed"/></p>
... ''' % 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')
>>> teststr('''
... <python with="open(r'%s') as fp, open(r'%s') as fp2">
...     <p><python value="fp.read()"/></p>
...     <p><python value="fp2.read()"/></p>
... </python>
... ''' % (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 context by keyword arguments):

>>> teststr('''
... <p def="myfunc">hello, <python value="msg"/></p>
... <python value="myfunc(msg='world')" escape="false"/>
... ''')
'<html><body><p>hello,world</p></body></html>'

Embedded python script

CDATA is required and use write function like DOM’s document.write:

>>> teststr('''
... <python><![CDATA[
...     write('<p>hello, world</p>')
... ]]></python>
... ''')
'<html><body><p>hello, world</p></body></html>'

and escape xml entities:

>>> teststr('''
... <python><![CDATA[
...     write('<p>', 'hello, world', '</p>', escape=True)
... ]]></python>
... ''')
'<html><body>&lt;p&gt;hello, world&lt;/p&gt;</body></html>'

Include python script file:

>>> echo('sub-script.py', '''write('hello, world')''')
>>> echoxml('template.html', '''
...     <p><python src="sub-script.py"/></p>
... ''')
>>> testfile(os.path.join(tmpdir, 'template.html'))
'<html><body><p>hello, world</p></body></html>'

and share variables:

>>> echo('sub-script.py', '''
... global msg
... msg = 'hello, world'
... msg2 = 'hello, world'
... global myfunc
... def myfunc(name):
...     return 'hello, ' + name
... ''')
>>> echoxml('template.html', '''
...     <python src="sub-script.py"/>
...     <p><python value="msg"/></p>
...     <p>
...         <python value="msg2" __mode__="strict" except="NameError as e"/>
...         <python value="e"/>
...     </p>
...     <p><python value="myfunc('world')"/></p>
... ''')
>>> testfile(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>"

Include another template

Simply, include all elements:

>>> echoxml('sub-template.html', '<p>hello, world</p>')
>>> echoxml('template.html', '<python template="sub-template.html"/>')
>>> testfile(os.path.join(tmpdir, 'template.html'))
'<html><body><html><body><p>hello, world</p></body></html></body></html>'

Then include a part of elements:

>>> echoxml('sub-template.html', '<p id="myid">hello, world</p>')
>>> echoxml('template.html',
...      '<python template="sub-template.html" fragment="myid"/>')
>>> testfile(os.path.join(tmpdir, 'template.html'))
'<html><body><p id="myid">hello, world</p></body></html>'

And share variables:

>>> echoxml('sub-template.html', '''
...     <p id="myid">hello, world</p>
...     <python><![CDATA[
...         global msg
...         msg = 'hello, world'
...     ]]></python>
...     <p def="global myfunc"><python value="text"/></p>
... ''')
>>> echoxml('template.html', '''
...     <python template="sub-template.html" fragment="myid"/>
...     <p><python value="msg"/></p>
...     <python value="myfunc(text='hello, world')" escape="false"/>
... ''')
>>> testfile(os.path.join(tmpdir, 'template.html'))
'<html><body><p id="myid">hello, world</p><p>hello, world</p><p>hello, world</p></body></html>'

Techniques and notices

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

This module works with null xml namespace, but doesn’t remove any namespace:

>>> testfile(StringIO('''<?xml version="1.0"?>
... <root xmlns    = "http://default-namespace.org/"
...       xmlns:py = "http://www.python.org/ns/">
...     <py:elem1 py:if="0"/>
...     <elem2 xmlns="" />
...     <py:elem3 if="0"/>
... </root>'''))
'<?xml version="1.0"?>\n<root xmlns="http://default-namespace.org/" xmlns:py="http://www.python.org/ns/"><py:elem1 py:if="0"/><elem2 xmlns=""/></root>'

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

>>> teststr('''
... <python><![CDATA[ a = b = 0 ]]></python>
... <python def="myfunc"><![CDATA[
...     global a
...     write('a = %d\\n' % a)
...     write('b = %d\\n' % b)
...     a = b = 1
... ]]></python>
... <python value="myfunc()"/>
... <python value="a, b"/>
... ''')
'<html><body>a = 0\nb = 0\n(1, 0)</body></html>'

By the default, White spaces and comments are stripped:

>>> teststr('''<p><!-- comment --> hello, world </p>''')
'<html><body><p>hello, world</p></body></html>'

Use direct mode:

>>> teststr('''<p __mode__="direct"><!-- comment --> hello, world </p>''')
'<html><body><p><!-- comment --> hello, world </p></body></html>'

The attribute order is important:

>>> teststr('''
... <p if="0" for_="i in range(2)"><python value="i"/></p>
... <p for_="i in range(2)" if="i > 0"><python value="i"/></p>
... ''')
'<html><body><p>1</p></body></html>'

If you need closing tag such as textarea, then write below:

>>> teststr('''<textarea><python/></textarea>''')
'<html><body><textarea></textarea></body></html>'

Entities will not be expanded:

>>> teststr('''&nbsp;&unknown_entity;''')
'<html><body>&nbsp;&unknown_entity;</body></html>'
Special variables are available in some cases:
  • __file__ = str -> path of file (template or script)
  • __noloop__ = bool -> whether loop statements executed
    (and when not strict mode)
  • _ = 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.1.1 updated document for PyPI
  • 0.1.0 first release

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for katagami, version 0.1.1
Filename, size File type Python version Upload date Hashes
Filename, size katagami-0.1.1.zip (15.1 kB) File type Source Python version None Upload date Hashes View

Supported by

AWS AWS Cloud computing Datadog Datadog Monitoring DigiCert DigiCert EV certificate Facebook / Instagram Facebook / Instagram PSF Sponsor Fastly Fastly CDN Google Google Object Storage and Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Salesforce Salesforce PSF Sponsor Sentry Sentry Error logging StatusPage StatusPage Status page