8b66ce807958f2ce5ff9c5ccc3d8029a005c75bb
[jinja2.git] / jinja2 / filters.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja.filters
4     ~~~~~~~~~~~~~
5
6     Bundled jinja filters.
7
8     :copyright: 2008 by Armin Ronacher, Christoph Hack.
9     :license: BSD, see LICENSE for more details.
10 """
11 import re
12 from random import choice
13 try:
14     from operator import itemgetter
15 except ImportError:
16     itemgetter = lambda a: lambda b: b[a]
17 from urllib import urlencode, quote
18 from jinja.utils import escape
19
20
21 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
22
23
24 def contextfilter(f):
25     """
26     Decorator for marking context dependent filters. The current context
27     argument will be passed as first argument.
28     """
29     f.contextfilter = True
30     return f
31
32
33 def do_replace(s, old, new, count=None):
34     """
35     Return a copy of the value with all occurrences of a substring
36     replaced with a new one. The first argument is the substring
37     that should be replaced, the second is the replacement string.
38     If the optional third argument ``count`` is given, only the first
39     ``count`` occurrences are replaced:
40
41     .. sourcecode:: jinja
42
43         {{ "Hello World"|replace("Hello", "Goodbye") }}
44             -> Goodbye World
45
46         {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
47             -> d'oh, d'oh, aaargh
48     """
49     if not isinstance(old, basestring) or \
50        not isinstance(new, basestring):
51         raise FilterArgumentError('the replace filter requires '
52                                   'string replacement arguments')
53     if count is None:
54         return s.replace(old, new)
55     if not isinstance(count, (int, long)):
56         raise FilterArgumentError('the count parameter of the '
57                                    'replace filter requires '
58                                    'an integer')
59     return s.replace(old, new, count)
60
61
62 def do_upper(s):
63     """
64     Convert a value to uppercase.
65     """
66     return s.upper()
67
68
69 def do_lower(s):
70     """
71     Convert a value to lowercase.
72     """
73     return s.lower()
74
75
76 def do_escape(s, attribute=False):
77     """
78     XML escape ``&``, ``<``, and ``>`` in a string of data. If the
79     optional parameter is `true` this filter will also convert
80     ``"`` to ``&quot;``. This filter is just used if the environment
81     was configured with disabled `auto_escape`.
82
83     This method will have no effect it the value is already escaped.
84     """
85     # XXX: Does this still exists?
86     #if isinstance(s, TemplateData):
87     #    return s
88     if hasattr(s, '__html__'):
89         return s.__html__()
90     return escape(unicode(s), attribute)
91
92
93 def do_xmlattr(d, autospace=False):
94     """
95     Create an SGML/XML attribute string based on the items in a dict.
96     All values that are neither `none` nor `undefined` are automatically
97     escaped:
98
99     .. sourcecode:: html+jinja
100
101         <ul{{ {'class': 'my_list', 'missing': None,
102                 'id': 'list-%d'|format(variable)}|xmlattr }}>
103         ...
104         </ul>
105
106     Results in something like this:
107
108     .. sourcecode:: html
109
110         <ul class="my_list" id="list-42">
111         ...
112         </ul>
113
114     As you can see it automatically prepends a space in front of the item
115     if the filter returned something. You can disable this by passing
116     `false` as only argument to the filter.
117
118     *New in Jinja 1.1*
119     """
120     if not hasattr(d, 'iteritems'):
121         raise TypeError('a dict is required')
122     result = []
123     for key, value in d.iteritems():
124         if value not in (None, env.undefined_singleton):
125             result.append(u'%s="%s"' % (
126                 escape(env.to_unicode(key)),
127                 escape(env.to_unicode(value), True)
128             ))
129     rv = u' '.join(result)
130     if autospace:
131         rv = ' ' + rv
132     return rv
133
134
135 def do_capitalize(s):
136     """
137     Capitalize a value. The first character will be uppercase, all others
138     lowercase.
139     """
140     return unicode(s).capitalize()
141
142
143 def do_title(s):
144     """
145     Return a titlecased version of the value. I.e. words will start with
146     uppercase letters, all remaining characters are lowercase.
147     """
148     return unicode(s).title()
149
150
151 def do_dictsort(case_sensitive=False, by='key'):
152     """
153     Sort a dict and yield (key, value) pairs. Because python dicts are
154     unsorted you may want to use this function to order them by either
155     key or value:
156
157     .. sourcecode:: jinja
158
159         {% for item in mydict|dictsort %}
160             sort the dict by key, case insensitive
161
162         {% for item in mydict|dicsort(true) %}
163             sort the dict by key, case sensitive
164
165         {% for item in mydict|dictsort(false, 'value') %}
166             sort the dict by key, case insensitive, sorted
167             normally and ordered by value.
168     """
169     if by == 'key':
170         pos = 0
171     elif by == 'value':
172         pos = 1
173     else:
174         raise FilterArgumentError('You can only sort by either '
175                                   '"key" or "value"')
176     def sort_func(value, env):
177         if isinstance(value, basestring):
178             value = env.to_unicode(value)
179             if not case_sensitive:
180                 value = value.lower()
181         return value
182
183     def wrapped(env, context, value):
184         items = value.items()
185         items.sort(lambda a, b: cmp(sort_func(a[pos], env),
186                                     sort_func(b[pos], env)))
187         return items
188     return wrapped
189
190
191 def do_default(value, default_value=u'', boolean=False):
192     """
193     If the value is undefined it will return the passed default value,
194     otherwise the value of the variable:
195
196     .. sourcecode:: jinja
197
198         {{ my_variable|default('my_variable is not defined') }}
199
200     This will output the value of ``my_variable`` if the variable was
201     defined, otherwise ``'my_variable is not defined'``. If you want
202     to use default with variables that evaluate to false you have to
203     set the second parameter to `true`:
204
205     .. sourcecode:: jinja
206
207         {{ ''|default('the string was empty', true) }}
208     """
209     # XXX: undefined_sigleton
210     if (boolean and not value) or value in (env.undefined_singleton, None):
211         return default_value
212     return value
213
214
215 def do_join(value, d=u''):
216     """
217     Return a string which is the concatenation of the strings in the
218     sequence. The separator between elements is an empty string per
219     default, you can define ith with the optional parameter:
220
221     .. sourcecode:: jinja
222
223         {{ [1, 2, 3]|join('|') }}
224             -> 1|2|3
225
226         {{ [1, 2, 3]|join }}
227             -> 123
228     """
229     return unicode(d).join([unicode(x) for x in value])
230
231
232 def do_count():
233     """
234     Return the length of the value. In case if getting an integer or float
235     it will convert it into a string an return the length of the new
236     string. If the object has no length it will of corse return 0.
237     """
238     try:
239         if type(value) in (int, float, long):
240             return len(str(value))
241         return len(value)
242     except TypeError:
243         return 0
244
245
246 def do_reverse(l):
247     """
248     Return a reversed list of the sequence filtered. You can use this
249     for example for reverse iteration:
250
251     .. sourcecode:: jinja
252
253         {% for item in seq|reverse %}
254             {{ item|e }}
255         {% endfor %}
256     """
257     try:
258         return value[::-1]
259     except:
260         l = list(value)
261         l.reverse()
262         return l
263
264
265 def do_center(value, width=80):
266     """
267     Centers the value in a field of a given width.
268     """
269     return unicode(value).center(width)
270
271
272 def do_first(seq):
273     """
274     Return the frist item of a sequence.
275     """
276     try:
277         return iter(seq).next()
278     except StopIteration:
279         return env.undefined_singleton
280
281
282 def do_last(seq):
283     """
284     Return the last item of a sequence.
285     """
286     try:
287         return iter(reversed(seq)).next()
288     except StopIteration:
289         return env.undefined_singleton
290
291
292 def do_random():
293     """
294     Return a random item from the sequence.
295     """
296     try:
297         return choice(seq)
298     except IndexError:
299         return env.undefined_singleton
300
301
302 def do_urlencode(value):
303     """
304     urlencode a string or directory.
305
306     .. sourcecode:: jinja
307
308         {{ {'foo': 'bar', 'blub': 'blah'}|urlencode }}
309             -> foo=bar&blub=blah
310
311         {{ 'Hello World' }}
312             -> Hello%20World
313     """
314     if isinstance(value, dict):
315         tmp = {}
316         for key, value in value.iteritems():
317             # XXX env.charset?
318             key = unicode(key).encode(env.charset)
319             value = unicode(value).encode(env.charset)
320             tmp[key] = value
321         return urlencode(tmp)
322     else:
323         # XXX: env.charset?
324         return quote(unicode(value).encode(env.charset))
325
326
327 def do_jsonencode(value):
328     """
329     JSON dump a variable. just works if simplejson is installed.
330
331     .. sourcecode:: jinja
332
333         {{ 'Hello World'|jsonencode }}
334             -> "Hello World"
335     """
336     global simplejson
337     try:
338         simplejson
339     except NameError:
340         import simplejson
341     return simplejson.dumps(value)
342
343
344 def do_filesizeformat():
345     """
346     Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
347     bytes, etc).
348     """
349     def wrapped(env, context, value):
350         # fail silently
351         try:
352             bytes = float(value)
353         except TypeError:
354             bytes = 0
355
356         if bytes < 1024:
357             return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
358         elif bytes < 1024 * 1024:
359             return "%.1f KB" % (bytes / 1024)
360         elif bytes < 1024 * 1024 * 1024:
361             return "%.1f MB" % (bytes / (1024 * 1024))
362         return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
363     return wrapped
364
365
366 def do_pprint(value, verbose=False):
367     """
368     Pretty print a variable. Useful for debugging.
369
370     With Jinja 1.2 onwards you can pass it a parameter.  If this parameter
371     is truthy the output will be more verbose (this requires `pretty`)
372     """
373     return pformat(value, verbose=verbose)
374
375
376 def do_urlize(value, trim_url_limit=None, nofollow=False):
377     """
378     Converts URLs in plain text into clickable links.
379
380     If you pass the filter an additional integer it will shorten the urls
381     to that number. Also a third argument exists that makes the urls
382     "nofollow":
383
384     .. sourcecode:: jinja
385
386         {{ mytext|urlize(40, True) }}
387             links are shortened to 40 chars and defined with rel="nofollow"
388     """
389     return urlize(unicode(value), trim_url_limit, nofollow)
390
391
392 def do_indent(s, width=4, indentfirst=False):
393     """
394     {{ s|indent[ width[ indentfirst[ usetab]]] }}
395
396     Return a copy of the passed string, each line indented by
397     4 spaces. The first line is not indented. If you want to
398     change the number of spaces or indent the first line too
399     you can pass additional parameters to the filter:
400
401     .. sourcecode:: jinja
402
403         {{ mytext|indent(2, True) }}
404             indent by two spaces and indent the first line too.
405     """
406     indention = ' ' * width
407     if indentfirst:
408         return u'\n'.join([indention + line for line in s.splitlines()])
409     return s.replace('\n', '\n' + indention)
410
411
412 def do_truncate(s, length=255, killwords=False, end='...'):
413     """
414     Return a truncated copy of the string. The length is specified
415     with the first parameter which defaults to ``255``. If the second
416     parameter is ``true`` the filter will cut the text at length. Otherwise
417     it will try to save the last word. If the text was in fact
418     truncated it will append an ellipsis sign (``"..."``). If you want a
419     different ellipsis sign than ``"..."`` you can specify it using the
420     third parameter.
421
422     .. sourcecode jinja::
423
424         {{ mytext|truncate(300, false, '&raquo;') }}
425             truncate mytext to 300 chars, don't split up words, use a
426             right pointing double arrow as ellipsis sign.
427     """
428     if len(s) <= length:
429         return s
430     elif killwords:
431         return s[:length] + end
432     words = s.split(' ')
433     result = []
434     m = 0
435     for word in words:
436         m += len(word) + 1
437         if m > length:
438             break
439         result.append(word)
440     result.append(end)
441     return u' '.join(result)
442
443
444 def do_wordwrap(s, pos=79, hard=False):
445     """
446     Return a copy of the string passed to the filter wrapped after
447     ``79`` characters. You can override this default using the first
448     parameter. If you set the second parameter to `true` Jinja will
449     also split words apart (usually a bad idea because it makes
450     reading hard).
451     """
452     if len(s) < pos:
453         return s
454     if hard:
455         return u'\n'.join([s[idx:idx + pos] for idx in
456                           xrange(0, len(s), pos)])
457     # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
458     return reduce(lambda line, word, pos=pos: u'%s%s%s' %
459                   (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
460                                 len(word.split('\n', 1)[0]) >= pos)],
461                    word), s.split(' '))
462
463
464 def do_wordcount(s):
465     """
466     Count the words in that string.
467     """
468     return len([x for x in s.split() if x])
469
470
471 def do_textile(s):
472     """
473     Prase the string using textile.
474
475     requires the `PyTextile`_ library.
476
477     .. _PyTextile: http://dealmeida.net/projects/textile/
478     """
479     from textile import textile
480     return textile(s.encode('utf-8')).decode('utf-8')
481
482
483 def do_markdown(s):
484     """
485     Parse the string using markdown.
486
487     requires the `Python-markdown`_ library.
488
489     .. _Python-markdown: http://www.freewisdom.org/projects/python-markdown/
490     """
491     from markdown import markdown
492     return markdown(s.encode('utf-8')).decode('utf-8')
493
494
495 def do_rst(s):
496     """
497     Parse the string using the reStructuredText parser from the
498     docutils package.
499
500     requires `docutils`_.
501
502     .. _docutils: http://docutils.sourceforge.net/
503     """
504     from docutils.core import publish_parts
505     parts = publish_parts(source=s, writer_name='html4css1')
506     return parts['fragment']
507
508 def do_int(default=0):
509     """
510     Convert the value into an integer. If the
511     conversion doesn't work it will return ``0``. You can
512     override this default using the first parameter.
513     """
514     def wrapped(env, context, value):
515         try:
516             return int(value)
517         except (TypeError, ValueError):
518             try:
519                 return int(float(value))
520             except (TypeError, ValueError):
521                 return default
522     return wrapped
523
524
525 def do_float(default=0.0):
526     """
527     Convert the value into a floating point number. If the
528     conversion doesn't work it will return ``0.0``. You can
529     override this default using the first parameter.
530     """
531     def wrapped(env, context, value):
532         try:
533             return float(value)
534         except (TypeError, ValueError):
535             return default
536     return wrapped
537
538
539 def do_string():
540     """
541     Convert the value into an string.
542     """
543     return lambda e, c, v: e.to_unicode(v)
544
545
546 def do_format(*args):
547     """
548     Apply python string formatting on an object:
549
550     .. sourcecode:: jinja
551
552         {{ "%s - %s"|format("Hello?", "Foo!") }}
553             -> Hello? - Foo!
554
555     Note that you cannot use the mapping syntax (``%(name)s``)
556     like in python. Use `|dformat` for that.
557     """
558     def wrapped(env, context, value):
559         return env.to_unicode(value) % args
560     return wrapped
561
562
563 def do_dformat(d):
564     """
565     Apply python mapping string formatting on an object:
566
567     .. sourcecode:: jinja
568
569         {{ "Hello %(username)s!"|dformat({'username': 'John Doe'}) }}
570             -> Hello John Doe!
571
572     This is useful when adding variables to translateable
573     string expressions.
574
575     *New in Jinja 1.1*
576     """
577     if not isinstance(d, dict):
578         raise FilterArgumentError('dict required')
579     def wrapped(env, context, value):
580         return env.to_unicode(value) % d
581     return wrapped
582
583
584 def do_trim(value):
585     """
586     Strip leading and trailing whitespace.
587     """
588     return value.strip()
589
590
591 def do_capture(name='captured', clean=False):
592     """
593     Store the value in a variable called ``captured`` or a variable
594     with the name provided. Useful for filter blocks:
595
596     .. sourcecode:: jinja
597
598         {% filter capture('foo') %}
599             ...
600         {% endfilter %}
601         {{ foo }}
602
603     This will output "..." two times. One time from the filter block
604     and one time from the variable. If you don't want the filter to
605     output something you can use it in `clean` mode:
606
607     .. sourcecode:: jinja
608
609         {% filter capture('foo', True) %}
610             ...
611         {% endfilter %}
612         {{ foo }}
613     """
614     if not isinstance(name, basestring):
615         raise FilterArgumentError('You can only capture into variables')
616     def wrapped(env, context, value):
617         context[name] = value
618         if clean:
619             return TemplateData()
620         return value
621     return wrapped
622
623
624 def do_striptags(value):
625     """
626     Strip SGML/XML tags and replace adjacent whitespace by one space.
627
628     *new in Jinja 1.1*
629     """
630     return ' '.join(_striptags_re.sub('', value).split())
631
632
633 def do_slice(slices, fill_with=None):
634     """
635     Slice an iterator and return a list of lists containing
636     those items. Useful if you want to create a div containing
637     three div tags that represent columns:
638
639     .. sourcecode:: html+jinja
640
641         <div class="columwrapper">
642           {%- for column in items|slice(3) %}
643             <ul class="column-{{ loop.index }}">
644             {%- for item in column %}
645               <li>{{ item }}</li>
646             {%- endfor %}
647             </ul>
648           {%- endfor %}
649         </div>
650
651     If you pass it a second argument it's used to fill missing
652     values on the last iteration.
653
654     *new in Jinja 1.1*
655     """
656     def wrapped(env, context, value):
657         result = []
658         seq = list(value)
659         length = len(seq)
660         items_per_slice = length // slices
661         slices_with_extra = length % slices
662         offset = 0
663         for slice_number in xrange(slices):
664             start = offset + slice_number * items_per_slice
665             if slice_number < slices_with_extra:
666                 offset += 1
667             end = offset + (slice_number + 1) * items_per_slice
668             tmp = seq[start:end]
669             if fill_with is not None and slice_number >= slices_with_extra:
670                 tmp.append(fill_with)
671             result.append(tmp)
672         return result
673     return wrapped
674
675
676 def do_batch(linecount, fill_with=None):
677     """
678     A filter that batches items. It works pretty much like `slice`
679     just the other way round. It returns a list of lists with the
680     given number of items. If you provide a second parameter this
681     is used to fill missing items. See this example:
682
683     .. sourcecode:: html+jinja
684
685         <table>
686         {%- for row in items|batch(3, '&nbsp;') %}
687           <tr>
688           {%- for column in row %}
689             <tr>{{ column }}</td>
690           {%- endfor %}
691           </tr>
692         {%- endfor %}
693         </table>
694
695     *new in Jinja 1.1*
696     """
697     def wrapped(env, context, value):
698         result = []
699         tmp = []
700         for item in value:
701             if len(tmp) == linecount:
702                 result.append(tmp)
703                 tmp = []
704             tmp.append(item)
705         if tmp:
706             if fill_with is not None and len(tmp) < linecount:
707                 tmp += [fill_with] * (linecount - len(tmp))
708             result.append(tmp)
709         return result
710     return wrapped
711
712
713 def do_sum():
714     """
715     Sum up the given sequence of numbers.
716
717     *new in Jinja 1.1*
718     """
719     def wrapped(env, context, value):
720         return sum(value)
721     return wrapped
722
723
724 def do_abs():
725     """
726     Return the absolute value of a number.
727
728     *new in Jinja 1.1*
729     """
730     def wrapped(env, context, value):
731         return abs(value)
732     return wrapped
733
734
735 def do_round(precision=0, method='common'):
736     """
737     Round the number to a given precision. The first
738     parameter specifies the precision (default is ``0``), the
739     second the rounding method:
740
741     - ``'common'`` rounds either up or down
742     - ``'ceil'`` always rounds up
743     - ``'floor'`` always rounds down
744
745     If you don't specify a method ``'common'`` is used.
746
747     .. sourcecode:: jinja
748
749         {{ 42.55|round }}
750             -> 43
751         {{ 42.55|round(1, 'floor') }}
752             -> 42.5
753
754     *new in Jinja 1.1*
755     """
756     if not method in ('common', 'ceil', 'floor'):
757         raise FilterArgumentError('method must be common, ceil or floor')
758     if precision < 0:
759         raise FilterArgumentError('precision must be a postive integer '
760                                   'or zero.')
761     def wrapped(env, context, value):
762         if method == 'common':
763             return round(value, precision)
764         import math
765         func = getattr(math, method)
766         if precision:
767             return func(value * 10 * precision) / (10 * precision)
768         else:
769             return func(value)
770     return wrapped
771
772
773 def do_sort(reverse=False):
774     """
775     Sort a sequence. Per default it sorts ascending, if you pass it
776     `True` as first argument it will reverse the sorting.
777
778     *new in Jinja 1.1*
779     """
780     def wrapped(env, context, value):
781         return sorted(value, reverse=reverse)
782     return wrapped
783
784
785 def do_groupby(attribute):
786     """
787     Group a sequence of objects by a common attribute.
788
789     If you for example have a list of dicts or objects that represent persons
790     with `gender`, `first_name` and `last_name` attributes and you want to
791     group all users by genders you can do something like the following
792     snippet:
793
794     .. sourcecode:: html+jinja
795
796         <ul>
797         {% for group in persons|groupby('gender') %}
798             <li>{{ group.grouper }}<ul>
799             {% for person in group.list %}
800                 <li>{{ person.first_name }} {{ person.last_name }}</li>
801             {% endfor %}</ul></li>
802         {% endfor %}
803         </ul>
804
805     As you can see the item we're grouping by is stored in the `grouper`
806     attribute and the `list` contains all the objects that have this grouper
807     in common.
808
809     *New in Jinja 1.2*
810     """
811     def wrapped(env, context, value):
812         expr = lambda x: env.get_attribute(x, attribute)
813         return sorted([{
814             'grouper':  a,
815             'list':     list(b)
816         } for a, b in groupby(sorted(value, key=expr), expr)],
817             key=itemgetter('grouper'))
818     return wrapped
819
820
821 def do_getattribute(attribute):
822     """
823     Get one attribute from an object. Normally you don't have to use this
824     filter because the attribute and subscript expressions try to either
825     get an attribute of an object or an item. In some situations it could
826     be that there is an item *and* an attribute with the same name. In that
827     situation only the item is returned, never the attribute.
828
829     .. sourcecode:: jinja
830
831         {{ foo.bar }} -> {{ foo|getattribute('bar') }}
832
833     *New in Jinja 1.2*
834     """
835     def wrapped(env, context, value):
836         try:
837             return get_attribute(value, attribute)
838         except (SecurityException, AttributeError):
839             return env.undefined_singleton
840     return wrapped
841
842
843 def do_getitem(key):
844     """
845     This filter basically works like the normal subscript expression but
846     it doesn't fall back to attribute lookup. If an item does not exist for
847     an object undefined is returned.
848
849     .. sourcecode:: jinja
850
851         {{ foo.bar }} -> {{ foo|getitem('bar') }}
852
853     *New in Jinja 1.2*
854     """
855     def wrapped(env, context, value):
856         try:
857             return value[key]
858         except (TypeError, KeyError, IndexError, AttributeError):
859             return env.undefined_singleton
860     return wrapped
861
862
863 FILTERS = {
864     'replace':              do_replace,
865     'upper':                do_upper,
866     'lower':                do_lower,
867     'escape':               do_escape,
868     'e':                    do_escape,
869     'xmlattr':              do_xmlattr,
870     'capitalize':           do_capitalize,
871     'title':                do_title,
872     'default':              do_default,
873     'join':                 do_join,
874     'count':                do_count,
875     'dictsort':             do_dictsort,
876     'length':               do_count,
877     'reverse':              do_reverse,
878     'center':               do_center,
879     'title':                do_title,
880     'capitalize':           do_capitalize,
881     'first':                do_first,
882     'last':                 do_last,
883     'random':               do_random,
884     'urlencode':            do_urlencode,
885     'jsonencode':           do_jsonencode,
886     'filesizeformat':       do_filesizeformat,
887     'pprint':               do_pprint,
888     'indent':               do_indent,
889     'truncate':             do_truncate,
890     'wordwrap':             do_wordwrap,
891     'wordcount':            do_wordcount,
892     'textile':              do_textile,
893     'markdown':             do_markdown,
894     'rst':                  do_rst,
895     'int':                  do_int,
896     'float':                do_float,
897     'string':               do_string,
898     'urlize':               do_urlize,
899     'format':               do_format,
900     'dformat':              do_dformat,
901     'capture':              do_capture,
902     'trim':                 do_trim,
903     'striptags':            do_striptags,
904     'slice':                do_slice,
905     'batch':                do_batch,
906     'sum':                  do_sum,
907     'abs':                  do_abs,
908     'round':                do_round,
909     'sort':                 do_sort,
910     'groupby':              do_groupby,
911     'getattribute':         do_getattribute,
912     'getitem':              do_getitem
913 }