Repeat Reference
This document covers all the ways of repeating elements found in a
Twiddler.
Lets start with a simple example which shows how you repeat
elements by creating a repeater from an element and then calling
its repeat method.
With this example, we'll also show how the repeat method takes the
same parameters as replace and applies them to the newly inserted element:
>>> from twiddler import Twiddler
>>> t = Twiddler('''<html>
... <body>
... <div name="stuff">row</div>
... </body>
... </html>''')
>>> e = t['stuff'].repeater()
>>> for i in range(3):
... e.repeat('row %i'%i,tag='span',id='row'+str(i))
<twiddler...>
>>> print t.render()
<html>
<body>
<span id="row0" name="stuff">row 0</span>
<span id="row1" name="stuff">row 1</span>
<span id="row2" name="stuff">row 2</span>
</body>
</html>
As can sort of be seen above, repeat returns the newly inserted
element. This is to allow us to easily do more complicated repeats:
>>> t = Twiddler('''<html>
... <body>
... <div name="stuff"><span name="substuff"/></div>
... </body>
... </html>''')
>>> e = t['stuff'].repeater()
>>> for i in range(4):
... if i % 2:
... e.repeat('Number %i' % i)
... else:
... n = e.repeat()
... c = n['substuff'].repeater()
... for j in range(2):
... c.repeat('Number %i' % j,name=False)
<twiddler...>
>>> print t.render()
<html>
<body>
<div name="stuff"><span>Number 0</span><span>Number 1</span></div>
<div name="stuff">Number 1</div>
<div name="stuff"><span>Number 0</span><span>Number 1</span></div>
<div name="stuff">Number 3</div>
</body>
</html>
This behaviour also lets us gracefully tackle problems that some
templating systems find difficult. For example, the generation of
HTML tables where the data is in columns, rather than rows:
>>> class Person:
... def __init__(self,**kw):
... self.__dict__.update(kw)
>>> people = [
... Person(firstname='Tom',surname='Smith'),
... Person(firstname='Dick',surname='Jones'),
... Person(firstname='Harry',surname='Doe'),
... ]
>>> t = Twiddler('''<html>
... <body>
... <table>
... <tr>
... <th>First Name</th>
... <td name="firstname">John</td>
... </tr>
... <tr>
... <th>Last Name</th>
... <td name="lastname">Doe</td>
... </tr>
... </table>
... </body>
... </html>''')
>>> firstname = t['firstname'].repeater()
>>> lastname = t['lastname'].repeater()
>>> for person in people:
... firstname.repeat(person.firstname)
... lastname.repeat(person.surname)
<twiddler...>
>>> print t.render()
<html>
<body>
<table>
<tr>
<th>First Name</th>
<td name="firstname">Tom</td>
<td name="firstname">Dick</td>
<td name="firstname">Harry</td>
</tr>
<tr>
<th>Last Name</th>
<td name="lastname">Smith</td>
<td name="lastname">Jones</td>
<td name="lastname">Doe</td>
</tr>
</table>
</body>
</html>
NB: Once an element has been turned into a repeater, it is removed
from the Twiddler as can be seen in this example:
>>> t = Twiddler('<node id="repeat">A node!</node>')
>>> print t.render()
<node id="repeat">A node!</node>
>>> r = t['repeat'].repeater()
>>> t.render()
u''
>>> t['repeat']
Traceback (most recent call last):
...
KeyError:...
Now, you may be wondering how to repeat elements when the
structure to be generated is recursive, such as a navigation tree.
To explain this, lets look at an example of how to generate the
following xml:
<a><b /><c><e /><f /></c><d /></a>
We'll start with the following data structure:
>>> data = {
... 'tag':'a',
... 'children':[
... {
... 'tag':'b',
... 'children':[]
... },
... {
... 'tag':'c',
... 'children':[
... {
... 'tag':'e',
... 'children':[]
... },
... {
... 'tag':'f',
... 'children':[]
... },
... ]
... },
... {
... 'tag':'d',
... 'children':[]
... },
... ]
... }
The Twiddler we'll start with looks like this:
>>> t = Twiddler('<tag id="parent"><tag id="child"/></tag>')
Now we need to actually manipulate the above Twiddler to give us
the required output. Recursion like this is always tricky, but the
following pattern will solve most problems of this type:
>>> n = t['parent']
>>> p = n.clone()
>>> stack = [(data,n)]
>>> while stack:
... d,n = stack.pop(0)
... n.replace(p.clone(),tag=d['tag'],id=False)
... r = n['child'].repeater()
... for child in d['children']:
... stack.append((child,r.repeat(n)))
Our Twiddler is now as we need it to be:
>>> print t.render()
<a><b /><c><e /><f /></c><d /></a>
The important method above is the 'clone' method of the
repeater. This returns a copy of the repeater element but does not
insert it into the Twidder in the same way that the 'repeat'
method does. The returned element can be inserted in place of any
element in any Twiddler by passing it as the content for an
appropriate replace call.
NB: While the element returned by the clone method can be
manipulated in the same way as any other Twiddler element, it
cannot be searched until it has been inserted into a Twiddler. As
can be seen in the following example, you'll get an error if you
try to do this:
>>> t = Twiddler('''<node id="root">
... <node id="parent"><node id="child"/></node>
... </node>''')
>>> e = t['parent'].clone()
>>> e.replace(tag="child",id=False)
>>> e['child'].replace(tag="subchild",id=False)
Traceback (most recent call last):
...
AttributeError:...
However, once inserted into a Twiddler, everything works as
normal:
>>> t['root'].replace(e)
>>> e['child'].replace(tag="subchild",id=False)
>>> print t.render()
<child><subchild /></child>
<BLANKLINE>
Another common way of generating recursive structures of this
nature is to use a recursive function. The only wart here is
that we need to do slightly more work to keep a pristine copy of
the source element and its children. The following shows how to
achieve this, using the example data structures from above:
>>> t = Twiddler('<tag id="parent"><tag id="child"/></tag>')
>>> def handle(d,n,p=None):
... if p is None:
... p = n.clone()
... n.replace(p.clone(),tag=d['tag'],id=False)
... r = n['child'].repeater()
... for child in d['children']:
... handle(child,r.repeat(n),p)
To render the required xml, we now call the recursive function
with the complete data structure and the first parent:
>>> handle(data,t['parent'])
Our Twiddler is now as we need it to be:
>>> print t.render()
<a><b /><c><e /><f /></c><d /></a>
changed November 13, 2007