Ticket #7295: 7295.1.diff

File 7295.1.diff, 10.6 KB (added by Antti Kaihola, 16 years ago)

Patch for fixing issues with string literals in templates

  • ../django/template/__init__.py

     
    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))
Back to Top