web/lib/django/template/defaultfilters.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 """Default variable filters."""
       
     2 
       
     3 import re
       
     4 from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
       
     5 import random as random_module
       
     6 try:
       
     7     from functools import wraps
       
     8 except ImportError:
       
     9     from django.utils.functional import wraps  # Python 2.4 fallback.
       
    10 
       
    11 from django.template import Variable, Library
       
    12 from django.conf import settings
       
    13 from django.utils import formats
       
    14 from django.utils.translation import ugettext, ungettext
       
    15 from django.utils.encoding import force_unicode, iri_to_uri
       
    16 from django.utils.safestring import mark_safe, SafeData
       
    17 
       
    18 register = Library()
       
    19 
       
    20 #######################
       
    21 # STRING DECORATOR    #
       
    22 #######################
       
    23 
       
    24 def stringfilter(func):
       
    25     """
       
    26     Decorator for filters which should only receive unicode objects. The object
       
    27     passed as the first positional argument will be converted to a unicode
       
    28     object.
       
    29     """
       
    30     def _dec(*args, **kwargs):
       
    31         if args:
       
    32             args = list(args)
       
    33             args[0] = force_unicode(args[0])
       
    34             if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
       
    35                 return mark_safe(func(*args, **kwargs))
       
    36         return func(*args, **kwargs)
       
    37 
       
    38     # Include a reference to the real function (used to check original
       
    39     # arguments by the template parser).
       
    40     _dec._decorated_function = getattr(func, '_decorated_function', func)
       
    41     for attr in ('is_safe', 'needs_autoescape'):
       
    42         if hasattr(func, attr):
       
    43             setattr(_dec, attr, getattr(func, attr))
       
    44     return wraps(func)(_dec)
       
    45 
       
    46 ###################
       
    47 # STRINGS         #
       
    48 ###################
       
    49 
       
    50 def addslashes(value):
       
    51     """
       
    52     Adds slashes before quotes. Useful for escaping strings in CSV, for
       
    53     example. Less useful for escaping JavaScript; use the ``escapejs``
       
    54     filter instead.
       
    55     """
       
    56     return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
       
    57 addslashes.is_safe = True
       
    58 addslashes = stringfilter(addslashes)
       
    59 
       
    60 def capfirst(value):
       
    61     """Capitalizes the first character of the value."""
       
    62     return value and value[0].upper() + value[1:]
       
    63 capfirst.is_safe=True
       
    64 capfirst = stringfilter(capfirst)
       
    65 
       
    66 _base_js_escapes = (
       
    67     ('\\', r'\u005C'),
       
    68     ('\'', r'\u0027'),
       
    69     ('"', r'\u0022'),
       
    70     ('>', r'\u003E'),
       
    71     ('<', r'\u003C'),
       
    72     ('&', r'\u0026'),
       
    73     ('=', r'\u003D'),
       
    74     ('-', r'\u002D'),
       
    75     (';', r'\u003B'),
       
    76     (u'\u2028', r'\u2028'),
       
    77     (u'\u2029', r'\u2029')
       
    78 )
       
    79 
       
    80 # Escape every ASCII character with a value less than 32.
       
    81 _js_escapes = (_base_js_escapes +
       
    82                tuple([('%c' % z, '\\u%04X' % z) for z in range(32)]))
       
    83 
       
    84 def escapejs(value):
       
    85     """Hex encodes characters for use in JavaScript strings."""
       
    86     for bad, good in _js_escapes:
       
    87         value = value.replace(bad, good)
       
    88     return value
       
    89 escapejs = stringfilter(escapejs)
       
    90 
       
    91 def fix_ampersands(value):
       
    92     """Replaces ampersands with ``&amp;`` entities."""
       
    93     from django.utils.html import fix_ampersands
       
    94     return fix_ampersands(value)
       
    95 fix_ampersands.is_safe=True
       
    96 fix_ampersands = stringfilter(fix_ampersands)
       
    97 
       
    98 # Values for testing floatformat input against infinity and NaN representations,
       
    99 # which differ across platforms and Python versions.  Some (i.e. old Windows
       
   100 # ones) are not recognized by Decimal but we want to return them unchanged vs.
       
   101 # returning an empty string as we do for completley invalid input.  Note these
       
   102 # need to be built up from values that are not inf/nan, since inf/nan values do
       
   103 # not reload properly from .pyc files on Windows prior to some level of Python 2.5
       
   104 # (see Python Issue757815 and Issue1080440).
       
   105 pos_inf = 1e200 * 1e200
       
   106 neg_inf = -1e200 * 1e200
       
   107 nan = (1e200 * 1e200) / (1e200 * 1e200)
       
   108 special_floats = [str(pos_inf), str(neg_inf), str(nan)]
       
   109 
       
   110 def floatformat(text, arg=-1):
       
   111     """
       
   112     Displays a float to a specified number of decimal places.
       
   113 
       
   114     If called without an argument, it displays the floating point number with
       
   115     one decimal place -- but only if there's a decimal place to be displayed:
       
   116 
       
   117     * num1 = 34.23234
       
   118     * num2 = 34.00000
       
   119     * num3 = 34.26000
       
   120     * {{ num1|floatformat }} displays "34.2"
       
   121     * {{ num2|floatformat }} displays "34"
       
   122     * {{ num3|floatformat }} displays "34.3"
       
   123 
       
   124     If arg is positive, it will always display exactly arg number of decimal
       
   125     places:
       
   126 
       
   127     * {{ num1|floatformat:3 }} displays "34.232"
       
   128     * {{ num2|floatformat:3 }} displays "34.000"
       
   129     * {{ num3|floatformat:3 }} displays "34.260"
       
   130 
       
   131     If arg is negative, it will display arg number of decimal places -- but
       
   132     only if there are places to be displayed:
       
   133 
       
   134     * {{ num1|floatformat:"-3" }} displays "34.232"
       
   135     * {{ num2|floatformat:"-3" }} displays "34"
       
   136     * {{ num3|floatformat:"-3" }} displays "34.260"
       
   137 
       
   138     If the input float is infinity or NaN, the (platform-dependent) string
       
   139     representation of that value will be displayed.
       
   140     """
       
   141 
       
   142     try:
       
   143         input_val = force_unicode(text)
       
   144         d = Decimal(input_val)
       
   145     except UnicodeEncodeError:
       
   146         return u''
       
   147     except InvalidOperation:
       
   148         if input_val in special_floats:
       
   149             return input_val
       
   150         try:
       
   151             d = Decimal(force_unicode(float(text)))
       
   152         except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError):
       
   153             return u''
       
   154     try:
       
   155         p = int(arg)
       
   156     except ValueError:
       
   157         return input_val
       
   158 
       
   159     try:
       
   160         m = int(d) - d
       
   161     except (ValueError, OverflowError, InvalidOperation):
       
   162         return input_val
       
   163 
       
   164     if not m and p < 0:
       
   165         return mark_safe(formats.number_format(u'%d' % (int(d)), 0))
       
   166 
       
   167     if p == 0:
       
   168         exp = Decimal(1)
       
   169     else:
       
   170         exp = Decimal('1.0') / (Decimal(10) ** abs(p))
       
   171     try:
       
   172         return mark_safe(formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p)))
       
   173     except InvalidOperation:
       
   174         return input_val
       
   175 floatformat.is_safe = True
       
   176 
       
   177 def iriencode(value):
       
   178     """Escapes an IRI value for use in a URL."""
       
   179     return force_unicode(iri_to_uri(value))
       
   180 iriencode.is_safe = True
       
   181 iriencode = stringfilter(iriencode)
       
   182 
       
   183 def linenumbers(value, autoescape=None):
       
   184     """Displays text with line numbers."""
       
   185     from django.utils.html import escape
       
   186     lines = value.split(u'\n')
       
   187     # Find the maximum width of the line count, for use with zero padding
       
   188     # string format command
       
   189     width = unicode(len(unicode(len(lines))))
       
   190     if not autoescape or isinstance(value, SafeData):
       
   191         for i, line in enumerate(lines):
       
   192             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
       
   193     else:
       
   194         for i, line in enumerate(lines):
       
   195             lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
       
   196     return mark_safe(u'\n'.join(lines))
       
   197 linenumbers.is_safe = True
       
   198 linenumbers.needs_autoescape = True
       
   199 linenumbers = stringfilter(linenumbers)
       
   200 
       
   201 def lower(value):
       
   202     """Converts a string into all lowercase."""
       
   203     return value.lower()
       
   204 lower.is_safe = True
       
   205 lower = stringfilter(lower)
       
   206 
       
   207 def make_list(value):
       
   208     """
       
   209     Returns the value turned into a list.
       
   210 
       
   211     For an integer, it's a list of digits.
       
   212     For a string, it's a list of characters.
       
   213     """
       
   214     return list(value)
       
   215 make_list.is_safe = False
       
   216 make_list = stringfilter(make_list)
       
   217 
       
   218 def slugify(value):
       
   219     """
       
   220     Normalizes string, converts to lowercase, removes non-alpha characters,
       
   221     and converts spaces to hyphens.
       
   222     """
       
   223     import unicodedata
       
   224     value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
       
   225     value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
       
   226     return mark_safe(re.sub('[-\s]+', '-', value))
       
   227 slugify.is_safe = True
       
   228 slugify = stringfilter(slugify)
       
   229 
       
   230 def stringformat(value, arg):
       
   231     """
       
   232     Formats the variable according to the arg, a string formatting specifier.
       
   233 
       
   234     This specifier uses Python string formating syntax, with the exception that
       
   235     the leading "%" is dropped.
       
   236 
       
   237     See http://docs.python.org/lib/typesseq-strings.html for documentation
       
   238     of Python string formatting
       
   239     """
       
   240     try:
       
   241         return (u"%" + unicode(arg)) % value
       
   242     except (ValueError, TypeError):
       
   243         return u""
       
   244 stringformat.is_safe = True
       
   245 
       
   246 def title(value):
       
   247     """Converts a string into titlecase."""
       
   248     t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
       
   249     return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t)
       
   250 title.is_safe = True
       
   251 title = stringfilter(title)
       
   252 
       
   253 def truncatewords(value, arg):
       
   254     """
       
   255     Truncates a string after a certain number of words.
       
   256 
       
   257     Argument: Number of words to truncate after.
       
   258     """
       
   259     from django.utils.text import truncate_words
       
   260     try:
       
   261         length = int(arg)
       
   262     except ValueError: # Invalid literal for int().
       
   263         return value # Fail silently.
       
   264     return truncate_words(value, length)
       
   265 truncatewords.is_safe = True
       
   266 truncatewords = stringfilter(truncatewords)
       
   267 
       
   268 def truncatewords_html(value, arg):
       
   269     """
       
   270     Truncates HTML after a certain number of words.
       
   271 
       
   272     Argument: Number of words to truncate after.
       
   273     """
       
   274     from django.utils.text import truncate_html_words
       
   275     try:
       
   276         length = int(arg)
       
   277     except ValueError: # invalid literal for int()
       
   278         return value # Fail silently.
       
   279     return truncate_html_words(value, length)
       
   280 truncatewords_html.is_safe = True
       
   281 truncatewords_html = stringfilter(truncatewords_html)
       
   282 
       
   283 def upper(value):
       
   284     """Converts a string into all uppercase."""
       
   285     return value.upper()
       
   286 upper.is_safe = False
       
   287 upper = stringfilter(upper)
       
   288 
       
   289 def urlencode(value):
       
   290     """Escapes a value for use in a URL."""
       
   291     from django.utils.http import urlquote
       
   292     return urlquote(value)
       
   293 urlencode.is_safe = False
       
   294 urlencode = stringfilter(urlencode)
       
   295 
       
   296 def urlize(value, autoescape=None):
       
   297     """Converts URLs in plain text into clickable links."""
       
   298     from django.utils.html import urlize
       
   299     return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
       
   300 urlize.is_safe=True
       
   301 urlize.needs_autoescape = True
       
   302 urlize = stringfilter(urlize)
       
   303 
       
   304 def urlizetrunc(value, limit, autoescape=None):
       
   305     """
       
   306     Converts URLs into clickable links, truncating URLs to the given character
       
   307     limit, and adding 'rel=nofollow' attribute to discourage spamming.
       
   308 
       
   309     Argument: Length to truncate URLs to.
       
   310     """
       
   311     from django.utils.html import urlize
       
   312     return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
       
   313                             autoescape=autoescape))
       
   314 urlizetrunc.is_safe = True
       
   315 urlizetrunc.needs_autoescape = True
       
   316 urlizetrunc = stringfilter(urlizetrunc)
       
   317 
       
   318 def wordcount(value):
       
   319     """Returns the number of words."""
       
   320     return len(value.split())
       
   321 wordcount.is_safe = False
       
   322 wordcount = stringfilter(wordcount)
       
   323 
       
   324 def wordwrap(value, arg):
       
   325     """
       
   326     Wraps words at specified line length.
       
   327 
       
   328     Argument: number of characters to wrap the text at.
       
   329     """
       
   330     from django.utils.text import wrap
       
   331     return wrap(value, int(arg))
       
   332 wordwrap.is_safe = True
       
   333 wordwrap = stringfilter(wordwrap)
       
   334 
       
   335 def ljust(value, arg):
       
   336     """
       
   337     Left-aligns the value in a field of a given width.
       
   338 
       
   339     Argument: field size.
       
   340     """
       
   341     return value.ljust(int(arg))
       
   342 ljust.is_safe = True
       
   343 ljust = stringfilter(ljust)
       
   344 
       
   345 def rjust(value, arg):
       
   346     """
       
   347     Right-aligns the value in a field of a given width.
       
   348 
       
   349     Argument: field size.
       
   350     """
       
   351     return value.rjust(int(arg))
       
   352 rjust.is_safe = True
       
   353 rjust = stringfilter(rjust)
       
   354 
       
   355 def center(value, arg):
       
   356     """Centers the value in a field of a given width."""
       
   357     return value.center(int(arg))
       
   358 center.is_safe = True
       
   359 center = stringfilter(center)
       
   360 
       
   361 def cut(value, arg):
       
   362     """
       
   363     Removes all values of arg from the given string.
       
   364     """
       
   365     safe = isinstance(value, SafeData)
       
   366     value = value.replace(arg, u'')
       
   367     if safe and arg != ';':
       
   368         return mark_safe(value)
       
   369     return value
       
   370 cut = stringfilter(cut)
       
   371 
       
   372 ###################
       
   373 # HTML STRINGS    #
       
   374 ###################
       
   375 
       
   376 def escape(value):
       
   377     """
       
   378     Marks the value as a string that should not be auto-escaped.
       
   379     """
       
   380     from django.utils.safestring import mark_for_escaping
       
   381     return mark_for_escaping(value)
       
   382 escape.is_safe = True
       
   383 escape = stringfilter(escape)
       
   384 
       
   385 def force_escape(value):
       
   386     """
       
   387     Escapes a string's HTML. This returns a new string containing the escaped
       
   388     characters (as opposed to "escape", which marks the content for later
       
   389     possible escaping).
       
   390     """
       
   391     from django.utils.html import escape
       
   392     return mark_safe(escape(value))
       
   393 force_escape = stringfilter(force_escape)
       
   394 force_escape.is_safe = True
       
   395 
       
   396 def linebreaks(value, autoescape=None):
       
   397     """
       
   398     Replaces line breaks in plain text with appropriate HTML; a single
       
   399     newline becomes an HTML line break (``<br />``) and a new line
       
   400     followed by a blank line becomes a paragraph break (``</p>``).
       
   401     """
       
   402     from django.utils.html import linebreaks
       
   403     autoescape = autoescape and not isinstance(value, SafeData)
       
   404     return mark_safe(linebreaks(value, autoescape))
       
   405 linebreaks.is_safe = True
       
   406 linebreaks.needs_autoescape = True
       
   407 linebreaks = stringfilter(linebreaks)
       
   408 
       
   409 def linebreaksbr(value, autoescape=None):
       
   410     """
       
   411     Converts all newlines in a piece of plain text to HTML line breaks
       
   412     (``<br />``).
       
   413     """
       
   414     if autoescape and not isinstance(value, SafeData):
       
   415         from django.utils.html import escape
       
   416         value = escape(value)
       
   417     return mark_safe(value.replace('\n', '<br />'))
       
   418 linebreaksbr.is_safe = True
       
   419 linebreaksbr.needs_autoescape = True
       
   420 linebreaksbr = stringfilter(linebreaksbr)
       
   421 
       
   422 def safe(value):
       
   423     """
       
   424     Marks the value as a string that should not be auto-escaped.
       
   425     """
       
   426     return mark_safe(value)
       
   427 safe.is_safe = True
       
   428 safe = stringfilter(safe)
       
   429 
       
   430 def safeseq(value):
       
   431     """
       
   432     A "safe" filter for sequences. Marks each element in the sequence,
       
   433     individually, as safe, after converting them to unicode. Returns a list
       
   434     with the results.
       
   435     """
       
   436     return [mark_safe(force_unicode(obj)) for obj in value]
       
   437 safeseq.is_safe = True
       
   438 
       
   439 def removetags(value, tags):
       
   440     """Removes a space separated list of [X]HTML tags from the output."""
       
   441     tags = [re.escape(tag) for tag in tags.split()]
       
   442     tags_re = u'(%s)' % u'|'.join(tags)
       
   443     starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
       
   444     endtag_re = re.compile(u'</%s>' % tags_re)
       
   445     value = starttag_re.sub(u'', value)
       
   446     value = endtag_re.sub(u'', value)
       
   447     return value
       
   448 removetags.is_safe = True
       
   449 removetags = stringfilter(removetags)
       
   450 
       
   451 def striptags(value):
       
   452     """Strips all [X]HTML tags."""
       
   453     from django.utils.html import strip_tags
       
   454     return strip_tags(value)
       
   455 striptags.is_safe = True
       
   456 striptags = stringfilter(striptags)
       
   457 
       
   458 ###################
       
   459 # LISTS           #
       
   460 ###################
       
   461 
       
   462 def dictsort(value, arg):
       
   463     """
       
   464     Takes a list of dicts, returns that list sorted by the property given in
       
   465     the argument.
       
   466     """
       
   467     var_resolve = Variable(arg).resolve
       
   468     decorated = [(var_resolve(item), item) for item in value]
       
   469     decorated.sort()
       
   470     return [item[1] for item in decorated]
       
   471 dictsort.is_safe = False
       
   472 
       
   473 def dictsortreversed(value, arg):
       
   474     """
       
   475     Takes a list of dicts, returns that list sorted in reverse order by the
       
   476     property given in the argument.
       
   477     """
       
   478     var_resolve = Variable(arg).resolve
       
   479     decorated = [(var_resolve(item), item) for item in value]
       
   480     decorated.sort()
       
   481     decorated.reverse()
       
   482     return [item[1] for item in decorated]
       
   483 dictsortreversed.is_safe = False
       
   484 
       
   485 def first(value):
       
   486     """Returns the first item in a list."""
       
   487     try:
       
   488         return value[0]
       
   489     except IndexError:
       
   490         return u''
       
   491 first.is_safe = False
       
   492 
       
   493 def join(value, arg, autoescape=None):
       
   494     """
       
   495     Joins a list with a string, like Python's ``str.join(list)``.
       
   496     """
       
   497     value = map(force_unicode, value)
       
   498     if autoescape:
       
   499         from django.utils.html import conditional_escape
       
   500         value = [conditional_escape(v) for v in value]
       
   501     try:
       
   502         data = arg.join(value)
       
   503     except AttributeError: # fail silently but nicely
       
   504         return value
       
   505     return mark_safe(data)
       
   506 join.is_safe = True
       
   507 join.needs_autoescape = True
       
   508 
       
   509 def last(value):
       
   510     "Returns the last item in a list"
       
   511     try:
       
   512         return value[-1]
       
   513     except IndexError:
       
   514         return u''
       
   515 last.is_safe = True
       
   516 
       
   517 def length(value):
       
   518     """Returns the length of the value - useful for lists."""
       
   519     try:
       
   520         return len(value)
       
   521     except (ValueError, TypeError):
       
   522         return ''
       
   523 length.is_safe = True
       
   524 
       
   525 def length_is(value, arg):
       
   526     """Returns a boolean of whether the value's length is the argument."""
       
   527     try:
       
   528         return len(value) == int(arg)
       
   529     except (ValueError, TypeError):
       
   530         return ''
       
   531 length_is.is_safe = False
       
   532 
       
   533 def random(value):
       
   534     """Returns a random item from the list."""
       
   535     return random_module.choice(value)
       
   536 random.is_safe = True
       
   537 
       
   538 def slice_(value, arg):
       
   539     """
       
   540     Returns a slice of the list.
       
   541 
       
   542     Uses the same syntax as Python's list slicing; see
       
   543     http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice
       
   544     for an introduction.
       
   545     """
       
   546     try:
       
   547         bits = []
       
   548         for x in arg.split(u':'):
       
   549             if len(x) == 0:
       
   550                 bits.append(None)
       
   551             else:
       
   552                 bits.append(int(x))
       
   553         return value[slice(*bits)]
       
   554 
       
   555     except (ValueError, TypeError):
       
   556         return value # Fail silently.
       
   557 slice_.is_safe = True
       
   558 
       
   559 def unordered_list(value, autoescape=None):
       
   560     """
       
   561     Recursively takes a self-nested list and returns an HTML unordered list --
       
   562     WITHOUT opening and closing <ul> tags.
       
   563 
       
   564     The list is assumed to be in the proper format. For example, if ``var``
       
   565     contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
       
   566     then ``{{ var|unordered_list }}`` would return::
       
   567 
       
   568         <li>States
       
   569         <ul>
       
   570                 <li>Kansas
       
   571                 <ul>
       
   572                         <li>Lawrence</li>
       
   573                         <li>Topeka</li>
       
   574                 </ul>
       
   575                 </li>
       
   576                 <li>Illinois</li>
       
   577         </ul>
       
   578         </li>
       
   579     """
       
   580     if autoescape:
       
   581         from django.utils.html import conditional_escape
       
   582         escaper = conditional_escape
       
   583     else:
       
   584         escaper = lambda x: x
       
   585     def convert_old_style_list(list_):
       
   586         """
       
   587         Converts old style lists to the new easier to understand format.
       
   588 
       
   589         The old list format looked like:
       
   590             ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
       
   591 
       
   592         And it is converted to:
       
   593             ['Item 1', ['Item 1.1', 'Item 1.2]]
       
   594         """
       
   595         if not isinstance(list_, (tuple, list)) or len(list_) != 2:
       
   596             return list_, False
       
   597         first_item, second_item = list_
       
   598         if second_item == []:
       
   599             return [first_item], True
       
   600         old_style_list = True
       
   601         new_second_item = []
       
   602         for sublist in second_item:
       
   603             item, old_style_list = convert_old_style_list(sublist)
       
   604             if not old_style_list:
       
   605                 break
       
   606             new_second_item.extend(item)
       
   607         if old_style_list:
       
   608             second_item = new_second_item
       
   609         return [first_item, second_item], old_style_list
       
   610     def _helper(list_, tabs=1):
       
   611         indent = u'\t' * tabs
       
   612         output = []
       
   613 
       
   614         list_length = len(list_)
       
   615         i = 0
       
   616         while i < list_length:
       
   617             title = list_[i]
       
   618             sublist = ''
       
   619             sublist_item = None
       
   620             if isinstance(title, (list, tuple)):
       
   621                 sublist_item = title
       
   622                 title = ''
       
   623             elif i < list_length - 1:
       
   624                 next_item = list_[i+1]
       
   625                 if next_item and isinstance(next_item, (list, tuple)):
       
   626                     # The next item is a sub-list.
       
   627                     sublist_item = next_item
       
   628                     # We've processed the next item now too.
       
   629                     i += 1
       
   630             if sublist_item:
       
   631                 sublist = _helper(sublist_item, tabs+1)
       
   632                 sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
       
   633                                                          indent, indent)
       
   634             output.append('%s<li>%s%s</li>' % (indent,
       
   635                     escaper(force_unicode(title)), sublist))
       
   636             i += 1
       
   637         return '\n'.join(output)
       
   638     value, converted = convert_old_style_list(value)
       
   639     return mark_safe(_helper(value))
       
   640 unordered_list.is_safe = True
       
   641 unordered_list.needs_autoescape = True
       
   642 
       
   643 ###################
       
   644 # INTEGERS        #
       
   645 ###################
       
   646 
       
   647 def add(value, arg):
       
   648     """Adds the arg to the value."""
       
   649     try:
       
   650         return int(value) + int(arg)
       
   651     except (ValueError, TypeError):
       
   652         try:
       
   653             return value + arg
       
   654         except:
       
   655             return value
       
   656 add.is_safe = False
       
   657 
       
   658 def get_digit(value, arg):
       
   659     """
       
   660     Given a whole number, returns the requested digit of it, where 1 is the
       
   661     right-most digit, 2 is the second-right-most digit, etc. Returns the
       
   662     original value for invalid input (if input or argument is not an integer,
       
   663     or if argument is less than 1). Otherwise, output is always an integer.
       
   664     """
       
   665     try:
       
   666         arg = int(arg)
       
   667         value = int(value)
       
   668     except ValueError:
       
   669         return value # Fail silently for an invalid argument
       
   670     if arg < 1:
       
   671         return value
       
   672     try:
       
   673         return int(str(value)[-arg])
       
   674     except IndexError:
       
   675         return 0
       
   676 get_digit.is_safe = False
       
   677 
       
   678 ###################
       
   679 # DATES           #
       
   680 ###################
       
   681 
       
   682 def date(value, arg=None):
       
   683     """Formats a date according to the given format."""
       
   684     from django.utils.dateformat import format
       
   685     if not value:
       
   686         return u''
       
   687     if arg is None:
       
   688         arg = settings.DATE_FORMAT
       
   689     try:
       
   690         return formats.date_format(value, arg)
       
   691     except AttributeError:
       
   692         try:
       
   693             return format(value, arg)
       
   694         except AttributeError:
       
   695             return ''
       
   696 date.is_safe = False
       
   697 
       
   698 def time(value, arg=None):
       
   699     """Formats a time according to the given format."""
       
   700     from django.utils import dateformat
       
   701     if value in (None, u''):
       
   702         return u''
       
   703     if arg is None:
       
   704         arg = settings.TIME_FORMAT
       
   705     try:
       
   706         return formats.time_format(value, arg)
       
   707     except AttributeError:
       
   708         try:
       
   709             return dateformat.time_format(value, arg)
       
   710         except AttributeError:
       
   711             return ''
       
   712 time.is_safe = False
       
   713 
       
   714 def timesince(value, arg=None):
       
   715     """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
       
   716     from django.utils.timesince import timesince
       
   717     if not value:
       
   718         return u''
       
   719     try:
       
   720         if arg:
       
   721             return timesince(value, arg)
       
   722         return timesince(value)
       
   723     except (ValueError, TypeError):
       
   724         return u''
       
   725 timesince.is_safe = False
       
   726 
       
   727 def timeuntil(value, arg=None):
       
   728     """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
       
   729     from django.utils.timesince import timeuntil
       
   730     from datetime import datetime
       
   731     if not value:
       
   732         return u''
       
   733     try:
       
   734         return timeuntil(value, arg)
       
   735     except (ValueError, TypeError):
       
   736         return u''
       
   737 timeuntil.is_safe = False
       
   738 
       
   739 ###################
       
   740 # LOGIC           #
       
   741 ###################
       
   742 
       
   743 def default(value, arg):
       
   744     """If value is unavailable, use given default."""
       
   745     return value or arg
       
   746 default.is_safe = False
       
   747 
       
   748 def default_if_none(value, arg):
       
   749     """If value is None, use given default."""
       
   750     if value is None:
       
   751         return arg
       
   752     return value
       
   753 default_if_none.is_safe = False
       
   754 
       
   755 def divisibleby(value, arg):
       
   756     """Returns True if the value is devisible by the argument."""
       
   757     return int(value) % int(arg) == 0
       
   758 divisibleby.is_safe = False
       
   759 
       
   760 def yesno(value, arg=None):
       
   761     """
       
   762     Given a string mapping values for true, false and (optionally) None,
       
   763     returns one of those strings accoding to the value:
       
   764 
       
   765     ==========  ======================  ==================================
       
   766     Value       Argument                Outputs
       
   767     ==========  ======================  ==================================
       
   768     ``True``    ``"yeah,no,maybe"``     ``yeah``
       
   769     ``False``   ``"yeah,no,maybe"``     ``no``
       
   770     ``None``    ``"yeah,no,maybe"``     ``maybe``
       
   771     ``None``    ``"yeah,no"``           ``"no"`` (converts None to False
       
   772                                         if no mapping for None is given.
       
   773     ==========  ======================  ==================================
       
   774     """
       
   775     if arg is None:
       
   776         arg = ugettext('yes,no,maybe')
       
   777     bits = arg.split(u',')
       
   778     if len(bits) < 2:
       
   779         return value # Invalid arg.
       
   780     try:
       
   781         yes, no, maybe = bits
       
   782     except ValueError:
       
   783         # Unpack list of wrong size (no "maybe" value provided).
       
   784         yes, no, maybe = bits[0], bits[1], bits[1]
       
   785     if value is None:
       
   786         return maybe
       
   787     if value:
       
   788         return yes
       
   789     return no
       
   790 yesno.is_safe = False
       
   791 
       
   792 ###################
       
   793 # MISC            #
       
   794 ###################
       
   795 
       
   796 def filesizeformat(bytes):
       
   797     """
       
   798     Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
       
   799     102 bytes, etc).
       
   800     """
       
   801     try:
       
   802         bytes = float(bytes)
       
   803     except (TypeError,ValueError,UnicodeDecodeError):
       
   804         return u"0 bytes"
       
   805 
       
   806     if bytes < 1024:
       
   807         return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
       
   808     if bytes < 1024 * 1024:
       
   809         return ugettext("%.1f KB") % (bytes / 1024)
       
   810     if bytes < 1024 * 1024 * 1024:
       
   811         return ugettext("%.1f MB") % (bytes / (1024 * 1024))
       
   812     return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
       
   813 filesizeformat.is_safe = True
       
   814 
       
   815 def pluralize(value, arg=u's'):
       
   816     """
       
   817     Returns a plural suffix if the value is not 1. By default, 's' is used as
       
   818     the suffix:
       
   819 
       
   820     * If value is 0, vote{{ value|pluralize }} displays "0 votes".
       
   821     * If value is 1, vote{{ value|pluralize }} displays "1 vote".
       
   822     * If value is 2, vote{{ value|pluralize }} displays "2 votes".
       
   823 
       
   824     If an argument is provided, that string is used instead:
       
   825 
       
   826     * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
       
   827     * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
       
   828     * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
       
   829 
       
   830     If the provided argument contains a comma, the text before the comma is
       
   831     used for the singular case and the text after the comma is used for the
       
   832     plural case:
       
   833 
       
   834     * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
       
   835     * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
       
   836     * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
       
   837     """
       
   838     if not u',' in arg:
       
   839         arg = u',' + arg
       
   840     bits = arg.split(u',')
       
   841     if len(bits) > 2:
       
   842         return u''
       
   843     singular_suffix, plural_suffix = bits[:2]
       
   844 
       
   845     try:
       
   846         if int(value) != 1:
       
   847             return plural_suffix
       
   848     except ValueError: # Invalid string that's not a number.
       
   849         pass
       
   850     except TypeError: # Value isn't a string or a number; maybe it's a list?
       
   851         try:
       
   852             if len(value) != 1:
       
   853                 return plural_suffix
       
   854         except TypeError: # len() of unsized object.
       
   855             pass
       
   856     return singular_suffix
       
   857 pluralize.is_safe = False
       
   858 
       
   859 def phone2numeric(value):
       
   860     """Takes a phone number and converts it in to its numerical equivalent."""
       
   861     from django.utils.text import phone2numeric
       
   862     return phone2numeric(value)
       
   863 phone2numeric.is_safe = True
       
   864 
       
   865 def pprint(value):
       
   866     """A wrapper around pprint.pprint -- for debugging, really."""
       
   867     from pprint import pformat
       
   868     try:
       
   869         return pformat(value)
       
   870     except Exception, e:
       
   871         return u"Error in formatting: %s" % force_unicode(e, errors="replace")
       
   872 pprint.is_safe = True
       
   873 
       
   874 # Syntax: register.filter(name of filter, callback)
       
   875 register.filter(add)
       
   876 register.filter(addslashes)
       
   877 register.filter(capfirst)
       
   878 register.filter(center)
       
   879 register.filter(cut)
       
   880 register.filter(date)
       
   881 register.filter(default)
       
   882 register.filter(default_if_none)
       
   883 register.filter(dictsort)
       
   884 register.filter(dictsortreversed)
       
   885 register.filter(divisibleby)
       
   886 register.filter(escape)
       
   887 register.filter(escapejs)
       
   888 register.filter(filesizeformat)
       
   889 register.filter(first)
       
   890 register.filter(fix_ampersands)
       
   891 register.filter(floatformat)
       
   892 register.filter(force_escape)
       
   893 register.filter(get_digit)
       
   894 register.filter(iriencode)
       
   895 register.filter(join)
       
   896 register.filter(last)
       
   897 register.filter(length)
       
   898 register.filter(length_is)
       
   899 register.filter(linebreaks)
       
   900 register.filter(linebreaksbr)
       
   901 register.filter(linenumbers)
       
   902 register.filter(ljust)
       
   903 register.filter(lower)
       
   904 register.filter(make_list)
       
   905 register.filter(phone2numeric)
       
   906 register.filter(pluralize)
       
   907 register.filter(pprint)
       
   908 register.filter(removetags)
       
   909 register.filter(random)
       
   910 register.filter(rjust)
       
   911 register.filter(safe)
       
   912 register.filter(safeseq)
       
   913 register.filter('slice', slice_)
       
   914 register.filter(slugify)
       
   915 register.filter(stringformat)
       
   916 register.filter(striptags)
       
   917 register.filter(time)
       
   918 register.filter(timesince)
       
   919 register.filter(timeuntil)
       
   920 register.filter(title)
       
   921 register.filter(truncatewords)
       
   922 register.filter(truncatewords_html)
       
   923 register.filter(unordered_list)
       
   924 register.filter(upper)
       
   925 register.filter(urlencode)
       
   926 register.filter(urlize)
       
   927 register.filter(urlizetrunc)
       
   928 register.filter(wordcount)
       
   929 register.filter(wordwrap)
       
   930 register.filter(yesno)