fixed xmlattr again
[jinja2.git] / jinja2 / filters.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.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 import math
13 from random import choice
14 try:
15     from operator import itemgetter
16 except ImportError:
17     itemgetter = lambda a: lambda b: b[a]
18 from urllib import urlencode, quote
19 from itertools import imap, groupby
20 from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
21 from jinja2.runtime import Undefined
22 from jinja2.exceptions import FilterArgumentError
23
24
25 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
26
27
28 def contextfilter(f):
29     """Decorator for marking context dependent filters. The current context
30     argument will be passed as first argument.
31     """
32     if getattr(f, 'environmentfilter', False):
33         raise TypeError('filter already marked as environment filter')
34     f.contextfilter = True
35     return f
36
37
38 def environmentfilter(f):
39     """Decorator for marking evironment dependent filters.  The environment
40     used for the template is passed to the filter as first argument.
41     """
42     if getattr(f, 'contextfilter', False):
43         raise TypeError('filter already marked as context filter')
44     f.environmentfilter = True
45     return f
46
47
48 def do_forceescape(value):
49     """Enforce HTML escaping.  This will probably double escape variables."""
50     if hasattr(value, '__html__'):
51         value = value.__html__()
52     return escape(unicode(value))
53
54
55 @environmentfilter
56 def do_replace(environment, s, old, new, count=None):
57     """Return a copy of the value with all occurrences of a substring
58     replaced with a new one. The first argument is the substring
59     that should be replaced, the second is the replacement string.
60     If the optional third argument ``count`` is given, only the first
61     ``count`` occurrences are replaced:
62
63     .. sourcecode:: jinja
64
65         {{ "Hello World"|replace("Hello", "Goodbye") }}
66             -> Goodbye World
67
68         {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
69             -> d'oh, d'oh, aaargh
70     """
71     if count is None:
72         count = -1
73     if not environment.autoescape:
74         return unicode(s).replace(unicode(old), unicode(new), count)
75     if hasattr(old, '__html__') or hasattr(new, '__html__') and \
76        not hasattr(s, '__html__'):
77         s = escape(s)
78     else:
79         s = soft_unicode(s)
80     return s.replace(old, new, count)
81
82
83 def do_upper(s):
84     """Convert a value to uppercase."""
85     return soft_unicode(s).upper()
86
87
88 def do_lower(s):
89     """Convert a value to lowercase."""
90     return soft_unicode(s).lower()
91
92
93 @environmentfilter
94 def do_xmlattr(_environment, d, autospace=True):
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 unless the second parameter is false.
116     """
117     rv = u' '.join(
118         u'%s="%s"' % (escape(key), escape(value))
119         for key, value in d.iteritems()
120         if value is not None and not isinstance(value, Undefined)
121     )
122     if autospace and rv:
123         rv = u' ' + rv
124     if _environment.autoescape:
125         rv = Markup(rv)
126     return rv
127
128
129 def do_capitalize(s):
130     """Capitalize a value. The first character will be uppercase, all others
131     lowercase.
132     """
133     return soft_unicode(s).capitalize()
134
135
136 def do_title(s):
137     """Return a titlecased version of the value. I.e. words will start with
138     uppercase letters, all remaining characters are lowercase.
139     """
140     return soft_unicode(s).title()
141
142
143 def do_dictsort(value, case_sensitive=False, by='key'):
144     """ Sort a dict and yield (key, value) pairs. Because python dicts are
145     unsorted you may want to use this function to order them by either
146     key or value:
147
148     .. sourcecode:: jinja
149
150         {% for item in mydict|dictsort %}
151             sort the dict by key, case insensitive
152
153         {% for item in mydict|dicsort(true) %}
154             sort the dict by key, case sensitive
155
156         {% for item in mydict|dictsort(false, 'value') %}
157             sort the dict by key, case insensitive, sorted
158             normally and ordered by value.
159     """
160     if by == 'key':
161         pos = 0
162     elif by == 'value':
163         pos = 1
164     else:
165         raise FilterArgumentError('You can only sort by either '
166                                   '"key" or "value"')
167     def sort_func(item):
168         value = item[pos]
169         if isinstance(value, basestring):
170             value = unicode(value)
171             if not case_sensitive:
172                 value = value.lower()
173         return value
174
175     return sorted(value.items(), key=sort_func)
176
177
178 def do_default(value, default_value=u'', boolean=False):
179     """If the value is undefined it will return the passed default value,
180     otherwise the value of the variable:
181
182     .. sourcecode:: jinja
183
184         {{ my_variable|default('my_variable is not defined') }}
185
186     This will output the value of ``my_variable`` if the variable was
187     defined, otherwise ``'my_variable is not defined'``. If you want
188     to use default with variables that evaluate to false you have to
189     set the second parameter to `true`:
190
191     .. sourcecode:: jinja
192
193         {{ ''|default('the string was empty', true) }}
194     """
195     if (boolean and not value) or isinstance(value, Undefined):
196         return default_value
197     return value
198
199
200 @environmentfilter
201 def do_join(environment, value, d=u''):
202     """Return a string which is the concatenation of the strings in the
203     sequence. The separator between elements is an empty string per
204     default, you can define ith with the optional parameter:
205
206     .. sourcecode:: jinja
207
208         {{ [1, 2, 3]|join('|') }}
209             -> 1|2|3
210
211         {{ [1, 2, 3]|join }}
212             -> 123
213     """
214     # no automatic escaping?  joining is a lot eaiser then
215     if not environment.autoescape:
216         return unicode(d).join(imap(unicode, value))
217
218     # if the delimiter doesn't have an html representation we check
219     # if any of the items has.  If yes we do a coercion to Markup
220     if not hasattr(d, '__html__'):
221         value = list(value)
222         do_escape = False
223         for idx, item in enumerate(value):
224             if hasattr(item, '__html__'):
225                 do_escape = True
226             else:
227                 value[idx] = unicode(item)
228         if do_escape:
229             d = escape(d)
230         else:
231             d = unicode(d)
232         return d.join(value)
233
234     # no html involved, to normal joining
235     return soft_unicode(d).join(imap(soft_unicode, value))
236
237
238 def do_center(value, width=80):
239     """Centers the value in a field of a given width."""
240     return unicode(value).center(width)
241
242
243 @environmentfilter
244 def do_first(environment, seq):
245     """Return the frist item of a sequence."""
246     try:
247         return iter(seq).next()
248     except StopIteration:
249         return environment.undefined('No first item, sequence was empty.')
250
251
252 @environmentfilter
253 def do_last(environment, seq):
254     """Return the last item of a sequence."""
255     try:
256         return iter(reversed(seq)).next()
257     except StopIteration:
258         return environment.undefined('No last item, sequence was empty.')
259
260
261 @environmentfilter
262 def do_random(environment, seq):
263     """Return a random item from the sequence."""
264     try:
265         return choice(seq)
266     except IndexError:
267         return environment.undefined('No random item, sequence was empty.')
268
269
270 def do_filesizeformat(value):
271     """Format the value like a 'human-readable' file size (i.e. 13 KB,
272     4.1 MB, 102 bytes, etc).
273     """
274     # fail silently
275     try:
276         bytes = float(value)
277     except TypeError:
278         bytes = 0
279
280     if bytes < 1024:
281         return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
282     elif bytes < 1024 * 1024:
283         return "%.1f KB" % (bytes / 1024)
284     elif bytes < 1024 * 1024 * 1024:
285         return "%.1f MB" % (bytes / (1024 * 1024))
286     return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
287
288
289 def do_pprint(value, verbose=False):
290     """Pretty print a variable. Useful for debugging.
291
292     With Jinja 1.2 onwards you can pass it a parameter.  If this parameter
293     is truthy the output will be more verbose (this requires `pretty`)
294     """
295     return pformat(value, verbose=verbose)
296
297
298 def do_urlize(value, trim_url_limit=None, nofollow=False):
299     """Converts URLs in plain text into clickable links.
300
301     If you pass the filter an additional integer it will shorten the urls
302     to that number. Also a third argument exists that makes the urls
303     "nofollow":
304
305     .. sourcecode:: jinja
306
307         {{ mytext|urlize(40, True) }}
308             links are shortened to 40 chars and defined with rel="nofollow"
309     """
310     return urlize(soft_unicode(value), trim_url_limit, nofollow)
311
312
313 def do_indent(s, width=4, indentfirst=False):
314     """Return a copy of the passed string, each line indented by
315     4 spaces. The first line is not indented. If you want to
316     change the number of spaces or indent the first line too
317     you can pass additional parameters to the filter:
318
319     .. sourcecode:: jinja
320
321         {{ mytext|indent(2, True) }}
322             indent by two spaces and indent the first line too.
323     """
324     indention = ' ' * width
325     if indentfirst:
326         return u'\n'.join(indention + line for line in s.splitlines())
327     return s.replace('\n', '\n' + indention)
328
329
330 def do_truncate(s, length=255, killwords=False, end='...'):
331     """Return a truncated copy of the string. The length is specified
332     with the first parameter which defaults to ``255``. If the second
333     parameter is ``true`` the filter will cut the text at length. Otherwise
334     it will try to save the last word. If the text was in fact
335     truncated it will append an ellipsis sign (``"..."``). If you want a
336     different ellipsis sign than ``"..."`` you can specify it using the
337     third parameter.
338
339     .. sourcecode jinja::
340
341         {{ mytext|truncate(300, false, '&raquo;') }}
342             truncate mytext to 300 chars, don't split up words, use a
343             right pointing double arrow as ellipsis sign.
344     """
345     if len(s) <= length:
346         return s
347     elif killwords:
348         return s[:length] + end
349     words = s.split(' ')
350     result = []
351     m = 0
352     for word in words:
353         m += len(word) + 1
354         if m > length:
355             break
356         result.append(word)
357     result.append(end)
358     return u' '.join(result)
359
360
361 def do_wordwrap(s, pos=79, hard=False):
362     """
363     Return a copy of the string passed to the filter wrapped after
364     ``79`` characters. You can override this default using the first
365     parameter. If you set the second parameter to `true` Jinja will
366     also split words apart (usually a bad idea because it makes
367     reading hard).
368     """
369     if len(s) < pos:
370         return s
371     if hard:
372         return u'\n'.join(s[idx:idx + pos] for idx in
373                           xrange(0, len(s), pos))
374
375     # TODO: switch to wordwrap.wrap
376     # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
377     return reduce(lambda line, word, pos=pos: u'%s%s%s' %
378                   (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
379                                 len(word.split('\n', 1)[0]) >= pos)],
380                    word), s.split(' '))
381
382
383 def do_wordcount(s):
384     """Count the words in that string."""
385     return len(s.split())
386
387
388 def do_int(value, default=0):
389     """Convert the value into an integer. If the
390     conversion doesn't work it will return ``0``. You can
391     override this default using the first parameter.
392     """
393     try:
394         return int(value)
395     except (TypeError, ValueError):
396         # this quirk is necessary so that "42.23"|int gives 42.
397         try:
398             return int(float(value))
399         except (TypeError, ValueError):
400             return default
401
402
403 def do_float(value, default=0.0):
404     """Convert the value into a floating point number. If the
405     conversion doesn't work it will return ``0.0``. You can
406     override this default using the first parameter.
407     """
408     try:
409         return float(value)
410     except (TypeError, ValueError):
411         return default
412
413
414 def do_format(value, *args, **kwargs):
415     """
416     Apply python string formatting on an object:
417
418     .. sourcecode:: jinja
419
420         {{ "%s - %s"|format("Hello?", "Foo!") }}
421             -> Hello? - Foo!
422     """
423     if kwargs:
424         kwargs.update(idx, arg in enumerate(args))
425         args = kwargs
426     return soft_unicode(value) % args
427
428
429 def do_trim(value):
430     """Strip leading and trailing whitespace."""
431     return soft_unicode(value).strip()
432
433
434 def do_striptags(value):
435     """Strip SGML/XML tags and replace adjacent whitespace by one space.
436     """
437     if hasattr(value, '__html__'):
438         value = value.__html__()
439     return u' '.join(_striptags_re.sub('', value).split())
440
441
442 def do_slice(value, slices, fill_with=None):
443     """Slice an iterator and return a list of lists containing
444     those items. Useful if you want to create a div containing
445     three div tags that represent columns:
446
447     .. sourcecode:: html+jinja
448
449         <div class="columwrapper">
450           {%- for column in items|slice(3) %}
451             <ul class="column-{{ loop.index }}">
452             {%- for item in column %}
453               <li>{{ item }}</li>
454             {%- endfor %}
455             </ul>
456           {%- endfor %}
457         </div>
458
459     If you pass it a second argument it's used to fill missing
460     values on the last iteration.
461     """
462     seq = list(value)
463     length = len(seq)
464     items_per_slice = length // slices
465     slices_with_extra = length % slices
466     offset = 0
467     for slice_number in xrange(slices):
468         start = offset + slice_number * items_per_slice
469         if slice_number < slices_with_extra:
470             offset += 1
471         end = offset + (slice_number + 1) * items_per_slice
472         tmp = seq[start:end]
473         if fill_with is not None and slice_number >= slices_with_extra:
474             tmp.append(fill_with)
475         yield tmp
476
477
478 def do_batch(value, linecount, fill_with=None):
479     """
480     A filter that batches items. It works pretty much like `slice`
481     just the other way round. It returns a list of lists with the
482     given number of items. If you provide a second parameter this
483     is used to fill missing items. See this example:
484
485     .. sourcecode:: html+jinja
486
487         <table>
488         {%- for row in items|batch(3, '&nbsp;') %}
489           <tr>
490           {%- for column in row %}
491             <tr>{{ column }}</td>
492           {%- endfor %}
493           </tr>
494         {%- endfor %}
495         </table>
496     """
497     result = []
498     tmp = []
499     for item in value:
500         if len(tmp) == linecount:
501             yield tmp
502             tmp = []
503         tmp.append(item)
504     if tmp:
505         if fill_with is not None and len(tmp) < linecount:
506             tmp += [fill_with] * (linecount - len(tmp))
507         yield tmp
508
509
510 def do_round(value, precision=0, method='common'):
511     """Round the number to a given precision. The first
512     parameter specifies the precision (default is ``0``), the
513     second the rounding method:
514
515     - ``'common'`` rounds either up or down
516     - ``'ceil'`` always rounds up
517     - ``'floor'`` always rounds down
518
519     If you don't specify a method ``'common'`` is used.
520
521     .. sourcecode:: jinja
522
523         {{ 42.55|round }}
524             -> 43
525         {{ 42.55|round(1, 'floor') }}
526             -> 42.5
527     """
528     if not method in ('common', 'ceil', 'floor'):
529         raise FilterArgumentError('method must be common, ceil or floor')
530     if precision < 0:
531         raise FilterArgumentError('precision must be a postive integer '
532                                   'or zero.')
533     if method == 'common':
534         return round(value, precision)
535     func = getattr(math, method)
536     if precision:
537         return func(value * 10 * precision) / (10 * precision)
538     else:
539         return func(value)
540
541
542 def do_sort(value, reverse=False):
543     """Sort a sequence. Per default it sorts ascending, if you pass it
544     true as first argument it will reverse the sorting.
545     """
546     return sorted(value, reverse=reverse)
547
548
549 @environmentfilter
550 def do_groupby(environment, value, attribute):
551     """Group a sequence of objects by a common attribute.
552
553     If you for example have a list of dicts or objects that represent persons
554     with `gender`, `first_name` and `last_name` attributes and you want to
555     group all users by genders you can do something like the following
556     snippet:
557
558     .. sourcecode:: html+jinja
559
560         <ul>
561         {% for group in persons|groupby('gender') %}
562             <li>{{ group.grouper }}<ul>
563             {% for person in group.list %}
564                 <li>{{ person.first_name }} {{ person.last_name }}</li>
565             {% endfor %}</ul></li>
566         {% endfor %}
567         </ul>
568
569     Additionally it's possible to use tuple unpacking for the grouper and
570     list:
571
572     .. sourcecode:: html+jinja
573
574         <ul>
575         {% for grouper, list in persons|groupby('gender') %}
576             ...
577         {% endfor %}
578         </ul>
579
580     As you can see the item we're grouping by is stored in the `grouper`
581     attribute and the `list` contains all the objects that have this grouper
582     in common.
583     """
584     expr = lambda x: environment.subscribe(x, attribute)
585     return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
586
587
588 class _GroupTuple(tuple):
589     __slots__ = ()
590     grouper = property(itemgetter(0))
591     list = property(itemgetter(1))
592
593     def __new__(cls, (key, value)):
594         return tuple.__new__(cls, (key, list(value)))
595
596
597 def do_list(value):
598     """Convert the value into a list.  If it was a string the returned list
599     will be a list of characters.
600     """
601     return list(value)
602
603
604 def do_mark_safe(value):
605     """Mark the value as safe which means that in an environment with automatic
606     escaping enabled this variable will not be escaped.
607     """
608     return Markup(value)
609
610
611 def do_reverse(value):
612     """Reverse the object or return an iterator the iterates over it the other
613     way round.
614     """
615     if isinstance(value, basestring):
616         return value[::-1]
617     try:
618         return reversed(value)
619     except TypeError:
620         try:
621             rv = list(value)
622             rv.reverse()
623             return rv
624         except TypeError:
625             raise FilterArgumentError('argument must be iterable')
626
627
628 FILTERS = {
629     'replace':              do_replace,
630     'upper':                do_upper,
631     'lower':                do_lower,
632     'escape':               escape,
633     'e':                    escape,
634     'forceescape':          do_forceescape,
635     'capitalize':           do_capitalize,
636     'title':                do_title,
637     'default':              do_default,
638     'd':                    do_default,
639     'join':                 do_join,
640     'count':                len,
641     'dictsort':             do_dictsort,
642     'length':               len,
643     'reverse':              do_reverse,
644     'center':               do_center,
645     'indent':               do_indent,
646     'title':                do_title,
647     'capitalize':           do_capitalize,
648     'first':                do_first,
649     'last':                 do_last,
650     'random':               do_random,
651     'filesizeformat':       do_filesizeformat,
652     'pprint':               do_pprint,
653     'truncate':             do_truncate,
654     'wordwrap':             do_wordwrap,
655     'wordcount':            do_wordcount,
656     'int':                  do_int,
657     'float':                do_float,
658     'string':               soft_unicode,
659     'list':                 do_list,
660     'urlize':               do_urlize,
661     'format':               do_format,
662     'trim':                 do_trim,
663     'striptags':            do_striptags,
664     'slice':                do_slice,
665     'batch':                do_batch,
666     'sum':                  sum,
667     'abs':                  abs,
668     'round':                do_round,
669     'sort':                 do_sort,
670     'groupby':              do_groupby,
671     'safe':                 do_mark_safe,
672     'xmlattr':              do_xmlattr
673 }