diff --git a/django/utils/regex_helper.py b/django/utils/regex_helper.py index b11fe960bb..ab101e8c32 100644 --- a/django/utils/regex_helper.py +++ b/django/utils/regex_helper.py @@ -134,18 +134,28 @@ def normalize(pattern): raise ValueError("Non-reversible reg-exp portion: '(?%s'" % ch) else: ch, escaped = pattern_iter.next() - if ch != '<': + if ch not in ('<', '='): raise ValueError("Non-reversible reg-exp portion: '(?P%s'" % ch) # We are in a named capturing group. Extra the name and # then skip to the end. + if ch == '<': + terminal_char = '>' + # We are in a named backreference. + else: + terminal_char = ')' name = [] ch, escaped = pattern_iter.next() - while ch != '>': + while ch != terminal_char: name.append(ch) ch, escaped = pattern_iter.next() param = ''.join(name) - result.append(Group(((u"%%(%s)s" % param), param))) - walk_to_end(ch, pattern_iter) + # Named backreferences have already consumed the + # 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 "*?+{": # Quanitifers affect the previous item in the result list. count, ch = get_quantifier(ch, pattern_iter) diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index a5df26f1a1..a1c9244918 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -76,6 +76,8 @@ test_data = ( ('people', NoReverseMatch, [], {'name': 'name with spaces'}), ('people2', '/people/name/', [], {}), ('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/', ['fred'], {}), ('hardcoded', '/hardcoded/', [], {}), diff --git a/tests/regressiontests/urlpatterns_reverse/urls.py b/tests/regressiontests/urlpatterns_reverse/urls.py index 5bde2b0a7e..1d4ae73c67 100644 --- a/tests/regressiontests/urlpatterns_reverse/urls.py +++ b/tests/regressiontests/urlpatterns_reverse/urls.py @@ -22,6 +22,7 @@ urlpatterns = patterns('', url(r'^people/(?P\w+)/$', empty_view, name="people"), url(r'^people/(?:name/)', empty_view, name="people2"), url(r'^people/(?:name/(\w+)/)?', empty_view, name="people2a"), + url(r'^people/(?P\w+)-(?P=name)/$', empty_view, name="people_backref"), url(r'^optional/(?P.*)/(?:.+/)?', empty_view, name="optional"), url(r'^hardcoded/$', empty_view, name="hardcoded"), url(r'^hardcoded/doc\.pdf$', empty_view, name="hardcoded2"), @@ -65,7 +66,4 @@ urlpatterns = patterns('', (r'defaults_view2/(?P\d+)/', 'defaults_view', {'arg2': 2}, 'defaults'), url('^includes/', include(other_patterns)), - ) - - diff --git a/tests/regressiontests/utils/regex_helper.py b/tests/regressiontests/utils/regex_helper.py new file mode 100644 index 0000000000..8dc712f5b9 --- /dev/null +++ b/tests/regressiontests/utils/regex_helper.py @@ -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.*)-(?P.*)" + 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.*)-(?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) diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index f48a4adfee..f5ca06ef1e 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -25,3 +25,4 @@ from .ipv6 import TestUtilsIPv6 from .timezone import TimezoneTests from .crypto import TestUtilsCryptoPBKDF2 from .archive import TestZip, TestTar, TestGzipTar, TestBzip2Tar +from .regex_helper import NormalizeTests