Fixed #28135 -- Made simplify_regex() handle non-capturing groups.

This commit is contained in:
Ayush Joshi 2022-01-14 10:03:55 +05:30 committed by Mariusz Felisiak
parent fdfa97fb16
commit 0a17666045
3 changed files with 27 additions and 2 deletions

View File

@ -137,9 +137,10 @@ if docutils_is_available:
for name, urlbase in ROLES.items(): for name, urlbase in ROLES.items():
create_reference_role(name, urlbase) create_reference_role(name, urlbase)
# Match the beginning of a named or unnamed group. # Match the beginning of a named, unnamed, or non-capturing groups.
named_group_matcher = _lazy_re_compile(r'\(\?P(<\w+>)') named_group_matcher = _lazy_re_compile(r'\(\?P(<\w+>)')
unnamed_group_matcher = _lazy_re_compile(r'\(') unnamed_group_matcher = _lazy_re_compile(r'\(')
non_capturing_group_matcher = _lazy_re_compile(r'\(\?\:')
def replace_metacharacters(pattern): def replace_metacharacters(pattern):
@ -210,3 +211,18 @@ def replace_unnamed_groups(pattern):
final_pattern += pattern[:start] + '<var>' final_pattern += pattern[:start] + '<var>'
prev_end = end prev_end = end
return final_pattern + pattern[prev_end:] return final_pattern + pattern[prev_end:]
def remove_non_capturing_groups(pattern):
r"""
Find non-capturing groups in the given `pattern` and remove them, e.g.
1. (?P<a>\w+)/b/(?:\w+)c(?:\w+) => (?P<a>\\w+)/b/c
2. ^(?:\w+(?:\w+))a => ^a
3. ^a(?:\w+)/b(?:\w+) => ^a/b
"""
group_start_end_indices = _find_groups(pattern, non_capturing_group_matcher)
final_pattern, prev_end = '', None
for start, end, _ in group_start_end_indices:
final_pattern += pattern[prev_end:start]
prev_end = end
return final_pattern + pattern[prev_end:]

View File

@ -8,7 +8,8 @@ from django.contrib import admin
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.admindocs import utils from django.contrib.admindocs import utils
from django.contrib.admindocs.utils import ( from django.contrib.admindocs.utils import (
replace_metacharacters, replace_named_groups, replace_unnamed_groups, remove_non_capturing_groups, replace_metacharacters, replace_named_groups,
replace_unnamed_groups,
) )
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.db import models from django.db import models
@ -410,6 +411,7 @@ def simplify_regex(pattern):
example, turn "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$" example, turn "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$"
into "/<sport_slug>/athletes/<athlete_slug>/". into "/<sport_slug>/athletes/<athlete_slug>/".
""" """
pattern = remove_non_capturing_groups(pattern)
pattern = replace_named_groups(pattern) pattern = replace_named_groups(pattern)
pattern = replace_unnamed_groups(pattern) pattern = replace_unnamed_groups(pattern)
pattern = replace_metacharacters(pattern) pattern = replace_metacharacters(pattern)

View File

@ -397,6 +397,13 @@ class AdminDocViewFunctionsTests(SimpleTestCase):
(r'^(?P<a>(x|y))/b/(?P<c>\w+)', '/<a>/b/<c>'), (r'^(?P<a>(x|y))/b/(?P<c>\w+)', '/<a>/b/<c>'),
(r'^(?P<a>(x|y))/b/(?P<c>\w+)ab', '/<a>/b/<c>ab'), (r'^(?P<a>(x|y))/b/(?P<c>\w+)ab', '/<a>/b/<c>ab'),
(r'^(?P<a>(x|y)(\(|\)))/b/(?P<c>\w+)ab', '/<a>/b/<c>ab'), (r'^(?P<a>(x|y)(\(|\)))/b/(?P<c>\w+)ab', '/<a>/b/<c>ab'),
# Non-capturing groups.
(r'^a(?:\w+)b', '/ab'),
(r'^a(?:(x|y))', '/a'),
(r'^(?:\w+(?:\w+))a', '/a'),
(r'^a(?:\w+)/b(?:\w+)', '/a/b'),
(r'(?P<a>\w+)/b/(?:\w+)c(?:\w+)', '/<a>/b/c'),
(r'(?P<a>\w+)/b/(\w+)/(?:\w+)c(?:\w+)', '/<a>/b/<var>/c'),
# Single and repeated metacharacters. # Single and repeated metacharacters.
(r'^a', '/a'), (r'^a', '/a'),
(r'^^a', '/a'), (r'^^a', '/a'),