blob: e8cf5aafd9efb411a655d3e80e30b7dc82794058 [file] [log] [blame]
Armin Ronacher92f572f2007-02-26 22:17:32 +01001# -*- coding: utf-8 -*-
2"""
Armin Ronacher26c0f512008-04-17 11:16:01 +02003 jinja2.filters
4 ~~~~~~~~~~~~~~
Armin Ronacher92f572f2007-02-26 22:17:32 +01005
6 Bundled jinja filters.
7
Christoph Hack2751d942008-04-09 12:14:55 +02008 :copyright: 2008 by Armin Ronacher, Christoph Hack.
Armin Ronacher92f572f2007-02-26 22:17:32 +01009 :license: BSD, see LICENSE for more details.
10"""
Georg Brandlaf31e4d2007-04-15 00:47:37 +020011import re
Armin Ronacher9a027f42008-04-17 11:13:40 +020012import math
Armin Ronacherfed86c12007-02-27 10:31:14 +010013from random import choice
Armin Ronacher1dcdac52007-12-09 22:53:46 +010014try:
15 from operator import itemgetter
16except ImportError:
17 itemgetter = lambda a: lambda b: b[a]
Armin Ronacherfed86c12007-02-27 10:31:14 +010018from urllib import urlencode, quote
Armin Ronacherbe4ae242008-04-18 09:49:08 +020019from itertools import imap, groupby
Armin Ronacher9a027f42008-04-17 11:13:40 +020020from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
Christoph Hack80909862008-04-14 01:35:10 +020021from jinja2.runtime import Undefined
Christoph Hacke9e43bb2008-04-13 23:35:48 +020022
Armin Ronacherfed86c12007-02-27 10:31:14 +010023
Armin Ronachereaf493e2007-10-01 22:31:16 +020024_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
Armin Ronacher9bcd4112007-05-29 14:17:24 +020025
26
Christoph Hack2751d942008-04-09 12:14:55 +020027def contextfilter(f):
Armin Ronacher8edbe492008-04-10 20:43:43 +020028 """Decorator for marking context dependent filters. The current context
Christoph Hack2751d942008-04-09 12:14:55 +020029 argument will be passed as first argument.
Armin Ronacher92f572f2007-02-26 22:17:32 +010030 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020031 if getattr(f, 'environmentfilter', False):
32 raise TypeError('filter already marked as environment filter')
Christoph Hack2751d942008-04-09 12:14:55 +020033 f.contextfilter = True
34 return f
Armin Ronacher9bcd4112007-05-29 14:17:24 +020035
36
Armin Ronacher9a027f42008-04-17 11:13:40 +020037def environmentfilter(f):
38 """Decorator for marking evironment dependent filters. The environment
39 used for the template is passed to the filter as first argument.
Armin Ronacher92f572f2007-02-26 22:17:32 +010040 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020041 if getattr(f, 'contextfilter', False):
42 raise TypeError('filter already marked as context filter')
43 f.environmentfilter = True
44 return f
45
46
Armin Ronacherf59bac22008-04-20 13:11:43 +020047def do_forceescape(value):
48 """Enforce HTML escaping. This will probably double escape variables."""
49 if hasattr(value, '__html__'):
50 value = value.__html__()
51 return escape(unicode(value))
52
53
Armin Ronacherd1342312008-04-28 12:20:12 +020054@environmentfilter
55def do_replace(environment, s, old, new, count=None):
Armin Ronacher9a027f42008-04-17 11:13:40 +020056 """Return a copy of the value with all occurrences of a substring
Armin Ronacher37a88512007-03-02 20:42:18 +010057 replaced with a new one. The first argument is the substring
58 that should be replaced, the second is the replacement string.
59 If the optional third argument ``count`` is given, only the first
60 ``count`` occurrences are replaced:
Armin Ronacher92f572f2007-02-26 22:17:32 +010061
Armin Ronacher37a88512007-03-02 20:42:18 +010062 .. sourcecode:: jinja
63
64 {{ "Hello World"|replace("Hello", "Goodbye") }}
65 -> Goodbye World
66
67 {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
68 -> d'oh, d'oh, aaargh
Armin Ronacher92f572f2007-02-26 22:17:32 +010069 """
Armin Ronacher2da479f2007-04-02 10:50:18 +020070 if count is None:
Armin Ronacher9a027f42008-04-17 11:13:40 +020071 count = -1
Armin Ronacherd1342312008-04-28 12:20:12 +020072 if not environment.autoescape:
73 return unicode(s).replace(unicode(old), unicode(new), count)
Armin Ronacher9a027f42008-04-17 11:13:40 +020074 if hasattr(old, '__html__') or hasattr(new, '__html__') and \
75 not hasattr(s, '__html__'):
76 s = escape(s)
77 else:
78 s = soft_unicode(s)
Armin Ronacher92f572f2007-02-26 22:17:32 +010079 return s.replace(old, new, count)
Armin Ronacher92f572f2007-02-26 22:17:32 +010080
81
82def do_upper(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020083 """Convert a value to uppercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020084 return soft_unicode(s).upper()
Armin Ronacher92f572f2007-02-26 22:17:32 +010085
86
Christoph Hack2751d942008-04-09 12:14:55 +020087def do_lower(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020088 """Convert a value to lowercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020089 return soft_unicode(s).lower()
Armin Ronacher92f572f2007-02-26 22:17:32 +010090
91
Armin Ronacherd1342312008-04-28 12:20:12 +020092@environmentfilter
93def do_xmlattr(_environment, *args, **kwargs):
Armin Ronacher9a027f42008-04-17 11:13:40 +020094 """Create an SGML/XML attribute string based on the items in a dict.
Armin Ronacher450756b2007-04-15 15:13:59 +020095 All values that are neither `none` nor `undefined` are automatically
96 escaped:
97
Armin Ronacher8ca55df2007-04-15 15:16:08 +020098 .. sourcecode:: html+jinja
Armin Ronacher450756b2007-04-15 15:13:59 +020099
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +0200100 <ul{{ {'class': 'my_list', 'missing': None,
Armin Ronacherd459e272007-04-15 15:31:05 +0200101 'id': 'list-%d'|format(variable)}|xmlattr }}>
Armin Ronacher450756b2007-04-15 15:13:59 +0200102 ...
103 </ul>
104
105 Results in something like this:
106
Armin Ronacher8ca55df2007-04-15 15:16:08 +0200107 .. sourcecode:: html
108
Armin Ronacher450756b2007-04-15 15:13:59 +0200109 <ul class="my_list" id="list-42">
110 ...
111 </ul>
112
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200113 As you can see it automatically prepends a space in front of the item
Armin Ronacherd1342312008-04-28 12:20:12 +0200114 if the filter returned something.
Armin Ronacher450756b2007-04-15 15:13:59 +0200115 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200116 rv = u' '.join(
117 u'%s="%s"' % (escape(key), escape(value))
Armin Ronacherd1342312008-04-28 12:20:12 +0200118 for key, value in dict(*args, **kwargs).iteritems()
Armin Ronacher9a027f42008-04-17 11:13:40 +0200119 if value is not None and not isinstance(value, Undefined)
120 )
Armin Ronacherd1342312008-04-28 12:20:12 +0200121 if rv:
122 rv = u' ' + rv
123 if _environment.autoescape:
124 rv = Markup(rv)
125 return rv
Armin Ronacher450756b2007-04-15 15:13:59 +0200126
127
Armin Ronacher92f572f2007-02-26 22:17:32 +0100128def do_capitalize(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200129 """Capitalize a value. The first character will be uppercase, all others
Armin Ronacher37a88512007-03-02 20:42:18 +0100130 lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100131 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200132 return soft_unicode(s).capitalize()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100133
134
135def do_title(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200136 """Return a titlecased version of the value. I.e. words will start with
Armin Ronacher37a88512007-03-02 20:42:18 +0100137 uppercase letters, all remaining characters are lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100138 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200139 return soft_unicode(s).title()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100140
141
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200142def do_dictsort(value, case_sensitive=False, by='key'):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200143 """ Sort a dict and yield (key, value) pairs. Because python dicts are
Armin Ronacher2b765132007-03-13 16:48:10 +0100144 unsorted you may want to use this function to order them by either
145 key or value:
146
147 .. sourcecode:: jinja
148
149 {% for item in mydict|dictsort %}
150 sort the dict by key, case insensitive
151
152 {% for item in mydict|dicsort(true) %}
153 sort the dict by key, case sensitive
154
155 {% for item in mydict|dictsort(false, 'value') %}
156 sort the dict by key, case insensitive, sorted
157 normally and ordered by value.
158 """
159 if by == 'key':
160 pos = 0
161 elif by == 'value':
162 pos = 1
163 else:
164 raise FilterArgumentError('You can only sort by either '
165 '"key" or "value"')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200166 def sort_func(item):
167 value = item[pos]
Armin Ronacher2b765132007-03-13 16:48:10 +0100168 if isinstance(value, basestring):
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200169 value = unicode(value)
Armin Ronacher2b765132007-03-13 16:48:10 +0100170 if not case_sensitive:
171 value = value.lower()
172 return value
173
Armin Ronacher9a027f42008-04-17 11:13:40 +0200174 return sorted(value.items(), key=sort_func)
Armin Ronacher2b765132007-03-13 16:48:10 +0100175
176
Christoph Hack2751d942008-04-09 12:14:55 +0200177def do_default(value, default_value=u'', boolean=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200178 """If the value is undefined it will return the passed default value,
Armin Ronacher37a88512007-03-02 20:42:18 +0100179 otherwise the value of the variable:
Armin Ronacher92f572f2007-02-26 22:17:32 +0100180
Armin Ronacher37a88512007-03-02 20:42:18 +0100181 .. sourcecode:: jinja
182
183 {{ my_variable|default('my_variable is not defined') }}
184
185 This will output the value of ``my_variable`` if the variable was
186 defined, otherwise ``'my_variable is not defined'``. If you want
187 to use default with variables that evaluate to false you have to
188 set the second parameter to `true`:
189
190 .. sourcecode:: jinja
191
192 {{ ''|default('the string was empty', true) }}
Armin Ronacher92f572f2007-02-26 22:17:32 +0100193 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200194 if (boolean and not value) or isinstance(value, Undefined):
Christoph Hack2751d942008-04-09 12:14:55 +0200195 return default_value
196 return value
Armin Ronacher92f572f2007-02-26 22:17:32 +0100197
198
Armin Ronacherd1342312008-04-28 12:20:12 +0200199@environmentfilter
200def do_join(environment, value, d=u''):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200201 """Return a string which is the concatenation of the strings in the
Armin Ronacher37a88512007-03-02 20:42:18 +0100202 sequence. The separator between elements is an empty string per
203 default, you can define ith with the optional parameter:
204
205 .. sourcecode:: jinja
206
207 {{ [1, 2, 3]|join('|') }}
208 -> 1|2|3
209
210 {{ [1, 2, 3]|join }}
211 -> 123
Armin Ronacher92f572f2007-02-26 22:17:32 +0100212 """
Armin Ronacherd1342312008-04-28 12:20:12 +0200213 # no automatic escaping? joining is a lot eaiser then
214 if not environment.autoescape:
215 return unicode(d).join(imap(unicode, value))
216
Armin Ronacher9a027f42008-04-17 11:13:40 +0200217 # if the delimiter doesn't have an html representation we check
218 # if any of the items has. If yes we do a coercion to Markup
Priit Laes4149a0e2008-04-17 19:04:44 +0200219 if not hasattr(d, '__html__'):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200220 value = list(value)
221 do_escape = False
222 for idx, item in enumerate(value):
223 if hasattr(item, '__html__'):
224 do_escape = True
225 else:
226 value[idx] = unicode(item)
227 if do_escape:
228 d = escape(d)
229 else:
230 d = unicode(d)
231 return d.join(value)
232
233 # no html involved, to normal joining
234 return soft_unicode(d).join(imap(soft_unicode, value))
Armin Ronacher92f572f2007-02-26 22:17:32 +0100235
236
Armin Ronacherfed86c12007-02-27 10:31:14 +0100237def do_center(value, width=80):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200238 """Centers the value in a field of a given width."""
Christoph Hack2751d942008-04-09 12:14:55 +0200239 return unicode(value).center(width)
Armin Ronacherfed86c12007-02-27 10:31:14 +0100240
241
Armin Ronacher9a027f42008-04-17 11:13:40 +0200242@environmentfilter
243def do_first(environment, seq):
244 """Return the frist item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200245 try:
246 return iter(seq).next()
247 except StopIteration:
Armin Ronacher9a822052008-04-17 18:44:07 +0200248 return environment.undefined('No first item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100249
250
Armin Ronacher9a027f42008-04-17 11:13:40 +0200251@environmentfilter
252def do_last(environment, seq):
253 """Return the last item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200254 try:
255 return iter(reversed(seq)).next()
256 except StopIteration:
Armin Ronacher9a822052008-04-17 18:44:07 +0200257 return environment.undefined('No last item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100258
259
Armin Ronacher9a027f42008-04-17 11:13:40 +0200260@environmentfilter
261def do_random(environment, seq):
262 """Return a random item from the sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200263 try:
264 return choice(seq)
265 except IndexError:
Armin Ronacher9a822052008-04-17 18:44:07 +0200266 return environment.undefined('No random item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100267
268
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200269def do_filesizeformat(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200270 """Format the value like a 'human-readable' file size (i.e. 13 KB,
Armin Ronacher5f514882008-04-16 15:29:52 +0200271 4.1 MB, 102 bytes, etc).
Armin Ronacher2b765132007-03-13 16:48:10 +0100272 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200273 # fail silently
274 try:
275 bytes = float(value)
276 except TypeError:
277 bytes = 0
Armin Ronacher2b765132007-03-13 16:48:10 +0100278
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200279 if bytes < 1024:
280 return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
281 elif bytes < 1024 * 1024:
282 return "%.1f KB" % (bytes / 1024)
283 elif bytes < 1024 * 1024 * 1024:
284 return "%.1f MB" % (bytes / (1024 * 1024))
285 return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
Armin Ronacher2b765132007-03-13 16:48:10 +0100286
287
Christoph Hack2751d942008-04-09 12:14:55 +0200288def do_pprint(value, verbose=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200289 """Pretty print a variable. Useful for debugging.
Armin Ronacherf2ce1262007-10-21 21:51:51 +0200290
291 With Jinja 1.2 onwards you can pass it a parameter. If this parameter
Armin Ronacher5f3f1362007-10-21 22:15:04 +0200292 is truthy the output will be more verbose (this requires `pretty`)
Armin Ronacher2b765132007-03-13 16:48:10 +0100293 """
Christoph Hack2751d942008-04-09 12:14:55 +0200294 return pformat(value, verbose=verbose)
Armin Ronacher2b765132007-03-13 16:48:10 +0100295
296
297def do_urlize(value, trim_url_limit=None, nofollow=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200298 """Converts URLs in plain text into clickable links.
Armin Ronacher2b765132007-03-13 16:48:10 +0100299
300 If you pass the filter an additional integer it will shorten the urls
301 to that number. Also a third argument exists that makes the urls
302 "nofollow":
303
304 .. sourcecode:: jinja
305
306 {{ mytext|urlize(40, True) }}
307 links are shortened to 40 chars and defined with rel="nofollow"
308 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200309 return urlize(soft_unicode(value), trim_url_limit, nofollow)
Armin Ronacher2b765132007-03-13 16:48:10 +0100310
311
312def do_indent(s, width=4, indentfirst=False):
313 """
314 {{ s|indent[ width[ indentfirst[ usetab]]] }}
315
316 Return a copy of the passed string, each line indented by
317 4 spaces. The first line is not indented. If you want to
318 change the number of spaces or indent the first line too
319 you can pass additional parameters to the filter:
320
321 .. sourcecode:: jinja
322
323 {{ mytext|indent(2, True) }}
324 indent by two spaces and indent the first line too.
325 """
326 indention = ' ' * width
327 if indentfirst:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200328 return u'\n'.join(indention + line for line in s.splitlines())
Armin Ronacher2b765132007-03-13 16:48:10 +0100329 return s.replace('\n', '\n' + indention)
Armin Ronacher2b765132007-03-13 16:48:10 +0100330
331
332def do_truncate(s, length=255, killwords=False, end='...'):
333 """
Armin Ronacher2b765132007-03-13 16:48:10 +0100334 Return a truncated copy of the string. The length is specified
335 with the first parameter which defaults to ``255``. If the second
336 parameter is ``true`` the filter will cut the text at length. Otherwise
337 it will try to save the last word. If the text was in fact
338 truncated it will append an ellipsis sign (``"..."``). If you want a
339 different ellipsis sign than ``"..."`` you can specify it using the
340 third parameter.
341
342 .. sourcecode jinja::
343
344 {{ mytext|truncate(300, false, '&raquo;') }}
345 truncate mytext to 300 chars, don't split up words, use a
346 right pointing double arrow as ellipsis sign.
347 """
348 if len(s) <= length:
349 return s
350 elif killwords:
351 return s[:length] + end
352 words = s.split(' ')
353 result = []
354 m = 0
355 for word in words:
356 m += len(word) + 1
357 if m > length:
358 break
359 result.append(word)
360 result.append(end)
361 return u' '.join(result)
Armin Ronacher2b765132007-03-13 16:48:10 +0100362
363
364def do_wordwrap(s, pos=79, hard=False):
365 """
366 Return a copy of the string passed to the filter wrapped after
367 ``79`` characters. You can override this default using the first
368 parameter. If you set the second parameter to `true` Jinja will
369 also split words apart (usually a bad idea because it makes
370 reading hard).
371 """
372 if len(s) < pos:
373 return s
374 if hard:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200375 return u'\n'.join(s[idx:idx + pos] for idx in
376 xrange(0, len(s), pos))
377
378 # TODO: switch to wordwrap.wrap
Armin Ronacher2b765132007-03-13 16:48:10 +0100379 # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
380 return reduce(lambda line, word, pos=pos: u'%s%s%s' %
381 (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
382 len(word.split('\n', 1)[0]) >= pos)],
383 word), s.split(' '))
Armin Ronacher2b765132007-03-13 16:48:10 +0100384
Armin Ronacher963f97d2008-04-25 11:44:59 +0200385
Armin Ronacher2b765132007-03-13 16:48:10 +0100386def do_wordcount(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200387 """Count the words in that string."""
Priit Laes284f2002008-04-17 21:22:00 +0200388 return len(s.split())
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200389
Armin Ronacher963f97d2008-04-25 11:44:59 +0200390
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200391def do_int(value, default=0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200392 """Convert the value into an integer. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100393 conversion doesn't work it will return ``0``. You can
394 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100395 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200396 try:
397 return int(value)
398 except (TypeError, ValueError):
Armin Ronacherf59bac22008-04-20 13:11:43 +0200399 # this quirk is necessary so that "42.23"|int gives 42.
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200400 try:
401 return int(float(value))
402 except (TypeError, ValueError):
403 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100404
405
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200406def do_float(value, default=0.0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200407 """Convert the value into a floating point number. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100408 conversion doesn't work it will return ``0.0``. You can
409 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100410 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200411 try:
412 return float(value)
413 except (TypeError, ValueError):
414 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100415
416
Armin Ronacher5f514882008-04-16 15:29:52 +0200417def do_format(value, *args, **kwargs):
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100418 """
419 Apply python string formatting on an object:
420
421 .. sourcecode:: jinja
422
423 {{ "%s - %s"|format("Hello?", "Foo!") }}
424 -> Hello? - Foo!
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100425 """
Armin Ronacher5f514882008-04-16 15:29:52 +0200426 if kwargs:
427 kwargs.update(idx, arg in enumerate(args))
428 args = kwargs
Armin Ronacher9a027f42008-04-17 11:13:40 +0200429 return soft_unicode(value) % args
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100430
431
Armin Ronacher566295e2007-03-19 13:19:34 +0100432def do_trim(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200433 """Strip leading and trailing whitespace."""
434 return soft_unicode(value).strip()
Armin Ronacher566295e2007-03-19 13:19:34 +0100435
436
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200437def do_striptags(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200438 """Strip SGML/XML tags and replace adjacent whitespace by one space.
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200439 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200440 if hasattr(value, '__html__'):
441 value = value.__html__()
442 return u' '.join(_striptags_re.sub('', value).split())
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200443
444
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200445def do_slice(value, slices, fill_with=None):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200446 """Slice an iterator and return a list of lists containing
Armin Ronacherd071f952007-04-13 22:32:11 +0200447 those items. Useful if you want to create a div containing
448 three div tags that represent columns:
449
Armin Ronacher89376072007-04-13 22:34:35 +0200450 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200451
452 <div class="columwrapper">
453 {%- for column in items|slice(3) %}
454 <ul class="column-{{ loop.index }}">
455 {%- for item in column %}
456 <li>{{ item }}</li>
457 {%- endfor %}
458 </ul>
459 {%- endfor %}
460 </div>
461
Armin Ronachereec31382007-04-14 14:50:45 +0200462 If you pass it a second argument it's used to fill missing
463 values on the last iteration.
Armin Ronacherd071f952007-04-13 22:32:11 +0200464 """
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200465 seq = list(value)
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200466 length = len(seq)
467 items_per_slice = length // slices
468 slices_with_extra = length % slices
469 offset = 0
470 for slice_number in xrange(slices):
471 start = offset + slice_number * items_per_slice
472 if slice_number < slices_with_extra:
473 offset += 1
474 end = offset + (slice_number + 1) * items_per_slice
475 tmp = seq[start:end]
476 if fill_with is not None and slice_number >= slices_with_extra:
477 tmp.append(fill_with)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200478 yield tmp
Armin Ronacherd071f952007-04-13 22:32:11 +0200479
480
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200481def do_batch(value, linecount, fill_with=None):
Armin Ronacherd071f952007-04-13 22:32:11 +0200482 """
483 A filter that batches items. It works pretty much like `slice`
484 just the other way round. It returns a list of lists with the
485 given number of items. If you provide a second parameter this
486 is used to fill missing items. See this example:
487
Armin Ronacher89376072007-04-13 22:34:35 +0200488 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200489
490 <table>
491 {%- for row in items|batch(3, '&nbsp;') %}
492 <tr>
493 {%- for column in row %}
494 <tr>{{ column }}</td>
495 {%- endfor %}
496 </tr>
497 {%- endfor %}
498 </table>
Armin Ronacherd071f952007-04-13 22:32:11 +0200499 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200500 result = []
501 tmp = []
502 for item in value:
503 if len(tmp) == linecount:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200504 yield tmp
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200505 tmp = []
506 tmp.append(item)
507 if tmp:
508 if fill_with is not None and len(tmp) < linecount:
509 tmp += [fill_with] * (linecount - len(tmp))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200510 yield tmp
Armin Ronacherd071f952007-04-13 22:32:11 +0200511
512
Armin Ronacher9a027f42008-04-17 11:13:40 +0200513def do_round(value, precision=0, method='common'):
514 """Round the number to a given precision. The first
Armin Ronachereec31382007-04-14 14:50:45 +0200515 parameter specifies the precision (default is ``0``), the
516 second the rounding method:
517
518 - ``'common'`` rounds either up or down
519 - ``'ceil'`` always rounds up
520 - ``'floor'`` always rounds down
521
522 If you don't specify a method ``'common'`` is used.
523
524 .. sourcecode:: jinja
525
526 {{ 42.55|round }}
527 -> 43
528 {{ 42.55|round(1, 'floor') }}
529 -> 42.5
530 """
531 if not method in ('common', 'ceil', 'floor'):
532 raise FilterArgumentError('method must be common, ceil or floor')
533 if precision < 0:
534 raise FilterArgumentError('precision must be a postive integer '
535 'or zero.')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200536 if method == 'common':
537 return round(value, precision)
538 func = getattr(math, method)
539 if precision:
540 return func(value * 10 * precision) / (10 * precision)
541 else:
542 return func(value)
Armin Ronachereec31382007-04-14 14:50:45 +0200543
544
Armin Ronacher9a027f42008-04-17 11:13:40 +0200545def do_sort(value, reverse=False):
546 """Sort a sequence. Per default it sorts ascending, if you pass it
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200547 `True` as first argument it will reverse the sorting.
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200548 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200549 return sorted(value, reverse=reverse)
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200550
551
Armin Ronacher9a027f42008-04-17 11:13:40 +0200552@environmentfilter
553def do_groupby(environment, value, attribute):
554 """Group a sequence of objects by a common attribute.
Armin Ronachere39a5d22007-06-23 21:11:53 +0200555
556 If you for example have a list of dicts or objects that represent persons
557 with `gender`, `first_name` and `last_name` attributes and you want to
558 group all users by genders you can do something like the following
559 snippet:
560
561 .. sourcecode:: html+jinja
562
563 <ul>
564 {% for group in persons|groupby('gender') %}
565 <li>{{ group.grouper }}<ul>
566 {% for person in group.list %}
567 <li>{{ person.first_name }} {{ person.last_name }}</li>
568 {% endfor %}</ul></li>
569 {% endfor %}
570 </ul>
571
Armin Ronacher963f97d2008-04-25 11:44:59 +0200572 Additionally it's possible to use tuple unpacking for the grouper and
573 list:
574
575 .. sourcecode:: html+jinja
576
577 <ul>
578 {% for grouper, list in persons|groupby('gender') %}
579 ...
580 {% endfor %}
581 </ul>
582
Armin Ronachere39a5d22007-06-23 21:11:53 +0200583 As you can see the item we're grouping by is stored in the `grouper`
584 attribute and the `list` contains all the objects that have this grouper
585 in common.
586 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200587 expr = lambda x: environment.subscribe(x, attribute)
Armin Ronacherc9705c22008-04-27 21:28:03 +0200588 return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
Armin Ronacher963f97d2008-04-25 11:44:59 +0200589
590
591class _GroupTuple(tuple):
592 __slots__ = ()
593 grouper = property(itemgetter(0))
594 list = property(itemgetter(1))
Armin Ronachere39a5d22007-06-23 21:11:53 +0200595
Armin Ronacherc9705c22008-04-27 21:28:03 +0200596 def __new__(cls, (key, value)):
597 return tuple.__new__(cls, (key, list(value)))
598
Armin Ronachere39a5d22007-06-23 21:11:53 +0200599
Armin Ronacher92f572f2007-02-26 22:17:32 +0100600FILTERS = {
601 'replace': do_replace,
602 'upper': do_upper,
603 'lower': do_lower,
Armin Ronacher814f6c22008-04-17 15:52:23 +0200604 'escape': escape,
605 'e': escape,
Armin Ronacherf59bac22008-04-20 13:11:43 +0200606 'forceescape': do_forceescape,
Armin Ronacher92f572f2007-02-26 22:17:32 +0100607 'capitalize': do_capitalize,
608 'title': do_title,
609 'default': do_default,
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +0200610 'd': do_default,
Armin Ronacher92f572f2007-02-26 22:17:32 +0100611 'join': do_join,
Armin Ronacher5f514882008-04-16 15:29:52 +0200612 'count': len,
Armin Ronacher2b765132007-03-13 16:48:10 +0100613 'dictsort': do_dictsort,
Armin Ronacher5f514882008-04-16 15:29:52 +0200614 'length': len,
615 'reverse': reversed,
Armin Ronacherfed86c12007-02-27 10:31:14 +0100616 'center': do_center,
Priit Laes8464ab12008-04-17 21:21:04 +0200617 'indent': do_indent,
Armin Ronacherfed86c12007-02-27 10:31:14 +0100618 'title': do_title,
619 'capitalize': do_capitalize,
620 'first': do_first,
621 'last': do_last,
622 'random': do_random,
Armin Ronacher2b765132007-03-13 16:48:10 +0100623 'filesizeformat': do_filesizeformat,
624 'pprint': do_pprint,
Armin Ronacher2b765132007-03-13 16:48:10 +0100625 'truncate': do_truncate,
626 'wordwrap': do_wordwrap,
627 'wordcount': do_wordcount,
Armin Ronacher2b765132007-03-13 16:48:10 +0100628 'int': do_int,
629 'float': do_float,
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200630 'string': soft_unicode,
631 'list': list,
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100632 'urlize': do_urlize,
633 'format': do_format,
Armin Ronacherd071f952007-04-13 22:32:11 +0200634 'trim': do_trim,
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200635 'striptags': do_striptags,
Armin Ronacherd071f952007-04-13 22:32:11 +0200636 'slice': do_slice,
Armin Ronachereec31382007-04-14 14:50:45 +0200637 'batch': do_batch,
Armin Ronacher5f514882008-04-16 15:29:52 +0200638 'sum': sum,
639 'abs': abs,
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200640 'round': do_round,
Armin Ronachere39a5d22007-06-23 21:11:53 +0200641 'sort': do_sort,
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200642 'groupby': do_groupby,
Armin Ronacher0611e492008-04-25 23:44:14 +0200643 'safe': Markup,
644 'xmlattr': do_xmlattr
Armin Ronacher92f572f2007-02-26 22:17:32 +0100645}