Django

Code

Ticket #7295: 7295.2.diff

File 7295.2.diff, 11.2 kB (added by akaihola, 2 years ago)

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

  • django/template/__init__.py

    old new  
    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

    old new  
    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)