Ticket #7295: 7295.2.diff

File 7295.2.diff, 11.2 KB (added by akaihola, 7 years ago)

Patch now handles un-escaping correctly and optimizes numeric literals.

  • django/template/__init__.py

     
    5454from django.template.context import Context, RequestContext, ContextPopException
    5555from django.utils.itercompat import is_iterable
    5656from django.utils.functional import curry, Promise
    57 from django.utils.text import smart_split
     57from django.utils.text import smart_split, unescape_string_literal
    5858from django.utils.encoding import smart_unicode, force_unicode
    5959from django.utils.translation import ugettext as _
    6060from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     
    431431            self.pointer = i
    432432            return s
    433433
     434constant_string = r"""
     435(?:%(i18n_open)s%(strdq)s%(i18n_close)s|
     436%(i18n_open)s%(strsq)s%(i18n_close)s|
     437%(strdq)s|
     438%(strsq)s)|
     439%(num)s
     440""" % {
     441    'strdq': r'''"[^"\\]*(?:\\.[^"\\]*)*"''', # double-quoted string
     442    'strsq': r"""'[^'\\]*(?:\\.[^'\\]*)*'""", # single-quoted string
     443    'num': r'[-+\.]?\d[\d\.e]*', # numeric constant
     444    'i18n_open' : re.escape("_("),
     445    'i18n_close' : re.escape(")"),
     446  }
     447constant_string = constant_string.replace("\n", "")
     448
    434449filter_raw_string = r"""
    435 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
    436 ^"(?P<constant>%(str)s)"|
     450^(?P<constant>%(constant)s)|
    437451^(?P<var>[%(var_chars)s]+)|
    438452 (?:%(filter_sep)s
    439453     (?P<filter_name>\w+)
    440454         (?:%(arg_sep)s
    441455             (?:
    442               %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
    443               "(?P<constant_arg>%(str)s)"|
     456              (?P<constant_arg>%(constant)s)|
    444457              (?P<var_arg>[%(var_chars)s]+)
    445458             )
    446459         )?
    447460 )""" % {
    448     'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
     461    'constant': constant_string,
    449462    'var_chars': "\w\." ,
    450463    'filter_sep': re.escape(FILTER_SEPARATOR),
    451464    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
    452     'i18n_open' : re.escape("_("),
    453     'i18n_close' : re.escape(")"),
    454465  }
    455466
    456467filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
    457468filter_re = re.compile(filter_raw_string, re.UNICODE)
    458469
    459470class FilterExpression(object):
    460     """
     471    r"""
    461472    Parses a variable token and its optional filters (all as a single string),
    462473    and return a list of tuples of the filter name and arguments.
    463474    Sample:
     
    469480        >>> fe.var
    470481        'variable'
    471482
     483        >>> c = {'article': {'section': u'News'}}
     484        >>> def fe_test(s): return FilterExpression(s, p).resolve(c)
     485        >>> fe_test('article.section')
     486        u'News'
     487        >>> fe_test('article.section|upper')
     488        u'NEWS'
     489        >>> fe_test(u'"News"')
     490        u'News'
     491        >>> fe_test(u"'News'")
     492        u'News'
     493        >>> fe_test(ur'"Some \"Good\" News"')
     494        u'Some "Good" News'
     495        >>> fe_test(ur"'Some \'Bad\' News'")
     496        u"Some 'Bad' News"
     497
     498        >>> fe = FilterExpression(ur'"Some \"Good\" News"', p)
     499        >>> fe.filters
     500        []
     501        >>> fe.var
     502        u'Some "Good" News'
     503
    472504    This class should never be instantiated outside of the
    473505    get_filters_from_token helper function.
     506
     507    The filter_re regular expression is responsible for tokenizing the filter
     508    expression::
     509
     510        >>> def fre_test(s):
     511        ...     print '|'.join(','.join("%s=%s"%(key, val) for key, val in sorted(match.groupdict().items()) if val is not None) for match in filter_re.finditer(s))
     512        >>> fre_test('myvar')
     513        var=myvar
     514        >>> fre_test('myvar|myfilt:myarg')
     515        var=myvar|filter_name=myfilt,var_arg=myarg
     516        >>> fre_test(r'"Some \"Good\" News"|myfilt:"Some \"Bad\" News"')
     517        constant="Some \"Good\" News"|constant_arg="Some \"Bad\" News",filter_name=myfilt
     518        >>> fre_test(r"'More \'Good\' News'|myfilt:'More \'Bad\' News'")
     519        constant='More \'Good\' News'|constant_arg='More \'Bad\' News',filter_name=myfilt
     520
     521
     522        >>> p.compile_filter(r'"Some \"Good\" News"').resolve({})
     523        'Some "Good" News'
    474524    """
    475525    def __init__(self, token, parser):
    476526        self.token = token
     
    484534                raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
    485535                                           (token[:upto], token[upto:start], token[start:]))
    486536            if var == None:
    487                 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
    488                 if i18n_constant:
    489                     var = '"%s"' %  _(i18n_constant.replace(r'\"', '"'))
    490                 elif constant:
    491                     var = '"%s"' % constant.replace(r'\"', '"')
    492                 upto = match.end()
    493                 if var == None:
     537                var, constant = match.group("var", "constant")
     538                if constant:
     539                    try:
     540                        self.var = Variable(constant).resolve({})
     541                    except VariableDoesNotExist:
     542                        self.var = None
     543                elif var == None:
    494544                    raise TemplateSyntaxError("Could not find variable at start of %s" % token)
    495545                elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
    496546                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
     547                else:
     548                    self.var = Variable(var)
     549                upto = match.end()
    497550            else:
    498551                filter_name = match.group("filter_name")
    499552                args = []
    500                 constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
    501                 if i18n_arg:
    502                     args.append((False, _(i18n_arg.replace(r'\"', '"'))))
    503                 elif constant_arg is not None:
    504                     args.append((False, constant_arg.replace(r'\"', '"')))
     553                constant_arg, var_arg = match.group("constant_arg", "var_arg")
     554                if constant_arg:
     555                    args.append((False, Variable(constant_arg).resolve({})))
    505556                elif var_arg:
    506557                    args.append((True, Variable(var_arg)))
    507558                filter_func = parser.find_filter(filter_name)
     
    511562        if upto != len(token):
    512563            raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
    513564        self.filters = filters
    514         self.var = Variable(var)
    515565
    516566    def resolve(self, context, ignore_failures=False):
    517         try:
    518             obj = self.var.resolve(context)
    519         except VariableDoesNotExist:
    520             if ignore_failures:
    521                 obj = None
    522             else:
    523                 if settings.TEMPLATE_STRING_IF_INVALID:
    524                     global invalid_var_format_string
    525                     if invalid_var_format_string is None:
    526                         invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
    527                     if invalid_var_format_string:
    528                         return settings.TEMPLATE_STRING_IF_INVALID % self.var
    529                     return settings.TEMPLATE_STRING_IF_INVALID
     567        if isinstance(self.var, Variable):
     568            try:
     569                obj = self.var.resolve(context)
     570            except VariableDoesNotExist:
     571                if ignore_failures:
     572                    obj = None
    530573                else:
    531                     obj = settings.TEMPLATE_STRING_IF_INVALID
     574                    if settings.TEMPLATE_STRING_IF_INVALID:
     575                        global invalid_var_format_string
     576                        if invalid_var_format_string is None:
     577                            invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
     578                        if invalid_var_format_string:
     579                            return settings.TEMPLATE_STRING_IF_INVALID % self.var
     580                        return settings.TEMPLATE_STRING_IF_INVALID
     581                    else:
     582                        obj = settings.TEMPLATE_STRING_IF_INVALID
     583        else:
     584            obj = self.var
    532585        for func, args in self.filters:
    533586            arg_vals = []
    534587            for lookup, arg in args:
     
    593646    return Variable(path).resolve(context)
    594647
    595648class Variable(object):
    596     """
     649    r"""
    597650    A template variable, resolvable against a given context. The variable may be
    598651    a hard-coded string (if it begins and ends with single or double quote
    599652    marks)::
     
    609662        >>> c.article.section = 'News'
    610663        >>> Variable('article.section').resolve(c)
    611664        u'News'
     665        >>> Variable(u'"News"').resolve(c)
     666        u'News'
     667        >>> Variable(u"'News'").resolve(c)
     668        u'News'
    612669
    613670    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
     671
     672    Translated strings are also handled correctly::
     673
     674        >>> Variable('_(article.section)').resolve(c)
     675        u'News'
     676        >>> Variable('_("Good News")').resolve(c)
     677        u'Good News'
     678        >>> Variable("_('Better News')").resolve(c)
     679        u'Better News'
     680
     681    Escaped quotes work correctly as well::
     682
     683        >>> Variable(ur'"Some \"Good\" News"').resolve(c)
     684        u'Some "Good" News'
     685        >>> Variable(ur"'Some \'Better\' News'").resolve(c)
     686        u"Some 'Better' News"
    614687    """
    615688
    616689    def __init__(self, var):
     
    645718                var = var[2:-1]
    646719            # If it's wrapped with quotes (single or double), then
    647720            # we're also dealing with a literal.
    648             if var[0] in "\"'" and var[0] == var[-1]:
    649                 self.literal = mark_safe(var[1:-1])
    650             else:
     721            try:
     722                self.literal = mark_safe(unescape_string_literal(var))
     723            except ValueError:
    651724                # Otherwise we'll set self.lookups so that resolve() knows we're
    652725                # dealing with a bonafide variable
    653726                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
  • django/utils/text.py

     
    209209    """
    210210    text = force_unicode(text)
    211211    for bit in smart_split_re.finditer(text):
    212         bit = bit.group(0)
    213         if bit[0] == '"' and bit[-1] == '"':
    214             yield '"' + bit[1:-1].replace('\\"', '"').replace('\\\\', '\\') + '"'
    215         elif bit[0] == "'" and bit[-1] == "'":
    216             yield "'" + bit[1:-1].replace("\\'", "'").replace("\\\\", "\\") + "'"
    217         else:
    218             yield bit
     212        yield bit.group(0)
    219213smart_split = allow_lazy(smart_split, unicode)
    220214
     215def unescape_string_literal(s):
     216    r"""
     217    Convert quoted string literals to unquoted strings with escaped quotes and
     218    backslashes unquoted::
     219
     220        >>> unescape_string_literal('"abc"')
     221        'abc'
     222        >>> unescape_string_literal("'abc'")
     223        'abc'
     224        >>> unescape_string_literal('"a \"bc\""')
     225        'a "bc"'
     226        >>> unescape_string_literal("'\'ab\' c'")
     227        "'ab' c"
     228
     229    Would this function be more appropriate in the django.utils.text module?
     230    """
     231    if s[0] not in "\"'" or s[-1] != s[0]:
     232        raise ValueError("Not a string literal: %r" % s)
     233    quote = s[0]
     234    return s[1:-1].replace(r'\%s' % quote, quote).replace(r'\\', '\\')
     235unescape_string_literal = allow_lazy(unescape_string_literal)
Back to Top