Filters

Twiddler allows the use of filters to manipulate text that is inserted by the 'replace' and 'repeat' method of TwiddlerElements. In fact, by default, a filter is used that will replace characters that mean something in xml and html with their escaped equivalents:

>>> from twiddler import Twiddler
>>> t = Twiddler('''<html>
...   <body id="body">
...   </body>
... </html>''')
>>> t['body'].replace('<div/>foo & bar',title='<baz> & <bob>')
>>> print t.render()
<html>
  <body id="body" title="&lt;baz&gt; &amp; &lt;bob&gt;">&lt;div/&gt;foo &amp; bar</body>
</html>

Now, in certain circumstances, you may want to override filtering for a specific operation. In the above example, we may be deliberately trying to insert html. In this case, filtering can be turned off:

>>> t['body'].replace('<div id="foo">foo</div>',title=False,filters=False)
>>> print t.render()
<html>
  <body id="body"><div id="foo">foo</div></body>
</html>

NB: Inserting tagged content in this way does not created new elements in the twiddler:

>>> t['foo']
Traceback (most recent call last):
...
KeyError:...

You may also want to provide your own filters for specific operations. A common example of this is for internationalising content. In this case you will likely have a function that can be imported to do the translation:

>>> phrases = {'hello':'bonjour'}
>>> def translate(phrase):
...     return phrases.get(phrase,'untranslated: '+phrase)

This can now be used as a filter as follows:

>>> t['body'].replace('hello',title='hello',filters=(translate,))
>>> print t.render()
<html>
  <body id="body" title="bonjour">bonjour</body>
</html>

As an aside, some translation functions require the translation domain to be supplied:

>>> domains = {'mydomain':{'hello':'bonjour'}}
>>> def anothertranslate(domain,phrase):
...     return domains.get(domain).get(phrase,'untranslated: '+phrase)

Where this is the case, you can manually use the function prior to the twiddler operation:

>>> t['body'].replace(anothertranslate('mydomain','hello'))
>>> print t.render()
<html>
  <body id="body" title="bonjour">bonjour</body>
</html>

You can also construct a helper object to use as a filter:

>>> class Translator:
...   def __init__(self,domain):self.domain=domain
...   def __call__(self,phrase):return anothertranslate(self.domain,phrase)
>>> translate = Translator('mydomain')
>>> t['body'].replace('hello',title='hello',filters=(translate,))
>>> print t.render()
<html>
  <body id="body" title="bonjour">bonjour</body>
</html>

As you may have guessed from the syntax above, multiple filters can be supplied. In this case, they are applied in order. To demonstrate this, we need an example filter that is somewhat meaningless:

>>> mungers = {'hello':'we got hello!','bonjour':'we got bonjour!'}
>>> def munge(text):
...     return mungers.get(text,text)

We can now see the results of applying filters in different orders:

>>> t['body'].replace('hello',filters=(translate,munge))
>>> print t.render()
<html>
  <body id="body" title="bonjour">we got bonjour!</body>
</html>
>>> t['body'].replace(title='hello',filters=(munge,translate))
>>> print t.render()
<html>
  <body id="body" title="untranslated: we got hello!">we got bonjour!</body>
</html>

We've seen already that specifying filters like this overrides any default filters. This can have downsides:

>>> t['body'].replace('hello',title='<eep>',filters=translate)
>>> print t.render()
<html>
  <body id="body" title="untranslated: <eep>">bonjour</body>
</html>

So, you can specify that the default filters should be included. You do this by including True somewhere in the sequence of filters you provide:

>>> t['body'].replace('hello',title='<eep>',filters=(translate,True))
>>> print t.render()
<html>
  <body id="body" title="untranslated: &lt;eep&gt;">bonjour</body>
</html>

It may be that you want some filters, such as translation, to be applied for every operation you perform on a twiddler. This can be achieved by passing a sequence of filters during instantiation of the twiddler:

>>> t2 = Twiddler('''<html>
...   <body id="body">
...   </body>
... </html>''', filters=(translate,))

The translate filter will now be used for every operation, but the default quoting filter will not be applied, since we did not include it in the sequence:

>>> t2['body'].replace('hello',title='<eep>')
>>> print t2.render()
<html>
  <body id="body" title="untranslated: <eep>">bonjour</body>
</html>

Now, it may be that you want to do a set of operations where you don't want the default filters supplied during instantiation of the twiddler to be applied and yet you don't want to explicitly specify the filters on every call. In this case, you should use the setFilters method:

>>> t.setFilters(translate,munge,True)

>>> t['body'].replace('hello',title='<eep>')
>>> print t.render()
<html>
  <body id="body" title="untranslated: &lt;eep&gt;">we got bonjour!</body>
</html>

As you can see, True can be used in the same way when calling setFilters as when passing the filters parameter to a twiddler operation. Be aware that calling setFilters modifies the twiddler in the same way as a replace or repeat call does, so you may want to take a clone of the source twiddler before calling it.

changed November 13, 2007