blob: 137f5368748bee22f2accff7c8f1f712d5f5330b [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 Ronacher9a027f42008-04-17 11:13:40 +020019from itertools import imap
20from 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
24
Armin Ronachereaf493e2007-10-01 22:31:16 +020025_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
Armin Ronacher9bcd4112007-05-29 14:17:24 +020026
27
Christoph Hack2751d942008-04-09 12:14:55 +020028def contextfilter(f):
Armin Ronacher8edbe492008-04-10 20:43:43 +020029 """Decorator for marking context dependent filters. The current context
Christoph Hack2751d942008-04-09 12:14:55 +020030 argument will be passed as first argument.
Armin Ronacher92f572f2007-02-26 22:17:32 +010031 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020032 if getattr(f, 'environmentfilter', False):
33 raise TypeError('filter already marked as environment filter')
Christoph Hack2751d942008-04-09 12:14:55 +020034 f.contextfilter = True
35 return f
Armin Ronacher9bcd4112007-05-29 14:17:24 +020036
37
Armin Ronacher9a027f42008-04-17 11:13:40 +020038def environmentfilter(f):
39 """Decorator for marking evironment dependent filters. The environment
40 used for the template is passed to the filter as first argument.
Armin Ronacher92f572f2007-02-26 22:17:32 +010041 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020042 if getattr(f, 'contextfilter', False):
43 raise TypeError('filter already marked as context filter')
44 f.environmentfilter = True
45 return f
46
47
48def do_replace(s, old, new, count=None):
49 """Return a copy of the value with all occurrences of a substring
Armin Ronacher37a88512007-03-02 20:42:18 +010050 replaced with a new one. The first argument is the substring
51 that should be replaced, the second is the replacement string.
52 If the optional third argument ``count`` is given, only the first
53 ``count`` occurrences are replaced:
Armin Ronacher92f572f2007-02-26 22:17:32 +010054
Armin Ronacher37a88512007-03-02 20:42:18 +010055 .. sourcecode:: jinja
56
57 {{ "Hello World"|replace("Hello", "Goodbye") }}
58 -> Goodbye World
59
60 {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
61 -> d'oh, d'oh, aaargh
Armin Ronacher92f572f2007-02-26 22:17:32 +010062 """
Armin Ronacher2da479f2007-04-02 10:50:18 +020063 if count is None:
Armin Ronacher9a027f42008-04-17 11:13:40 +020064 count = -1
65 if hasattr(old, '__html__') or hasattr(new, '__html__') and \
66 not hasattr(s, '__html__'):
67 s = escape(s)
68 else:
69 s = soft_unicode(s)
Armin Ronacher92f572f2007-02-26 22:17:32 +010070 return s.replace(old, new, count)
Armin Ronacher92f572f2007-02-26 22:17:32 +010071
72
73def do_upper(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020074 """Convert a value to uppercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020075 return soft_unicode(s).upper()
Armin Ronacher92f572f2007-02-26 22:17:32 +010076
77
Christoph Hack2751d942008-04-09 12:14:55 +020078def do_lower(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020079 """Convert a value to lowercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020080 return soft_unicode(s).lower()
Armin Ronacher92f572f2007-02-26 22:17:32 +010081
82
Armin Ronacher9a027f42008-04-17 11:13:40 +020083def do_escape(s):
84 """XML escape ``&``, ``<``, ``>``, and ``"`` in a string of data.
Armin Ronacher92f572f2007-02-26 22:17:32 +010085
Armin Ronacher37a88512007-03-02 20:42:18 +010086 This method will have no effect it the value is already escaped.
Armin Ronacher92f572f2007-02-26 22:17:32 +010087 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020088 return escape(s)
Armin Ronacher92f572f2007-02-26 22:17:32 +010089
90
Christoph Hack2751d942008-04-09 12:14:55 +020091def do_xmlattr(d, autospace=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +020092 """Create an SGML/XML attribute string based on the items in a dict.
Armin Ronacher450756b2007-04-15 15:13:59 +020093 All values that are neither `none` nor `undefined` are automatically
94 escaped:
95
Armin Ronacher8ca55df2007-04-15 15:16:08 +020096 .. sourcecode:: html+jinja
Armin Ronacher450756b2007-04-15 15:13:59 +020097
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +020098 <ul{{ {'class': 'my_list', 'missing': None,
Armin Ronacherd459e272007-04-15 15:31:05 +020099 'id': 'list-%d'|format(variable)}|xmlattr }}>
Armin Ronacher450756b2007-04-15 15:13:59 +0200100 ...
101 </ul>
102
103 Results in something like this:
104
Armin Ronacher8ca55df2007-04-15 15:16:08 +0200105 .. sourcecode:: html
106
Armin Ronacher450756b2007-04-15 15:13:59 +0200107 <ul class="my_list" id="list-42">
108 ...
109 </ul>
110
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200111 As you can see it automatically prepends a space in front of the item
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +0200112 if the filter returned something. You can disable this by passing
113 `false` as only argument to the filter.
Armin Ronacher450756b2007-04-15 15:13:59 +0200114 """
Christoph Hack2751d942008-04-09 12:14:55 +0200115 if not hasattr(d, 'iteritems'):
116 raise TypeError('a dict is required')
117 result = []
118 for key, value in d.iteritems():
Armin Ronacher5f514882008-04-16 15:29:52 +0200119 if value is not None and not isinstance(value, Undefined):
Christoph Hack2751d942008-04-09 12:14:55 +0200120 result.append(u'%s="%s"' % (
121 escape(env.to_unicode(key)),
122 escape(env.to_unicode(value), True)
123 ))
Armin Ronacher9a027f42008-04-17 11:13:40 +0200124 rv = u' '.join(
125 u'%s="%s"' % (escape(key), escape(value))
126 for key, value in d.iteritems()
127 if value is not None and not isinstance(value, Undefined)
128 )
Christoph Hack2751d942008-04-09 12:14:55 +0200129 if autospace:
130 rv = ' ' + rv
Armin Ronacher9a027f42008-04-17 11:13:40 +0200131 return Markup(rv)
Armin Ronacher450756b2007-04-15 15:13:59 +0200132
133
Armin Ronacher92f572f2007-02-26 22:17:32 +0100134def do_capitalize(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200135 """Capitalize a value. The first character will be uppercase, all others
Armin Ronacher37a88512007-03-02 20:42:18 +0100136 lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100137 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200138 return soft_unicode(s).capitalize()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100139
140
141def do_title(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200142 """Return a titlecased version of the value. I.e. words will start with
Armin Ronacher37a88512007-03-02 20:42:18 +0100143 uppercase letters, all remaining characters are lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100144 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200145 return soft_unicode(s).title()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100146
147
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200148def do_dictsort(value, case_sensitive=False, by='key'):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200149 """ Sort a dict and yield (key, value) pairs. Because python dicts are
Armin Ronacher2b765132007-03-13 16:48:10 +0100150 unsorted you may want to use this function to order them by either
151 key or value:
152
153 .. sourcecode:: jinja
154
155 {% for item in mydict|dictsort %}
156 sort the dict by key, case insensitive
157
158 {% for item in mydict|dicsort(true) %}
159 sort the dict by key, case sensitive
160
161 {% for item in mydict|dictsort(false, 'value') %}
162 sort the dict by key, case insensitive, sorted
163 normally and ordered by value.
164 """
165 if by == 'key':
166 pos = 0
167 elif by == 'value':
168 pos = 1
169 else:
170 raise FilterArgumentError('You can only sort by either '
171 '"key" or "value"')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200172 def sort_func(item):
173 value = item[pos]
Armin Ronacher2b765132007-03-13 16:48:10 +0100174 if isinstance(value, basestring):
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200175 value = unicode(value)
Armin Ronacher2b765132007-03-13 16:48:10 +0100176 if not case_sensitive:
177 value = value.lower()
178 return value
179
Armin Ronacher9a027f42008-04-17 11:13:40 +0200180 return sorted(value.items(), key=sort_func)
Armin Ronacher2b765132007-03-13 16:48:10 +0100181
182
Christoph Hack2751d942008-04-09 12:14:55 +0200183def do_default(value, default_value=u'', boolean=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200184 """If the value is undefined it will return the passed default value,
Armin Ronacher37a88512007-03-02 20:42:18 +0100185 otherwise the value of the variable:
Armin Ronacher92f572f2007-02-26 22:17:32 +0100186
Armin Ronacher37a88512007-03-02 20:42:18 +0100187 .. sourcecode:: jinja
188
189 {{ my_variable|default('my_variable is not defined') }}
190
191 This will output the value of ``my_variable`` if the variable was
192 defined, otherwise ``'my_variable is not defined'``. If you want
193 to use default with variables that evaluate to false you have to
194 set the second parameter to `true`:
195
196 .. sourcecode:: jinja
197
198 {{ ''|default('the string was empty', true) }}
Armin Ronacher92f572f2007-02-26 22:17:32 +0100199 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200200 if (boolean and not value) or isinstance(value, Undefined):
Christoph Hack2751d942008-04-09 12:14:55 +0200201 return default_value
202 return value
Armin Ronacher92f572f2007-02-26 22:17:32 +0100203
204
Christoph Hack2751d942008-04-09 12:14:55 +0200205def do_join(value, d=u''):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200206 """Return a string which is the concatenation of the strings in the
Armin Ronacher37a88512007-03-02 20:42:18 +0100207 sequence. The separator between elements is an empty string per
208 default, you can define ith with the optional parameter:
209
210 .. sourcecode:: jinja
211
212 {{ [1, 2, 3]|join('|') }}
213 -> 1|2|3
214
215 {{ [1, 2, 3]|join }}
216 -> 123
Armin Ronacher92f572f2007-02-26 22:17:32 +0100217 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200218 # 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 hasttr(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))
Armin Ronacher92f572f2007-02-26 22:17:32 +0100236
237
Armin Ronacherfed86c12007-02-27 10:31:14 +0100238def do_center(value, width=80):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200239 """Centers the value in a field of a given width."""
Christoph Hack2751d942008-04-09 12:14:55 +0200240 return unicode(value).center(width)
Armin Ronacherfed86c12007-02-27 10:31:14 +0100241
242
Armin Ronacher9a027f42008-04-17 11:13:40 +0200243@environmentfilter
244def do_first(environment, seq):
245 """Return the frist item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200246 try:
247 return iter(seq).next()
248 except StopIteration:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200249 return environment.undefined('seq|first',
Armin Ronacher5f514882008-04-16 15:29:52 +0200250 extra='the sequence was empty')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100251
252
Armin Ronacher9a027f42008-04-17 11:13:40 +0200253@environmentfilter
254def do_last(environment, seq):
255 """Return the last item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200256 try:
257 return iter(reversed(seq)).next()
258 except StopIteration:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200259 return environment.undefined('seq|last',
Armin Ronacher5f514882008-04-16 15:29:52 +0200260 extra='the sequence was empty')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100261
262
Armin Ronacher9a027f42008-04-17 11:13:40 +0200263@environmentfilter
264def do_random(environment, seq):
265 """Return a random item from the sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200266 try:
267 return choice(seq)
268 except IndexError:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200269 return environment.undefined('seq|random',
Armin Ronacher5f514882008-04-16 15:29:52 +0200270 extra='the sequence was empty')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100271
272
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200273def do_filesizeformat(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200274 """Format the value like a 'human-readable' file size (i.e. 13 KB,
Armin Ronacher5f514882008-04-16 15:29:52 +0200275 4.1 MB, 102 bytes, etc).
Armin Ronacher2b765132007-03-13 16:48:10 +0100276 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200277 # fail silently
278 try:
279 bytes = float(value)
280 except TypeError:
281 bytes = 0
Armin Ronacher2b765132007-03-13 16:48:10 +0100282
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200283 if bytes < 1024:
284 return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
285 elif bytes < 1024 * 1024:
286 return "%.1f KB" % (bytes / 1024)
287 elif bytes < 1024 * 1024 * 1024:
288 return "%.1f MB" % (bytes / (1024 * 1024))
289 return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
Armin Ronacher2b765132007-03-13 16:48:10 +0100290
291
Christoph Hack2751d942008-04-09 12:14:55 +0200292def do_pprint(value, verbose=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200293 """Pretty print a variable. Useful for debugging.
Armin Ronacherf2ce1262007-10-21 21:51:51 +0200294
295 With Jinja 1.2 onwards you can pass it a parameter. If this parameter
Armin Ronacher5f3f1362007-10-21 22:15:04 +0200296 is truthy the output will be more verbose (this requires `pretty`)
Armin Ronacher2b765132007-03-13 16:48:10 +0100297 """
Christoph Hack2751d942008-04-09 12:14:55 +0200298 return pformat(value, verbose=verbose)
Armin Ronacher2b765132007-03-13 16:48:10 +0100299
300
301def do_urlize(value, trim_url_limit=None, nofollow=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200302 """Converts URLs in plain text into clickable links.
Armin Ronacher2b765132007-03-13 16:48:10 +0100303
304 If you pass the filter an additional integer it will shorten the urls
305 to that number. Also a third argument exists that makes the urls
306 "nofollow":
307
308 .. sourcecode:: jinja
309
310 {{ mytext|urlize(40, True) }}
311 links are shortened to 40 chars and defined with rel="nofollow"
312 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200313 return urlize(soft_unicode(value), trim_url_limit, nofollow)
Armin Ronacher2b765132007-03-13 16:48:10 +0100314
315
316def do_indent(s, width=4, indentfirst=False):
317 """
318 {{ s|indent[ width[ indentfirst[ usetab]]] }}
319
320 Return a copy of the passed string, each line indented by
321 4 spaces. The first line is not indented. If you want to
322 change the number of spaces or indent the first line too
323 you can pass additional parameters to the filter:
324
325 .. sourcecode:: jinja
326
327 {{ mytext|indent(2, True) }}
328 indent by two spaces and indent the first line too.
329 """
330 indention = ' ' * width
331 if indentfirst:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200332 return u'\n'.join(indention + line for line in s.splitlines())
Armin Ronacher2b765132007-03-13 16:48:10 +0100333 return s.replace('\n', '\n' + indention)
Armin Ronacher2b765132007-03-13 16:48:10 +0100334
335
336def do_truncate(s, length=255, killwords=False, end='...'):
337 """
Armin Ronacher2b765132007-03-13 16:48:10 +0100338 Return a truncated copy of the string. The length is specified
339 with the first parameter which defaults to ``255``. If the second
340 parameter is ``true`` the filter will cut the text at length. Otherwise
341 it will try to save the last word. If the text was in fact
342 truncated it will append an ellipsis sign (``"..."``). If you want a
343 different ellipsis sign than ``"..."`` you can specify it using the
344 third parameter.
345
346 .. sourcecode jinja::
347
348 {{ mytext|truncate(300, false, '&raquo;') }}
349 truncate mytext to 300 chars, don't split up words, use a
350 right pointing double arrow as ellipsis sign.
351 """
352 if len(s) <= length:
353 return s
354 elif killwords:
355 return s[:length] + end
356 words = s.split(' ')
357 result = []
358 m = 0
359 for word in words:
360 m += len(word) + 1
361 if m > length:
362 break
363 result.append(word)
364 result.append(end)
365 return u' '.join(result)
Armin Ronacher2b765132007-03-13 16:48:10 +0100366
367
368def do_wordwrap(s, pos=79, hard=False):
369 """
370 Return a copy of the string passed to the filter wrapped after
371 ``79`` characters. You can override this default using the first
372 parameter. If you set the second parameter to `true` Jinja will
373 also split words apart (usually a bad idea because it makes
374 reading hard).
375 """
376 if len(s) < pos:
377 return s
378 if hard:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200379 return u'\n'.join(s[idx:idx + pos] for idx in
380 xrange(0, len(s), pos))
381
382 # TODO: switch to wordwrap.wrap
Armin Ronacher2b765132007-03-13 16:48:10 +0100383 # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
384 return reduce(lambda line, word, pos=pos: u'%s%s%s' %
385 (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
386 len(word.split('\n', 1)[0]) >= pos)],
387 word), s.split(' '))
Armin Ronacher2b765132007-03-13 16:48:10 +0100388
389
390def do_wordcount(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200391 """Count the words in that string."""
392 return len(x for x in s.split() if x)
Armin Ronacher2b765132007-03-13 16:48:10 +0100393
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200394
395def do_int(value, default=0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200396 """Convert the value into an integer. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100397 conversion doesn't work it will return ``0``. You can
398 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100399 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200400 try:
401 return int(value)
402 except (TypeError, ValueError):
Armin Ronacherab45b842007-03-18 20:47:50 +0100403 try:
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200404 return int(float(value))
Armin Ronacherab45b842007-03-18 20:47:50 +0100405 except (TypeError, ValueError):
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200406 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100407
408
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200409def do_float(value, default=0.0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200410 """Convert the value into a floating point number. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100411 conversion doesn't work it will return ``0.0``. You can
412 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100413 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200414 try:
415 return float(value)
416 except (TypeError, ValueError):
417 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100418
419
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200420def do_string(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200421 """Convert the value into an string."""
422 return soft_unicode(value)
Armin Ronacher2b765132007-03-13 16:48:10 +0100423
424
Armin Ronacher5f514882008-04-16 15:29:52 +0200425def do_format(value, *args, **kwargs):
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100426 """
427 Apply python string formatting on an object:
428
429 .. sourcecode:: jinja
430
431 {{ "%s - %s"|format("Hello?", "Foo!") }}
432 -> Hello? - Foo!
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100433 """
Armin Ronacher5f514882008-04-16 15:29:52 +0200434 if kwargs:
435 kwargs.update(idx, arg in enumerate(args))
436 args = kwargs
Armin Ronacher9a027f42008-04-17 11:13:40 +0200437 return soft_unicode(value) % args
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100438
439
Armin Ronacher566295e2007-03-19 13:19:34 +0100440def do_trim(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200441 """Strip leading and trailing whitespace."""
442 return soft_unicode(value).strip()
Armin Ronacher566295e2007-03-19 13:19:34 +0100443
444
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200445def do_striptags(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200446 """Strip SGML/XML tags and replace adjacent whitespace by one space.
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200447 """
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200448 return ' '.join(_striptags_re.sub('', value).split())
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200449
450
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200451def do_slice(value, slices, fill_with=None):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200452 """Slice an iterator and return a list of lists containing
Armin Ronacherd071f952007-04-13 22:32:11 +0200453 those items. Useful if you want to create a div containing
454 three div tags that represent columns:
455
Armin Ronacher89376072007-04-13 22:34:35 +0200456 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200457
458 <div class="columwrapper">
459 {%- for column in items|slice(3) %}
460 <ul class="column-{{ loop.index }}">
461 {%- for item in column %}
462 <li>{{ item }}</li>
463 {%- endfor %}
464 </ul>
465 {%- endfor %}
466 </div>
467
Armin Ronachereec31382007-04-14 14:50:45 +0200468 If you pass it a second argument it's used to fill missing
469 values on the last iteration.
Armin Ronacherd071f952007-04-13 22:32:11 +0200470 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200471 result = []
472 seq = list(value)
473 length = len(seq)
474 items_per_slice = length // slices
475 slices_with_extra = length % slices
476 offset = 0
477 for slice_number in xrange(slices):
478 start = offset + slice_number * items_per_slice
479 if slice_number < slices_with_extra:
480 offset += 1
481 end = offset + (slice_number + 1) * items_per_slice
482 tmp = seq[start:end]
483 if fill_with is not None and slice_number >= slices_with_extra:
484 tmp.append(fill_with)
485 result.append(tmp)
486 return result
Armin Ronacherd071f952007-04-13 22:32:11 +0200487
488
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200489def do_batch(value, linecount, fill_with=None):
Armin Ronacherd071f952007-04-13 22:32:11 +0200490 """
491 A filter that batches items. It works pretty much like `slice`
492 just the other way round. It returns a list of lists with the
493 given number of items. If you provide a second parameter this
494 is used to fill missing items. See this example:
495
Armin Ronacher89376072007-04-13 22:34:35 +0200496 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200497
498 <table>
499 {%- for row in items|batch(3, '&nbsp;') %}
500 <tr>
501 {%- for column in row %}
502 <tr>{{ column }}</td>
503 {%- endfor %}
504 </tr>
505 {%- endfor %}
506 </table>
Armin Ronacherd071f952007-04-13 22:32:11 +0200507 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200508 result = []
509 tmp = []
510 for item in value:
511 if len(tmp) == linecount:
Armin Ronacherd071f952007-04-13 22:32:11 +0200512 result.append(tmp)
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200513 tmp = []
514 tmp.append(item)
515 if tmp:
516 if fill_with is not None and len(tmp) < linecount:
517 tmp += [fill_with] * (linecount - len(tmp))
518 result.append(tmp)
519 return result
Armin Ronacherd071f952007-04-13 22:32:11 +0200520
521
Armin Ronacher9a027f42008-04-17 11:13:40 +0200522def do_round(value, precision=0, method='common'):
523 """Round the number to a given precision. The first
Armin Ronachereec31382007-04-14 14:50:45 +0200524 parameter specifies the precision (default is ``0``), the
525 second the rounding method:
526
527 - ``'common'`` rounds either up or down
528 - ``'ceil'`` always rounds up
529 - ``'floor'`` always rounds down
530
531 If you don't specify a method ``'common'`` is used.
532
533 .. sourcecode:: jinja
534
535 {{ 42.55|round }}
536 -> 43
537 {{ 42.55|round(1, 'floor') }}
538 -> 42.5
539 """
540 if not method in ('common', 'ceil', 'floor'):
541 raise FilterArgumentError('method must be common, ceil or floor')
542 if precision < 0:
543 raise FilterArgumentError('precision must be a postive integer '
544 'or zero.')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200545 if method == 'common':
546 return round(value, precision)
547 func = getattr(math, method)
548 if precision:
549 return func(value * 10 * precision) / (10 * precision)
550 else:
551 return func(value)
Armin Ronachereec31382007-04-14 14:50:45 +0200552
553
Armin Ronacher9a027f42008-04-17 11:13:40 +0200554def do_sort(value, reverse=False):
555 """Sort a sequence. Per default it sorts ascending, if you pass it
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200556 `True` as first argument it will reverse the sorting.
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200557 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200558 return sorted(value, reverse=reverse)
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200559
560
Armin Ronacher9a027f42008-04-17 11:13:40 +0200561@environmentfilter
562def do_groupby(environment, value, attribute):
563 """Group a sequence of objects by a common attribute.
Armin Ronachere39a5d22007-06-23 21:11:53 +0200564
565 If you for example have a list of dicts or objects that represent persons
566 with `gender`, `first_name` and `last_name` attributes and you want to
567 group all users by genders you can do something like the following
568 snippet:
569
570 .. sourcecode:: html+jinja
571
572 <ul>
573 {% for group in persons|groupby('gender') %}
574 <li>{{ group.grouper }}<ul>
575 {% for person in group.list %}
576 <li>{{ person.first_name }} {{ person.last_name }}</li>
577 {% endfor %}</ul></li>
578 {% endfor %}
579 </ul>
580
581 As you can see the item we're grouping by is stored in the `grouper`
582 attribute and the `list` contains all the objects that have this grouper
583 in common.
584 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200585 expr = lambda x: environment.subscribe(x, attribute)
586 return sorted([{
587 'grouper': a,
588 'list': b
589 } for a, b in groupby(sorted(value, key=expr), expr)],
590 key=itemgetter('grouper'))
Armin Ronachere39a5d22007-06-23 21:11:53 +0200591
592
Armin Ronacher92f572f2007-02-26 22:17:32 +0100593FILTERS = {
594 'replace': do_replace,
595 'upper': do_upper,
596 'lower': do_lower,
597 'escape': do_escape,
598 'e': do_escape,
Armin Ronacher450756b2007-04-15 15:13:59 +0200599 'xmlattr': do_xmlattr,
Armin Ronacher92f572f2007-02-26 22:17:32 +0100600 'capitalize': do_capitalize,
601 'title': do_title,
602 'default': do_default,
603 'join': do_join,
Armin Ronacher5f514882008-04-16 15:29:52 +0200604 'count': len,
Armin Ronacher2b765132007-03-13 16:48:10 +0100605 'dictsort': do_dictsort,
Armin Ronacher5f514882008-04-16 15:29:52 +0200606 'length': len,
607 'reverse': reversed,
Armin Ronacherfed86c12007-02-27 10:31:14 +0100608 'center': do_center,
609 'title': do_title,
610 'capitalize': do_capitalize,
611 'first': do_first,
612 'last': do_last,
613 'random': do_random,
Armin Ronacher2b765132007-03-13 16:48:10 +0100614 'filesizeformat': do_filesizeformat,
615 'pprint': do_pprint,
616 'indent': do_indent,
617 'truncate': do_truncate,
618 'wordwrap': do_wordwrap,
619 'wordcount': do_wordcount,
Armin Ronacher2b765132007-03-13 16:48:10 +0100620 'int': do_int,
621 'float': do_float,
Tassilo Schweyer934cb712007-03-14 18:04:07 +0100622 'string': do_string,
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100623 'urlize': do_urlize,
624 'format': do_format,
Armin Ronacherd071f952007-04-13 22:32:11 +0200625 'trim': do_trim,
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200626 'striptags': do_striptags,
Armin Ronacherd071f952007-04-13 22:32:11 +0200627 'slice': do_slice,
Armin Ronachereec31382007-04-14 14:50:45 +0200628 'batch': do_batch,
Armin Ronacher5f514882008-04-16 15:29:52 +0200629 'sum': sum,
630 'abs': abs,
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200631 'round': do_round,
Armin Ronachere39a5d22007-06-23 21:11:53 +0200632 'sort': do_sort,
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200633 'groupby': do_groupby,
634 'safe': Markup
Armin Ronacher92f572f2007-02-26 22:17:32 +0100635}