Home | History | Annotate | Download | only in jinja2
      1 # -*- coding: utf-8 -*-
      2 """
      3     jinja2.filters
      4     ~~~~~~~~~~~~~~
      5 
      6     Bundled jinja filters.
      7 
      8     :copyright: (c) 2010 by the Jinja Team.
      9     :license: BSD, see LICENSE for more details.
     10 """
     11 import re
     12 import math
     13 from random import choice
     14 from operator import itemgetter
     15 from itertools import imap, groupby
     16 from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
     17 from jinja2.runtime import Undefined
     18 from jinja2.exceptions import FilterArgumentError, SecurityError
     19 
     20 
     21 _word_re = re.compile(r'\w+(?u)')
     22 
     23 
     24 def contextfilter(f):
     25     """Decorator for marking context dependent filters. The current
     26     :class:`Context` will be passed as first argument.
     27     """
     28     f.contextfilter = True
     29     return f
     30 
     31 
     32 def evalcontextfilter(f):
     33     """Decorator for marking eval-context dependent filters.  An eval
     34     context object is passed as first argument.  For more information
     35     about the eval context, see :ref:`eval-context`.
     36 
     37     .. versionadded:: 2.4
     38     """
     39     f.evalcontextfilter = True
     40     return f
     41 
     42 
     43 def environmentfilter(f):
     44     """Decorator for marking evironment dependent filters.  The current
     45     :class:`Environment` is passed to the filter as first argument.
     46     """
     47     f.environmentfilter = True
     48     return f
     49 
     50 
     51 def make_attrgetter(environment, attribute):
     52     """Returns a callable that looks up the given attribute from a
     53     passed object with the rules of the environment.  Dots are allowed
     54     to access attributes of attributes.
     55     """
     56     if not isinstance(attribute, basestring) or '.' not in attribute:
     57         return lambda x: environment.getitem(x, attribute)
     58     attribute = attribute.split('.')
     59     def attrgetter(item):
     60         for part in attribute:
     61             item = environment.getitem(item, part)
     62         return item
     63     return attrgetter
     64 
     65 
     66 def do_forceescape(value):
     67     """Enforce HTML escaping.  This will probably double escape variables."""
     68     if hasattr(value, '__html__'):
     69         value = value.__html__()
     70     return escape(unicode(value))
     71 
     72 
     73 @evalcontextfilter
     74 def do_replace(eval_ctx, s, old, new, count=None):
     75     """Return a copy of the value with all occurrences of a substring
     76     replaced with a new one. The first argument is the substring
     77     that should be replaced, the second is the replacement string.
     78     If the optional third argument ``count`` is given, only the first
     79     ``count`` occurrences are replaced:
     80 
     81     .. sourcecode:: jinja
     82 
     83         {{ "Hello World"|replace("Hello", "Goodbye") }}
     84             -> Goodbye World
     85 
     86         {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
     87             -> d'oh, d'oh, aaargh
     88     """
     89     if count is None:
     90         count = -1
     91     if not eval_ctx.autoescape:
     92         return unicode(s).replace(unicode(old), unicode(new), count)
     93     if hasattr(old, '__html__') or hasattr(new, '__html__') and \
     94        not hasattr(s, '__html__'):
     95         s = escape(s)
     96     else:
     97         s = soft_unicode(s)
     98     return s.replace(soft_unicode(old), soft_unicode(new), count)
     99 
    100 
    101 def do_upper(s):
    102     """Convert a value to uppercase."""
    103     return soft_unicode(s).upper()
    104 
    105 
    106 def do_lower(s):
    107     """Convert a value to lowercase."""
    108     return soft_unicode(s).lower()
    109 
    110 
    111 @evalcontextfilter
    112 def do_xmlattr(_eval_ctx, d, autospace=True):
    113     """Create an SGML/XML attribute string based on the items in a dict.
    114     All values that are neither `none` nor `undefined` are automatically
    115     escaped:
    116 
    117     .. sourcecode:: html+jinja
    118 
    119         <ul{{ {'class': 'my_list', 'missing': none,
    120                 'id': 'list-%d'|format(variable)}|xmlattr }}>
    121         ...
    122         </ul>
    123 
    124     Results in something like this:
    125 
    126     .. sourcecode:: html
    127 
    128         <ul class="my_list" id="list-42">
    129         ...
    130         </ul>
    131 
    132     As you can see it automatically prepends a space in front of the item
    133     if the filter returned something unless the second parameter is false.
    134     """
    135     rv = u' '.join(
    136         u'%s="%s"' % (escape(key), escape(value))
    137         for key, value in d.iteritems()
    138         if value is not None and not isinstance(value, Undefined)
    139     )
    140     if autospace and rv:
    141         rv = u' ' + rv
    142     if _eval_ctx.autoescape:
    143         rv = Markup(rv)
    144     return rv
    145 
    146 
    147 def do_capitalize(s):
    148     """Capitalize a value. The first character will be uppercase, all others
    149     lowercase.
    150     """
    151     return soft_unicode(s).capitalize()
    152 
    153 
    154 def do_title(s):
    155     """Return a titlecased version of the value. I.e. words will start with
    156     uppercase letters, all remaining characters are lowercase.
    157     """
    158     return soft_unicode(s).title()
    159 
    160 
    161 def do_dictsort(value, case_sensitive=False, by='key'):
    162     """Sort a dict and yield (key, value) pairs. Because python dicts are
    163     unsorted you may want to use this function to order them by either
    164     key or value:
    165 
    166     .. sourcecode:: jinja
    167 
    168         {% for item in mydict|dictsort %}
    169             sort the dict by key, case insensitive
    170 
    171         {% for item in mydict|dicsort(true) %}
    172             sort the dict by key, case sensitive
    173 
    174         {% for item in mydict|dictsort(false, 'value') %}
    175             sort the dict by key, case insensitive, sorted
    176             normally and ordered by value.
    177     """
    178     if by == 'key':
    179         pos = 0
    180     elif by == 'value':
    181         pos = 1
    182     else:
    183         raise FilterArgumentError('You can only sort by either '
    184                                   '"key" or "value"')
    185     def sort_func(item):
    186         value = item[pos]
    187         if isinstance(value, basestring) and not case_sensitive:
    188             value = value.lower()
    189         return value
    190 
    191     return sorted(value.items(), key=sort_func)
    192 
    193 
    194 @environmentfilter
    195 def do_sort(environment, value, reverse=False, case_sensitive=False,
    196             attribute=None):
    197     """Sort an iterable.  Per default it sorts ascending, if you pass it
    198     true as first argument it will reverse the sorting.
    199 
    200     If the iterable is made of strings the third parameter can be used to
    201     control the case sensitiveness of the comparison which is disabled by
    202     default.
    203 
    204     .. sourcecode:: jinja
    205 
    206         {% for item in iterable|sort %}
    207             ...
    208         {% endfor %}
    209 
    210     It is also possible to sort by an attribute (for example to sort
    211     by the date of an object) by specifying the `attribute` parameter:
    212 
    213     .. sourcecode:: jinja
    214 
    215         {% for item in iterable|sort(attribute='date') %}
    216             ...
    217         {% endfor %}
    218 
    219     .. versionchanged:: 2.6
    220        The `attribute` parameter was added.
    221     """
    222     if not case_sensitive:
    223         def sort_func(item):
    224             if isinstance(item, basestring):
    225                 item = item.lower()
    226             return item
    227     else:
    228         sort_func = None
    229     if attribute is not None:
    230         getter = make_attrgetter(environment, attribute)
    231         def sort_func(item, processor=sort_func or (lambda x: x)):
    232             return processor(getter(item))
    233     return sorted(value, key=sort_func, reverse=reverse)
    234 
    235 
    236 def do_default(value, default_value=u'', boolean=False):
    237     """If the value is undefined it will return the passed default value,
    238     otherwise the value of the variable:
    239 
    240     .. sourcecode:: jinja
    241 
    242         {{ my_variable|default('my_variable is not defined') }}
    243 
    244     This will output the value of ``my_variable`` if the variable was
    245     defined, otherwise ``'my_variable is not defined'``. If you want
    246     to use default with variables that evaluate to false you have to
    247     set the second parameter to `true`:
    248 
    249     .. sourcecode:: jinja
    250 
    251         {{ ''|default('the string was empty', true) }}
    252     """
    253     if (boolean and not value) or isinstance(value, Undefined):
    254         return default_value
    255     return value
    256 
    257 
    258 @evalcontextfilter
    259 def do_join(eval_ctx, value, d=u'', attribute=None):
    260     """Return a string which is the concatenation of the strings in the
    261     sequence. The separator between elements is an empty string per
    262     default, you can define it with the optional parameter:
    263 
    264     .. sourcecode:: jinja
    265 
    266         {{ [1, 2, 3]|join('|') }}
    267             -> 1|2|3
    268 
    269         {{ [1, 2, 3]|join }}
    270             -> 123
    271 
    272     It is also possible to join certain attributes of an object:
    273 
    274     .. sourcecode:: jinja
    275 
    276         {{ users|join(', ', attribute='username') }}
    277 
    278     .. versionadded:: 2.6
    279        The `attribute` parameter was added.
    280     """
    281     if attribute is not None:
    282         value = imap(make_attrgetter(eval_ctx.environment, attribute), value)
    283 
    284     # no automatic escaping?  joining is a lot eaiser then
    285     if not eval_ctx.autoescape:
    286         return unicode(d).join(imap(unicode, value))
    287 
    288     # if the delimiter doesn't have an html representation we check
    289     # if any of the items has.  If yes we do a coercion to Markup
    290     if not hasattr(d, '__html__'):
    291         value = list(value)
    292         do_escape = False
    293         for idx, item in enumerate(value):
    294             if hasattr(item, '__html__'):
    295                 do_escape = True
    296             else:
    297                 value[idx] = unicode(item)
    298         if do_escape:
    299             d = escape(d)
    300         else:
    301             d = unicode(d)
    302         return d.join(value)
    303 
    304     # no html involved, to normal joining
    305     return soft_unicode(d).join(imap(soft_unicode, value))
    306 
    307 
    308 def do_center(value, width=80):
    309     """Centers the value in a field of a given width."""
    310     return unicode(value).center(width)
    311 
    312 
    313 @environmentfilter
    314 def do_first(environment, seq):
    315     """Return the first item of a sequence."""
    316     try:
    317         return iter(seq).next()
    318     except StopIteration:
    319         return environment.undefined('No first item, sequence was empty.')
    320 
    321 
    322 @environmentfilter
    323 def do_last(environment, seq):
    324     """Return the last item of a sequence."""
    325     try:
    326         return iter(reversed(seq)).next()
    327     except StopIteration:
    328         return environment.undefined('No last item, sequence was empty.')
    329 
    330 
    331 @environmentfilter
    332 def do_random(environment, seq):
    333     """Return a random item from the sequence."""
    334     try:
    335         return choice(seq)
    336     except IndexError:
    337         return environment.undefined('No random item, sequence was empty.')
    338 
    339 
    340 def do_filesizeformat(value, binary=False):
    341     """Format the value like a 'human-readable' file size (i.e. 13 kB,
    342     4.1 MB, 102 Bytes, etc).  Per default decimal prefixes are used (Mega,
    343     Giga, etc.), if the second parameter is set to `True` the binary
    344     prefixes are used (Mebi, Gibi).
    345     """
    346     bytes = float(value)
    347     base = binary and 1024 or 1000
    348     prefixes = [
    349         (binary and "KiB" or "kB"),
    350         (binary and "MiB" or "MB"),
    351         (binary and "GiB" or "GB"),
    352         (binary and "TiB" or "TB"),
    353         (binary and "PiB" or "PB"),
    354         (binary and "EiB" or "EB"),
    355         (binary and "ZiB" or "ZB"),
    356         (binary and "YiB" or "YB")
    357     ]
    358     if bytes == 1:
    359         return "1 Byte"
    360     elif bytes < base:
    361         return "%d Bytes" % bytes
    362     else:
    363         for i, prefix in enumerate(prefixes):
    364             unit = base * base ** (i + 1)
    365             if bytes < unit:
    366                 return "%.1f %s" % ((bytes / unit), prefix)
    367         return "%.1f %s" % ((bytes / unit), prefix)
    368 
    369 
    370 def do_pprint(value, verbose=False):
    371     """Pretty print a variable. Useful for debugging.
    372 
    373     With Jinja 1.2 onwards you can pass it a parameter.  If this parameter
    374     is truthy the output will be more verbose (this requires `pretty`)
    375     """
    376     return pformat(value, verbose=verbose)
    377 
    378 
    379 @evalcontextfilter
    380 def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
    381     """Converts URLs in plain text into clickable links.
    382 
    383     If you pass the filter an additional integer it will shorten the urls
    384     to that number. Also a third argument exists that makes the urls
    385     "nofollow":
    386 
    387     .. sourcecode:: jinja
    388 
    389         {{ mytext|urlize(40, true) }}
    390             links are shortened to 40 chars and defined with rel="nofollow"
    391     """
    392     rv = urlize(value, trim_url_limit, nofollow)
    393     if eval_ctx.autoescape:
    394         rv = Markup(rv)
    395     return rv
    396 
    397 
    398 def do_indent(s, width=4, indentfirst=False):
    399     """Return a copy of the passed string, each line indented by
    400     4 spaces. The first line is not indented. If you want to
    401     change the number of spaces or indent the first line too
    402     you can pass additional parameters to the filter:
    403 
    404     .. sourcecode:: jinja
    405 
    406         {{ mytext|indent(2, true) }}
    407             indent by two spaces and indent the first line too.
    408     """
    409     indention = u' ' * width
    410     rv = (u'\n' + indention).join(s.splitlines())
    411     if indentfirst:
    412         rv = indention + rv
    413     return rv
    414 
    415 
    416 def do_truncate(s, length=255, killwords=False, end='...'):
    417     """Return a truncated copy of the string. The length is specified
    418     with the first parameter which defaults to ``255``. If the second
    419     parameter is ``true`` the filter will cut the text at length. Otherwise
    420     it will try to save the last word. If the text was in fact
    421     truncated it will append an ellipsis sign (``"..."``). If you want a
    422     different ellipsis sign than ``"..."`` you can specify it using the
    423     third parameter.
    424 
    425     .. sourcecode jinja::
    426 
    427         {{ mytext|truncate(300, false, '&raquo;') }}
    428             truncate mytext to 300 chars, don't split up words, use a
    429             right pointing double arrow as ellipsis sign.
    430     """
    431     if len(s) <= length:
    432         return s
    433     elif killwords:
    434         return s[:length] + end
    435     words = s.split(' ')
    436     result = []
    437     m = 0
    438     for word in words:
    439         m += len(word) + 1
    440         if m > length:
    441             break
    442         result.append(word)
    443     result.append(end)
    444     return u' '.join(result)
    445 
    446 @environmentfilter
    447 def do_wordwrap(environment, s, width=79, break_long_words=True):
    448     """
    449     Return a copy of the string passed to the filter wrapped after
    450     ``79`` characters.  You can override this default using the first
    451     parameter.  If you set the second parameter to `false` Jinja will not
    452     split words apart if they are longer than `width`.
    453     """
    454     import textwrap
    455     return environment.newline_sequence.join(textwrap.wrap(s, width=width, expand_tabs=False,
    456                                    replace_whitespace=False,
    457                                    break_long_words=break_long_words))
    458 
    459 
    460 def do_wordcount(s):
    461     """Count the words in that string."""
    462     return len(_word_re.findall(s))
    463 
    464 
    465 def do_int(value, default=0):
    466     """Convert the value into an integer. If the
    467     conversion doesn't work it will return ``0``. You can
    468     override this default using the first parameter.
    469     """
    470     try:
    471         return int(value)
    472     except (TypeError, ValueError):
    473         # this quirk is necessary so that "42.23"|int gives 42.
    474         try:
    475             return int(float(value))
    476         except (TypeError, ValueError):
    477             return default
    478 
    479 
    480 def do_float(value, default=0.0):
    481     """Convert the value into a floating point number. If the
    482     conversion doesn't work it will return ``0.0``. You can
    483     override this default using the first parameter.
    484     """
    485     try:
    486         return float(value)
    487     except (TypeError, ValueError):
    488         return default
    489 
    490 
    491 def do_format(value, *args, **kwargs):
    492     """
    493     Apply python string formatting on an object:
    494 
    495     .. sourcecode:: jinja
    496 
    497         {{ "%s - %s"|format("Hello?", "Foo!") }}
    498             -> Hello? - Foo!
    499     """
    500     if args and kwargs:
    501         raise FilterArgumentError('can\'t handle positional and keyword '
    502                                   'arguments at the same time')
    503     return soft_unicode(value) % (kwargs or args)
    504 
    505 
    506 def do_trim(value):
    507     """Strip leading and trailing whitespace."""
    508     return soft_unicode(value).strip()
    509 
    510 
    511 def do_striptags(value):
    512     """Strip SGML/XML tags and replace adjacent whitespace by one space.
    513     """
    514     if hasattr(value, '__html__'):
    515         value = value.__html__()
    516     return Markup(unicode(value)).striptags()
    517 
    518 
    519 def do_slice(value, slices, fill_with=None):
    520     """Slice an iterator and return a list of lists containing
    521     those items. Useful if you want to create a div containing
    522     three ul tags that represent columns:
    523 
    524     .. sourcecode:: html+jinja
    525 
    526         <div class="columwrapper">
    527           {%- for column in items|slice(3) %}
    528             <ul class="column-{{ loop.index }}">
    529             {%- for item in column %}
    530               <li>{{ item }}</li>
    531             {%- endfor %}
    532             </ul>
    533           {%- endfor %}
    534         </div>
    535 
    536     If you pass it a second argument it's used to fill missing
    537     values on the last iteration.
    538     """
    539     seq = list(value)
    540     length = len(seq)
    541     items_per_slice = length // slices
    542     slices_with_extra = length % slices
    543     offset = 0
    544     for slice_number in xrange(slices):
    545         start = offset + slice_number * items_per_slice
    546         if slice_number < slices_with_extra:
    547             offset += 1
    548         end = offset + (slice_number + 1) * items_per_slice
    549         tmp = seq[start:end]
    550         if fill_with is not None and slice_number >= slices_with_extra:
    551             tmp.append(fill_with)
    552         yield tmp
    553 
    554 
    555 def do_batch(value, linecount, fill_with=None):
    556     """
    557     A filter that batches items. It works pretty much like `slice`
    558     just the other way round. It returns a list of lists with the
    559     given number of items. If you provide a second parameter this
    560     is used to fill missing items. See this example:
    561 
    562     .. sourcecode:: html+jinja
    563 
    564         <table>
    565         {%- for row in items|batch(3, '&nbsp;') %}
    566           <tr>
    567           {%- for column in row %}
    568             <td>{{ column }}</td>
    569           {%- endfor %}
    570           </tr>
    571         {%- endfor %}
    572         </table>
    573     """
    574     result = []
    575     tmp = []
    576     for item in value:
    577         if len(tmp) == linecount:
    578             yield tmp
    579             tmp = []
    580         tmp.append(item)
    581     if tmp:
    582         if fill_with is not None and len(tmp) < linecount:
    583             tmp += [fill_with] * (linecount - len(tmp))
    584         yield tmp
    585 
    586 
    587 def do_round(value, precision=0, method='common'):
    588     """Round the number to a given precision. The first
    589     parameter specifies the precision (default is ``0``), the
    590     second the rounding method:
    591 
    592     - ``'common'`` rounds either up or down
    593     - ``'ceil'`` always rounds up
    594     - ``'floor'`` always rounds down
    595 
    596     If you don't specify a method ``'common'`` is used.
    597 
    598     .. sourcecode:: jinja
    599 
    600         {{ 42.55|round }}
    601             -> 43.0
    602         {{ 42.55|round(1, 'floor') }}
    603             -> 42.5
    604 
    605     Note that even if rounded to 0 precision, a float is returned.  If
    606     you need a real integer, pipe it through `int`:
    607 
    608     .. sourcecode:: jinja
    609 
    610         {{ 42.55|round|int }}
    611             -> 43
    612     """
    613     if not method in ('common', 'ceil', 'floor'):
    614         raise FilterArgumentError('method must be common, ceil or floor')
    615     if method == 'common':
    616         return round(value, precision)
    617     func = getattr(math, method)
    618     return func(value * (10 ** precision)) / (10 ** precision)
    619 
    620 
    621 @environmentfilter
    622 def do_groupby(environment, value, attribute):
    623     """Group a sequence of objects by a common attribute.
    624 
    625     If you for example have a list of dicts or objects that represent persons
    626     with `gender`, `first_name` and `last_name` attributes and you want to
    627     group all users by genders you can do something like the following
    628     snippet:
    629 
    630     .. sourcecode:: html+jinja
    631 
    632         <ul>
    633         {% for group in persons|groupby('gender') %}
    634             <li>{{ group.grouper }}<ul>
    635             {% for person in group.list %}
    636                 <li>{{ person.first_name }} {{ person.last_name }}</li>
    637             {% endfor %}</ul></li>
    638         {% endfor %}
    639         </ul>
    640 
    641     Additionally it's possible to use tuple unpacking for the grouper and
    642     list:
    643 
    644     .. sourcecode:: html+jinja
    645 
    646         <ul>
    647         {% for grouper, list in persons|groupby('gender') %}
    648             ...
    649         {% endfor %}
    650         </ul>
    651 
    652     As you can see the item we're grouping by is stored in the `grouper`
    653     attribute and the `list` contains all the objects that have this grouper
    654     in common.
    655 
    656     .. versionchanged:: 2.6
    657        It's now possible to use dotted notation to group by the child
    658        attribute of another attribute.
    659     """
    660     expr = make_attrgetter(environment, attribute)
    661     return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
    662 
    663 
    664 class _GroupTuple(tuple):
    665     __slots__ = ()
    666     grouper = property(itemgetter(0))
    667     list = property(itemgetter(1))
    668 
    669     def __new__(cls, (key, value)):
    670         return tuple.__new__(cls, (key, list(value)))
    671 
    672 
    673 @environmentfilter
    674 def do_sum(environment, iterable, attribute=None, start=0):
    675     """Returns the sum of a sequence of numbers plus the value of parameter
    676     'start' (which defaults to 0).  When the sequence is empty it returns
    677     start.
    678 
    679     It is also possible to sum up only certain attributes:
    680 
    681     .. sourcecode:: jinja
    682 
    683         Total: {{ items|sum(attribute='price') }}
    684 
    685     .. versionchanged:: 2.6
    686        The `attribute` parameter was added to allow suming up over
    687        attributes.  Also the `start` parameter was moved on to the right.
    688     """
    689     if attribute is not None:
    690         iterable = imap(make_attrgetter(environment, attribute), iterable)
    691     return sum(iterable, start)
    692 
    693 
    694 def do_list(value):
    695     """Convert the value into a list.  If it was a string the returned list
    696     will be a list of characters.
    697     """
    698     return list(value)
    699 
    700 
    701 def do_mark_safe(value):
    702     """Mark the value as safe which means that in an environment with automatic
    703     escaping enabled this variable will not be escaped.
    704     """
    705     return Markup(value)
    706 
    707 
    708 def do_mark_unsafe(value):
    709     """Mark a value as unsafe.  This is the reverse operation for :func:`safe`."""
    710     return unicode(value)
    711 
    712 
    713 def do_reverse(value):
    714     """Reverse the object or return an iterator the iterates over it the other
    715     way round.
    716     """
    717     if isinstance(value, basestring):
    718         return value[::-1]
    719     try:
    720         return reversed(value)
    721     except TypeError:
    722         try:
    723             rv = list(value)
    724             rv.reverse()
    725             return rv
    726         except TypeError:
    727             raise FilterArgumentError('argument must be iterable')
    728 
    729 
    730 @environmentfilter
    731 def do_attr(environment, obj, name):
    732     """Get an attribute of an object.  ``foo|attr("bar")`` works like
    733     ``foo["bar"]`` just that always an attribute is returned and items are not
    734     looked up.
    735 
    736     See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
    737     """
    738     try:
    739         name = str(name)
    740     except UnicodeError:
    741         pass
    742     else:
    743         try:
    744             value = getattr(obj, name)
    745         except AttributeError:
    746             pass
    747         else:
    748             if environment.sandboxed and not \
    749                environment.is_safe_attribute(obj, name, value):
    750                 return environment.unsafe_undefined(obj, name)
    751             return value
    752     return environment.undefined(obj=obj, name=name)
    753 
    754 
    755 FILTERS = {
    756     'attr':                 do_attr,
    757     'replace':              do_replace,
    758     'upper':                do_upper,
    759     'lower':                do_lower,
    760     'escape':               escape,
    761     'e':                    escape,
    762     'forceescape':          do_forceescape,
    763     'capitalize':           do_capitalize,
    764     'title':                do_title,
    765     'default':              do_default,
    766     'd':                    do_default,
    767     'join':                 do_join,
    768     'count':                len,
    769     'dictsort':             do_dictsort,
    770     'sort':                 do_sort,
    771     'length':               len,
    772     'reverse':              do_reverse,
    773     'center':               do_center,
    774     'indent':               do_indent,
    775     'title':                do_title,
    776     'capitalize':           do_capitalize,
    777     'first':                do_first,
    778     'last':                 do_last,
    779     'random':               do_random,
    780     'filesizeformat':       do_filesizeformat,
    781     'pprint':               do_pprint,
    782     'truncate':             do_truncate,
    783     'wordwrap':             do_wordwrap,
    784     'wordcount':            do_wordcount,
    785     'int':                  do_int,
    786     'float':                do_float,
    787     'string':               soft_unicode,
    788     'list':                 do_list,
    789     'urlize':               do_urlize,
    790     'format':               do_format,
    791     'trim':                 do_trim,
    792     'striptags':            do_striptags,
    793     'slice':                do_slice,
    794     'batch':                do_batch,
    795     'sum':                  do_sum,
    796     'abs':                  abs,
    797     'round':                do_round,
    798     'groupby':              do_groupby,
    799     'safe':                 do_mark_safe,
    800     'xmlattr':              do_xmlattr
    801 }
    802