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