Fixed #17492 -- Allow reversal of named backreferences. Thanks nate_b

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17336 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Chris Beaven 2012-01-03 22:49:13 +00:00
parent e5719b203c
commit e52c52ea13
5 changed files with 66 additions and 7 deletions

View File

@ -134,18 +134,28 @@ def normalize(pattern):
raise ValueError("Non-reversible reg-exp portion: '(?%s'" % ch) raise ValueError("Non-reversible reg-exp portion: '(?%s'" % ch)
else: else:
ch, escaped = pattern_iter.next() ch, escaped = pattern_iter.next()
if ch != '<': if ch not in ('<', '='):
raise ValueError("Non-reversible reg-exp portion: '(?P%s'" % ch) raise ValueError("Non-reversible reg-exp portion: '(?P%s'" % ch)
# We are in a named capturing group. Extra the name and # We are in a named capturing group. Extra the name and
# then skip to the end. # then skip to the end.
if ch == '<':
terminal_char = '>'
# We are in a named backreference.
else:
terminal_char = ')'
name = [] name = []
ch, escaped = pattern_iter.next() ch, escaped = pattern_iter.next()
while ch != '>': while ch != terminal_char:
name.append(ch) name.append(ch)
ch, escaped = pattern_iter.next() ch, escaped = pattern_iter.next()
param = ''.join(name) param = ''.join(name)
result.append(Group(((u"%%(%s)s" % param), param))) # Named backreferences have already consumed the
walk_to_end(ch, pattern_iter) # parenthesis.
if terminal_char != ')':
result.append(Group(((u"%%(%s)s" % param), param)))
walk_to_end(ch, pattern_iter)
else:
result.append(Group(((u"%%(%s)s" % param), None)))
elif ch in "*?+{": elif ch in "*?+{":
# Quanitifers affect the previous item in the result list. # Quanitifers affect the previous item in the result list.
count, ch = get_quantifier(ch, pattern_iter) count, ch = get_quantifier(ch, pattern_iter)

View File

@ -76,6 +76,8 @@ test_data = (
('people', NoReverseMatch, [], {'name': 'name with spaces'}), ('people', NoReverseMatch, [], {'name': 'name with spaces'}),
('people2', '/people/name/', [], {}), ('people2', '/people/name/', [], {}),
('people2a', '/people/name/fred/', ['fred'], {}), ('people2a', '/people/name/fred/', ['fred'], {}),
('people_backref', '/people/nate-nate/', ['nate'], {}),
('people_backref', '/people/nate-nate/', [], {'name': 'nate'}),
('optional', '/optional/fred/', [], {'name': 'fred'}), ('optional', '/optional/fred/', [], {'name': 'fred'}),
('optional', '/optional/fred/', ['fred'], {}), ('optional', '/optional/fred/', ['fred'], {}),
('hardcoded', '/hardcoded/', [], {}), ('hardcoded', '/hardcoded/', [], {}),

View File

@ -22,6 +22,7 @@ urlpatterns = patterns('',
url(r'^people/(?P<name>\w+)/$', empty_view, name="people"), url(r'^people/(?P<name>\w+)/$', empty_view, name="people"),
url(r'^people/(?:name/)', empty_view, name="people2"), url(r'^people/(?:name/)', empty_view, name="people2"),
url(r'^people/(?:name/(\w+)/)?', empty_view, name="people2a"), url(r'^people/(?:name/(\w+)/)?', empty_view, name="people2a"),
url(r'^people/(?P<name>\w+)-(?P=name)/$', empty_view, name="people_backref"),
url(r'^optional/(?P<name>.*)/(?:.+/)?', empty_view, name="optional"), url(r'^optional/(?P<name>.*)/(?:.+/)?', empty_view, name="optional"),
url(r'^hardcoded/$', empty_view, name="hardcoded"), url(r'^hardcoded/$', empty_view, name="hardcoded"),
url(r'^hardcoded/doc\.pdf$', empty_view, name="hardcoded2"), url(r'^hardcoded/doc\.pdf$', empty_view, name="hardcoded2"),
@ -65,7 +66,4 @@ urlpatterns = patterns('',
(r'defaults_view2/(?P<arg1>\d+)/', 'defaults_view', {'arg2': 2}, 'defaults'), (r'defaults_view2/(?P<arg1>\d+)/', 'defaults_view', {'arg2': 2}, 'defaults'),
url('^includes/', include(other_patterns)), url('^includes/', include(other_patterns)),
) )

View File

@ -0,0 +1,48 @@
from django.utils import regex_helper
from django.utils import unittest
class NormalizeTests(unittest.TestCase):
def test_empty(self):
pattern = r""
expected = [(u'', [])]
result = regex_helper.normalize(pattern)
self.assertEqual(result, expected)
def test_escape(self):
pattern = r"\\\^\$\.\|\?\*\+\(\)\["
expected = [(u'\\^$.|?*+()[', [])]
result = regex_helper.normalize(pattern)
self.assertEqual(result, expected)
def test_group_positional(self):
pattern = r"(.*)-(.+)"
expected = [(u'%(_0)s-%(_1)s', ['_0', '_1'])]
result = regex_helper.normalize(pattern)
self.assertEqual(result, expected)
def test_group_ignored(self):
pattern = r"(?i)(?L)(?m)(?s)(?u)(?#)"
expected = [(u'', [])]
result = regex_helper.normalize(pattern)
self.assertEqual(result, expected)
def test_group_noncapturing(self):
pattern = r"(?:non-capturing)"
expected = [(u'non-capturing', [])]
result = regex_helper.normalize(pattern)
self.assertEqual(result, expected)
def test_group_named(self):
pattern = r"(?P<first_group_name>.*)-(?P<second_group_name>.*)"
expected = [(u'%(first_group_name)s-%(second_group_name)s',
['first_group_name', 'second_group_name'])]
result = regex_helper.normalize(pattern)
self.assertEqual(result, expected)
def test_group_backreference(self):
pattern = r"(?P<first_group_name>.*)-(?P=first_group_name)"
expected = [(u'%(first_group_name)s-%(first_group_name)s',
['first_group_name'])]
result = regex_helper.normalize(pattern)
self.assertEqual(result, expected)

View File

@ -25,3 +25,4 @@ from .ipv6 import TestUtilsIPv6
from .timezone import TimezoneTests from .timezone import TimezoneTests
from .crypto import TestUtilsCryptoPBKDF2 from .crypto import TestUtilsCryptoPBKDF2
from .archive import TestZip, TestTar, TestGzipTar, TestBzip2Tar from .archive import TestZip, TestTar, TestGzipTar, TestBzip2Tar
from .regex_helper import NormalizeTests