| 1 | Index: /home/tobryan1/workspace/django/django/core/urlresolvers.py
|
|---|
| 2 | ===================================================================
|
|---|
| 3 | --- /home/tobryan1/workspace/django/django/core/urlresolvers.py (revision 5478)
|
|---|
| 4 | +++ /home/tobryan1/workspace/django/django/core/urlresolvers.py (working copy)
|
|---|
| 5 | @@ -27,6 +27,18 @@
|
|---|
| 6 | return callback, ''
|
|---|
| 7 | return callback[:dot], callback[dot+1:]
|
|---|
| 8 |
|
|---|
| 9 | +ESCAPE_CHARS = re.compile(r'([(){}|[\].^$*+?\\])')
|
|---|
| 10 | +
|
|---|
| 11 | +def escape(s):
|
|---|
| 12 | + """
|
|---|
| 13 | + Escapes the characters that will get messed up later
|
|---|
| 14 | + """
|
|---|
| 15 | + return ESCAPE_CHARS.sub(r'\\\1', s)
|
|---|
| 16 | +
|
|---|
| 17 | +
|
|---|
| 18 | +CHAR_CLASS_NEEDS_BACKSLASH = re.compile(r'\[([(){}|[\].^$*+?])\]')
|
|---|
| 19 | +CHAR_CLASS_NO_BACKSLASH = re.compile(r'\[(.)\]')
|
|---|
| 20 | +
|
|---|
| 21 | def reverse_helper(regex, *args, **kwargs):
|
|---|
| 22 | """
|
|---|
| 23 | Does a "reverse" lookup -- returns the URL for the given args/kwargs.
|
|---|
| 24 | @@ -39,12 +51,74 @@
|
|---|
| 25 | 'places/3/'
|
|---|
| 26 | >>> reverse_helper(re.compile('^people/(?P<state>\w\w)/(\w+)/$'), 'adrian', state='il')
|
|---|
| 27 | 'people/il/adrian/'
|
|---|
| 28 | -
|
|---|
| 29 | + >>> reverse_helper(re.compile(r'^prices/less_than_\$(?P<price>\d+)/$'), price='10')
|
|---|
| 30 | + 'prices/less_than_$10/'
|
|---|
| 31 | + >>> reverse_helper(re.compile(r'^prices/less_than_[$](?P<price>\d+)/$'), price='10')
|
|---|
| 32 | + 'prices/less_than_$10/'
|
|---|
| 33 | + >>> reverse_helper(re.compile(r'^headlines/(?P<year>\d+)\.(?P<month>\d+)\.(?P<day>\d+)/$'), year=2007, month=5, day=21)
|
|---|
| 34 | + 'headlines/2007.5.21/'
|
|---|
| 35 | + >>> reverse_helper(re.compile(r'^priests/(?P<name>\w+)\+/$'), name='maynard')
|
|---|
| 36 | + 'priests/maynard+/'
|
|---|
| 37 | + >>> reverse_helper(re.compile(r'^windows_path/(?P<drive_name>[A-Z]):\\\\(?P<path>.+)/$'), drive_name='C', path=r'Documents and Settings\\spam')
|
|---|
| 38 | + 'windows_path/C:\\\\Documents and Settings\\\\spam/'
|
|---|
| 39 | + >>> reverse_helper(re.compile(r'\\Aexpr\\\\b/expr2\\b\\\\Z/$'))
|
|---|
| 40 | + 'expr\\\\b/expr2\\\\Z/'
|
|---|
| 41 | + >>> reverse_helper(re.compile(r'^(?P<name>[^/]+)/\\d+/$'), name='john')
|
|---|
| 42 | + Traceback (most recent call last):
|
|---|
| 43 | + ...
|
|---|
| 44 | + NoReverseMatch: \d must be replaced by an argument in reverse lookup
|
|---|
| 45 | +
|
|---|
| 46 | Raises NoReverseMatch if the args/kwargs aren't valid for the regex.
|
|---|
| 47 | - """
|
|---|
| 48 | + """
|
|---|
| 49 | # TODO: Handle nested parenthesis in the following regex.
|
|---|
| 50 | - result = re.sub(r'\(([^)]+)\)', MatchChecker(args, kwargs), regex.pattern)
|
|---|
| 51 | - return result.replace('^', '').replace('$', '')
|
|---|
| 52 | + result = re.sub(r'\(([^)]+)\)', lambda m: escape(MatchChecker(args, kwargs)(m)), regex.pattern)
|
|---|
| 53 | + #print result
|
|---|
| 54 | + # TODO: octal characters make things even more complicated
|
|---|
| 55 | + # you can use a single character class to avoid escaping, e.g. [$] or [.].
|
|---|
| 56 | + # normalize to backslash followed by character
|
|---|
| 57 | + result = CHAR_CLASS_NEEDS_BACKSLASH.sub(r'\\\1', result)
|
|---|
| 58 | + #print result
|
|---|
| 59 | + # you can put a single character in brackets (though why you would is
|
|---|
| 60 | + # beyond me); removes the brackets
|
|---|
| 61 | + result = CHAR_CLASS_NO_BACKSLASH.sub(r'\1', result)
|
|---|
| 62 | + #print result
|
|---|
| 63 | + # \A, \Z, \b, and \B match the empty string and should be removed, but
|
|---|
| 64 | + # only if preceded by an odd number of backslashes, otherwise the backslash
|
|---|
| 65 | + # right before is actually the second backslash in the backslash escape \\
|
|---|
| 66 | + def delete_if_slashes_odd(m):
|
|---|
| 67 | + odd_slash_match = re.match(r'^(\\\\)*\\$', m.group('slashes'))
|
|---|
| 68 | + if odd_slash_match:
|
|---|
| 69 | + return odd_slash_match.group(1)
|
|---|
| 70 | + else:
|
|---|
| 71 | + return m.group(0)
|
|---|
| 72 | + result = re.sub(r'(?P<slashes>\\+)(?P<char>[AbBZ])', delete_if_slashes_odd, result)
|
|---|
| 73 | + #print result
|
|---|
| 74 | + # ^ and $ match the empty string and should be removed, but only if
|
|---|
| 75 | + # preceded by an even number of backslashes (including none), otherwise the
|
|---|
| 76 | + # backslash right before is escaping the literal \^ or \$
|
|---|
| 77 | + def delete_if_slashes_even(m):
|
|---|
| 78 | + even_slash_match = re.match(r'^(\\\\)*$', m.group('slashes'))
|
|---|
| 79 | + if even_slash_match:
|
|---|
| 80 | + return m.group('slashes')
|
|---|
| 81 | + else:
|
|---|
| 82 | + return m.group(0)
|
|---|
| 83 | + result = re.sub(r'(?P<slashes>\\*)(?P<char>[$^])', delete_if_slashes_even, result)
|
|---|
| 84 | + #print result
|
|---|
| 85 | + # many characters are preceded by backslashes in regexes if the literal
|
|---|
| 86 | + # character is meant; as we go to a string, the backslash should go away.
|
|---|
| 87 | + # We should never find character classes that don't have a single
|
|---|
| 88 | + # replacement character. These are \number (the group matching expression),
|
|---|
| 89 | + # \d, \D, \s, \S, \w, and \W. If we find these at this point, we raise
|
|---|
| 90 | + # an exception.
|
|---|
| 91 | + def drop_backslash_if_valid(m):
|
|---|
| 92 | + char = m.group(1)
|
|---|
| 93 | + if re.match(r'[\ddDsSwW]', char):
|
|---|
| 94 | + raise NoReverseMatch(r'\%s must be replaced by an argument in reverse lookup' % char)
|
|---|
| 95 | + else:
|
|---|
| 96 | + return char
|
|---|
| 97 | + result = re.sub(r'\\([[\]{}()^$*+?.\\|\ddDsSwW])', drop_backslash_if_valid, result)
|
|---|
| 98 | + #print result
|
|---|
| 99 | + return result
|
|---|
| 100 |
|
|---|
| 101 | class MatchChecker(object):
|
|---|
| 102 | "Class used in reverse RegexURLPattern lookup."
|
|---|