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):
...     result = render('<html><body>%s</body></html>' % t, {'__file__': '<string>'})
...     if sys.platform != 'cli': # IronPython doesn't implement unicode.
...         result = result.encode()
...     return result
>>> 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 dumped in output (and see a helpful traceback):

>>> teststr('''<python value="not_found"/>''')
'<html><body><pre>Traceback (most recent call last):\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></body></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" 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’).

Use __mode__ for disabling any trick:
  • exception dump

  • treat exception as False in if and elif

  • special value named __noloop__

  • script indentation fix

    >>> render('''<html __mode__="strict"><body><p if="fail"/></body></html>''')
    Traceback (most recent call last):
        ...
    NameError: name 'fail' is not defined
    

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.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


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.2.0.zip (15.6 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page