Django

Code

Ticket #7295: 7295.1.diff

File 7295.1.diff, 10.6 kB (added by akaihola, 2 years ago)

Patch for fixing issues with string literals in templates

  • ../django/template/__init__.py

    old new  
    100100# uninitialised. 
    101101invalid_var_format_string = None 
    102102 
     103def unescape_string_literal(s): 
     104    r""" 
     105    Convert quoted string literals to unquoted strings with escaped quotes and 
     106    backslashes unquoted:: 
     107 
     108        >>> unescape_string_literal('"abc"') 
     109        'abc' 
     110        >>> unescape_string_literal("'abc'") 
     111        'abc' 
     112        >>> unescape_string_literal('"a \"bc\""') 
     113        'a "bc"' 
     114        >>> unescape_string_literal("'\'ab\' c'") 
     115        "'ab' c" 
     116 
     117    Would this function be more appropriate in the django.utils.text module? 
     118    """ 
     119    if s[0] not in "\"'" or s[-1] != s[0]: 
     120        raise ValueError("Not a string literal: %r" % s) 
     121    quote = s[0] 
     122    return s[1:-1].replace(r'\%s' % quote, quote).replace(r'\\', '\\') 
     123 
     124def resolve_string_literal(s): 
     125    r""" 
     126    Un-escape, translate and unquote string literals.  Handle single and double 
     127    quoted strings with corresponding quotes and backslashes escaped with 
     128    prepending backslashes:: 
     129 
     130        >>> resolve_string_literal(ur'"Some \"Good\" \\ News"') 
     131        u'Some "Good" \\ News' 
     132        >>> resolve_string_literal(ur"'Some \'Good\' \\ News'") 
     133        u"Some 'Good' \\ News" 
     134        >>> resolve_string_literal(ur'_("Some \"Good\" \\ News")') 
     135        u'Some "Good" \\ News' 
     136        >>> resolve_string_literal(ur"_('Some \'Good\' \\ News')") 
     137        u"Some 'Good' \\ News" 
     138    """ 
     139    if s.startswith('_(') and s.endswith(')'): 
     140        return mark_safe(_(unescape_string_literal(s[2:-1]))) 
     141    return mark_safe(unescape_string_literal(s)) 
     142 
    103143class TemplateSyntaxError(Exception): 
    104144    def __str__(self): 
    105145        try: 
     
    431471            self.pointer = i 
    432472            return s 
    433473 
     474constant_string = r""" 
     475(?:%(i18n_open)s%(strdq)s%(i18n_close)s| 
     476%(i18n_open)s%(strsq)s%(i18n_close)s| 
     477%(strdq)s| 
     478%(strsq)s) 
     479""" % { 
     480    'strdq': r'''"[^"\\]*(?:\\.[^"\\]*)*"''', # double-quoted string 
     481    'strsq': r"""'[^'\\]*(?:\\.[^'\\]*)*'""", # single-quoted string 
     482    'i18n_open' : re.escape("_("), 
     483    'i18n_close' : re.escape(")"), 
     484  } 
     485constant_string = constant_string.replace("\n", "") 
     486 
    434487filter_raw_string = r""" 
    435 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s| 
    436 ^"(?P<constant>%(str)s)"| 
     488^(?P<constant>%(constant)s)| 
    437489^(?P<var>[%(var_chars)s]+)| 
    438490 (?:%(filter_sep)s 
    439491     (?P<filter_name>\w+) 
    440492         (?:%(arg_sep)s 
    441493             (?: 
    442               %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s| 
    443               "(?P<constant_arg>%(str)s)"| 
     494              (?P<constant_arg>%(constant)s)| 
    444495              (?P<var_arg>[%(var_chars)s]+) 
    445496             ) 
    446497         )? 
    447498 )""" % { 
    448     'str': r"""[^"\\]*(?:\\.[^"\\]*)*"""
     499    'constant': constant_string
    449500    'var_chars': "\w\." , 
    450501    'filter_sep': re.escape(FILTER_SEPARATOR), 
    451502    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR), 
    452     'i18n_open' : re.escape("_("), 
    453     'i18n_close' : re.escape(")"), 
    454503  } 
    455504 
    456505filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "") 
    457506filter_re = re.compile(filter_raw_string, re.UNICODE) 
    458507 
    459508class FilterExpression(object): 
    460     """ 
     509    r""" 
    461510    Parses a variable token and its optional filters (all as a single string), 
    462511    and return a list of tuples of the filter name and arguments. 
    463512    Sample: 
     
    469518        >>> fe.var 
    470519        'variable' 
    471520 
     521        >>> c = {'article': {'section': u'News'}} 
     522        >>> def fe_test(s): return FilterExpression(s, p).resolve(c) 
     523        >>> fe_test('article.section') 
     524        u'News' 
     525        >>> fe_test('article.section|upper') 
     526        u'NEWS' 
     527        >>> fe_test(u'"News"') 
     528        u'News' 
     529        >>> fe_test(u"'News'") 
     530        u'News' 
     531        >>> fe_test(ur'"Some \"Good\" News"') 
     532        u'Some "Good" News' 
     533        >>> fe_test(ur"'Some \'Bad\' News'") 
     534        u"Some 'Bad' News" 
     535 
     536        >>> fe = FilterExpression(ur'"Some \"Good\" News"', p) 
     537        >>> fe.filters 
     538        [] 
     539        >>> fe.var 
     540        u'Some "Good" News' 
     541 
    472542    This class should never be instantiated outside of the 
    473543    get_filters_from_token helper function. 
     544 
     545    The filter_re regular expression is responsible for tokenizing the filter 
     546    expression:: 
     547 
     548        >>> def fre_test(s): 
     549        ...     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)) 
     550        >>> fre_test('myvar') 
     551        var=myvar 
     552        >>> fre_test('myvar|myfilt:myarg') 
     553        var=myvar|filter_name=myfilt,var_arg=myarg 
     554        >>> fre_test(r'"Some \"Good\" News"|myfilt:"Some \"Bad\" News"') 
     555        constant="Some \"Good\" News"|constant_arg="Some \"Bad\" News",filter_name=myfilt 
     556        >>> fre_test(r"'More \'Good\' News'|myfilt:'More \'Bad\' News'") 
     557        constant='More \'Good\' News'|constant_arg='More \'Bad\' News',filter_name=myfilt 
    474558    """ 
    475559    def __init__(self, token, parser): 
    476560        self.token = token 
     
    484568                raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \ 
    485569                                           (token[:upto], token[upto:start], token[start:])) 
    486570            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: 
     571                var, constant = match.group("var", "constant") 
     572                if constant: 
     573                    self.var = resolve_string_literal(constant) 
     574                elif var == None: 
    494575                    raise TemplateSyntaxError("Could not find variable at start of %s" % token) 
    495576                elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_': 
    496577                    raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var) 
     578                else: 
     579                    self.var = Variable(var) 
     580                upto = match.end() 
    497581            else: 
    498582                filter_name = match.group("filter_name") 
    499583                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'\"', '"'))) 
     584                constant_arg, var_arg = match.group("constant_arg", "var_arg") 
     585                if constant_arg: 
     586                    args.append((False, resolve_string_literal(constant_arg))) 
    505587                elif var_arg: 
    506588                    args.append((True, Variable(var_arg))) 
    507589                filter_func = parser.find_filter(filter_name) 
     
    511593        if upto != len(token): 
    512594            raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token)) 
    513595        self.filters = filters 
    514         self.var = Variable(var) 
    515596 
    516597    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 
     598        if isinstance(self.var, Variable): 
     599            try: 
     600                obj = self.var.resolve(context) 
     601            except VariableDoesNotExist: 
     602                if ignore_failures: 
     603                    obj = None 
    530604                else: 
    531                     obj = settings.TEMPLATE_STRING_IF_INVALID 
     605                    if settings.TEMPLATE_STRING_IF_INVALID: 
     606                        global invalid_var_format_string 
     607                        if invalid_var_format_string is None: 
     608                            invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID 
     609                        if invalid_var_format_string: 
     610                            return settings.TEMPLATE_STRING_IF_INVALID % self.var 
     611                        return settings.TEMPLATE_STRING_IF_INVALID 
     612                    else: 
     613                        obj = settings.TEMPLATE_STRING_IF_INVALID 
     614        else: 
     615            obj = self.var 
    532616        for func, args in self.filters: 
    533617            arg_vals = [] 
    534618            for lookup, arg in args: 
     
    593677    return Variable(path).resolve(context) 
    594678 
    595679class Variable(object): 
    596     """ 
     680    r""" 
    597681    A template variable, resolvable against a given context. The variable may be 
    598682    a hard-coded string (if it begins and ends with single or double quote 
    599683    marks):: 
     
    609693        >>> c.article.section = 'News' 
    610694        >>> Variable('article.section').resolve(c) 
    611695        u'News' 
     696        >>> Variable(u'"News"').resolve(c) 
     697        u'News' 
     698        >>> Variable(u"'News'").resolve(c) 
     699        u'News' 
    612700 
    613701    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') 
     702 
     703    Translated strings are also handled correctly:: 
     704 
     705        >>> Variable('_(article.section)').resolve(c) 
     706        u'News' 
     707        >>> Variable('_("Good News")').resolve(c) 
     708        u'Good News' 
     709        >>> Variable("_('Better News')").resolve(c) 
     710        u'Better News' 
     711 
     712    Escaped quotes work correctly as well:: 
     713 
     714        >>> Variable(ur'"Some \"Good\" News"').resolve(c) 
     715        u'Some "Good" News' 
     716        >>> Variable(ur"'Some \'Better\' News'").resolve(c) 
     717        u"Some 'Better' News" 
    614718    """ 
    615719 
    616720    def __init__(self, var): 
     
    645749                var = var[2:-1] 
    646750            # If it's wrapped with quotes (single or double), then 
    647751            # 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
     752            try
     753                self.literal = mark_safe(unescape_string_literal(var)
     754            except ValueError
    651755                # Otherwise we'll set self.lookups so that resolve() knows we're 
    652756                # dealing with a bonafide variable 
    653757                self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))