Ticket #7295: 7295.2.diff
File 7295.2.diff, 11.2 KB (added by , 16 years ago) |
---|
-
django/template/__init__.py
54 54 from django.template.context import Context, RequestContext, ContextPopException 55 55 from django.utils.itercompat import is_iterable 56 56 from django.utils.functional import curry, Promise 57 from django.utils.text import smart_split 57 from django.utils.text import smart_split, unescape_string_literal 58 58 from django.utils.encoding import smart_unicode, force_unicode 59 59 from django.utils.translation import ugettext as _ 60 60 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping … … 431 431 self.pointer = i 432 432 return s 433 433 434 constant_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 } 447 constant_string = constant_string.replace("\n", "") 448 434 449 filter_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)| 437 451 ^(?P<var>[%(var_chars)s]+)| 438 452 (?:%(filter_sep)s 439 453 (?P<filter_name>\w+) 440 454 (?:%(arg_sep)s 441 455 (?: 442 %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s| 443 "(?P<constant_arg>%(str)s)"| 456 (?P<constant_arg>%(constant)s)| 444 457 (?P<var_arg>[%(var_chars)s]+) 445 458 ) 446 459 )? 447 460 )""" % { 448 ' str': r"""[^"\\]*(?:\\.[^"\\]*)*""",461 'constant': constant_string, 449 462 'var_chars': "\w\." , 450 463 'filter_sep': re.escape(FILTER_SEPARATOR), 451 464 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR), 452 'i18n_open' : re.escape("_("),453 'i18n_close' : re.escape(")"),454 465 } 455 466 456 467 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "") 457 468 filter_re = re.compile(filter_raw_string, re.UNICODE) 458 469 459 470 class FilterExpression(object): 460 """471 r""" 461 472 Parses a variable token and its optional filters (all as a single string), 462 473 and return a list of tuples of the filter name and arguments. 463 474 Sample: … … 469 480 >>> fe.var 470 481 'variable' 471 482 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 472 504 This class should never be instantiated outside of the 473 505 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' 474 524 """ 475 525 def __init__(self, token, parser): 476 526 self.token = token … … 484 534 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \ 485 535 (token[:upto], token[upto:start], token[start:])) 486 536 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: 494 544 raise TemplateSyntaxError("Could not find variable at start of %s" % token) 495 545 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_': 496 546 raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var) 547 else: 548 self.var = Variable(var) 549 upto = match.end() 497 550 else: 498 551 filter_name = match.group("filter_name") 499 552 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({}))) 505 556 elif var_arg: 506 557 args.append((True, Variable(var_arg))) 507 558 filter_func = parser.find_filter(filter_name) … … 511 562 if upto != len(token): 512 563 raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token)) 513 564 self.filters = filters 514 self.var = Variable(var)515 565 516 566 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 530 573 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 532 585 for func, args in self.filters: 533 586 arg_vals = [] 534 587 for lookup, arg in args: … … 593 646 return Variable(path).resolve(context) 594 647 595 648 class Variable(object): 596 """649 r""" 597 650 A template variable, resolvable against a given context. The variable may be 598 651 a hard-coded string (if it begins and ends with single or double quote 599 652 marks):: … … 609 662 >>> c.article.section = 'News' 610 663 >>> Variable('article.section').resolve(c) 611 664 u'News' 665 >>> Variable(u'"News"').resolve(c) 666 u'News' 667 >>> Variable(u"'News'").resolve(c) 668 u'News' 612 669 613 670 (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" 614 687 """ 615 688 616 689 def __init__(self, var): … … 645 718 var = var[2:-1] 646 719 # If it's wrapped with quotes (single or double), then 647 720 # 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 e lse:721 try: 722 self.literal = mark_safe(unescape_string_literal(var)) 723 except ValueError: 651 724 # Otherwise we'll set self.lookups so that resolve() knows we're 652 725 # dealing with a bonafide variable 653 726 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) -
django/utils/text.py
209 209 """ 210 210 text = force_unicode(text) 211 211 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) 219 213 smart_split = allow_lazy(smart_split, unicode) 220 214 215 def 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'\\', '\\') 235 unescape_string_literal = allow_lazy(unescape_string_literal)