| 474 | | class FilterParser: |
|---|
| 475 | | """ |
|---|
| 476 | | Parses a variable token and its optional filters (all as a single string), |
|---|
| 477 | | and return a list of tuples of the filter name and arguments. |
|---|
| 478 | | Sample: |
|---|
| 479 | | >>> token = 'variable|default:"Default value"|date:"Y-m-d"' |
|---|
| 480 | | >>> p = FilterParser(token) |
|---|
| 481 | | >>> p.filters |
|---|
| 482 | | [('default', 'Default value'), ('date', 'Y-m-d')] |
|---|
| 483 | | >>> p.var |
|---|
| 484 | | 'variable' |
|---|
| 485 | | |
|---|
| 486 | | This class should never be instantiated outside of the |
|---|
| 487 | | get_filters_from_token helper function. |
|---|
| 488 | | """ |
|---|
| 489 | | def __init__(self, s): |
|---|
| 490 | | self.s = s |
|---|
| 491 | | self.i = -1 |
|---|
| 492 | | self.current = '' |
|---|
| 493 | | self.filters = [] |
|---|
| 494 | | self.current_filter_name = None |
|---|
| 495 | | self.current_filter_arg = None |
|---|
| 496 | | # First read the variable part. Decide whether we need to parse a |
|---|
| 497 | | # string or a variable by peeking into the stream. |
|---|
| 498 | | if self.peek_char() in ('_', '"', "'"): |
|---|
| 499 | | self.var = self.read_constant_string_token() |
|---|
| 500 | | else: |
|---|
| 501 | | self.var = self.read_alphanumeric_token() |
|---|
| 502 | | if not self.var: |
|---|
| 503 | | raise TemplateSyntaxError, "Could not read variable name: '%s'" % self.s |
|---|
| 504 | | if self.var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or self.var[0] == '_': |
|---|
| 505 | | raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % self.var |
|---|
| 506 | | # Have we reached the end? |
|---|
| 507 | | if self.current is None: |
|---|
| 508 | | return |
|---|
| 509 | | if self.current != FILTER_SEPARATOR: |
|---|
| 510 | | raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current) |
|---|
| 511 | | # We have a filter separator; start reading the filters |
|---|
| 512 | | self.read_filters() |
|---|
| 513 | | |
|---|
| 514 | | def peek_char(self): |
|---|
| 515 | | try: |
|---|
| 516 | | return self.s[self.i+1] |
|---|
| 517 | | except IndexError: |
|---|
| 518 | | return None |
|---|
| 519 | | |
|---|
| 520 | | def next_char(self): |
|---|
| 521 | | self.i = self.i + 1 |
|---|
| 522 | | try: |
|---|
| 523 | | self.current = self.s[self.i] |
|---|
| 524 | | except IndexError: |
|---|
| 525 | | self.current = None |
|---|
| 526 | | |
|---|
| 527 | | def read_constant_string_token(self): |
|---|
| 528 | | """ |
|---|
| 529 | | Reads a constant string that must be delimited by either " or ' |
|---|
| 530 | | characters. The string is returned with its delimiters. |
|---|
| 531 | | """ |
|---|
| 532 | | val = '' |
|---|
| 533 | | qchar = None |
|---|
| 534 | | i18n = False |
|---|
| 535 | | self.next_char() |
|---|
| 536 | | if self.current == '_': |
|---|
| 537 | | i18n = True |
|---|
| 538 | | self.next_char() |
|---|
| 539 | | if self.current != '(': |
|---|
| 540 | | raise TemplateSyntaxError, "Bad character (expecting '(') '%s'" % self.current |
|---|
| 541 | | self.next_char() |
|---|
| 542 | | if not self.current in ('"', "'"): |
|---|
| 543 | | raise TemplateSyntaxError, "Bad character (expecting '\"' or ''') '%s'" % self.current |
|---|
| 544 | | qchar = self.current |
|---|
| 545 | | val += qchar |
|---|
| 546 | | while 1: |
|---|
| 547 | | self.next_char() |
|---|
| 548 | | if self.current == qchar: |
|---|
| 549 | | break |
|---|
| 550 | | val += self.current |
|---|
| 551 | | val += self.current |
|---|
| 552 | | self.next_char() |
|---|
| 553 | | if i18n: |
|---|
| 554 | | if self.current != ')': |
|---|
| 555 | | raise TemplateSyntaxError, "Bad character (expecting ')') '%s'" % self.current |
|---|
| 556 | | self.next_char() |
|---|
| 557 | | val = qchar+_(val.strip(qchar))+qchar |
|---|
| 558 | | return val |
|---|
| 559 | | |
|---|
| 560 | | def read_alphanumeric_token(self): |
|---|
| 561 | | """ |
|---|
| 562 | | Reads a variable name or filter name, which are continuous strings of |
|---|
| 563 | | alphanumeric characters + the underscore. |
|---|
| 564 | | """ |
|---|
| 565 | | var = '' |
|---|
| 566 | | while 1: |
|---|
| 567 | | self.next_char() |
|---|
| 568 | | if self.current is None: |
|---|
| 569 | | break |
|---|
| 570 | | if self.current not in ALLOWED_VARIABLE_CHARS: |
|---|
| 571 | | break |
|---|
| 572 | | var += self.current |
|---|
| 573 | | return var |
|---|
| 574 | | |
|---|
| 575 | | def read_filters(self): |
|---|
| 576 | | while 1: |
|---|
| 577 | | filter_name, arg = self.read_filter() |
|---|
| 578 | | if not registered_filters.has_key(filter_name): |
|---|
| 579 | | raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name |
|---|
| 580 | | if registered_filters[filter_name][1] == True and arg is None: |
|---|
| 581 | | raise TemplateSyntaxError, "Filter '%s' requires an argument" % filter_name |
|---|
| 582 | | if registered_filters[filter_name][1] == False and arg is not None: |
|---|
| 583 | | raise TemplateSyntaxError, "Filter '%s' should not have an argument (argument is %r)" % (filter_name, arg) |
|---|
| 584 | | self.filters.append((filter_name, arg)) |
|---|
| 585 | | if self.current is None: |
|---|
| 586 | | break |
|---|
| 587 | | |
|---|
| 588 | | def read_filter(self): |
|---|
| 589 | | self.current_filter_name = self.read_alphanumeric_token() |
|---|
| 590 | | self.current_filter_arg = None |
|---|
| 591 | | # Have we reached the end? |
|---|
| 592 | | if self.current is None: |
|---|
| 593 | | return (self.current_filter_name, None) |
|---|
| 594 | | # Does the filter have an argument? |
|---|
| 595 | | if self.current == FILTER_ARGUMENT_SEPARATOR: |
|---|
| 596 | | self.current_filter_arg = self.read_arg() |
|---|
| 597 | | return (self.current_filter_name, self.current_filter_arg) |
|---|
| 598 | | # Next thing MUST be a pipe |
|---|
| 599 | | if self.current != FILTER_SEPARATOR: |
|---|
| 600 | | raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current) |
|---|
| 601 | | return (self.current_filter_name, self.current_filter_arg) |
|---|
| 602 | | |
|---|
| 603 | | def read_arg(self): |
|---|
| 604 | | # First read a " or a _(" |
|---|
| 605 | | self.next_char() |
|---|
| 606 | | translated = False |
|---|
| 607 | | if self.current == '_': |
|---|
| 608 | | self.next_char() |
|---|
| 609 | | if self.current != '(': |
|---|
| 610 | | raise TemplateSyntaxError, "Bad character (expecting '(') '%s'" % self.current |
|---|
| 611 | | translated = True |
|---|
| 612 | | self.next_char() |
|---|
| 613 | | if self.current != '"': |
|---|
| 614 | | raise TemplateSyntaxError, "Bad character (expecting '\"') '%s'" % self.current |
|---|
| 615 | | self.escaped = False |
|---|
| 616 | | arg = '' |
|---|
| 617 | | while 1: |
|---|
| 618 | | self.next_char() |
|---|
| 619 | | if self.current == '"' and not self.escaped: |
|---|
| 620 | | break |
|---|
| 621 | | if self.current == '\\' and not self.escaped: |
|---|
| 622 | | self.escaped = True |
|---|
| 623 | | continue |
|---|
| 624 | | if self.current == '\\' and self.escaped: |
|---|
| 625 | | arg += '\\' |
|---|
| 626 | | self.escaped = False |
|---|
| 627 | | continue |
|---|
| 628 | | if self.current == '"' and self.escaped: |
|---|
| 629 | | arg += '"' |
|---|
| 630 | | self.escaped = False |
|---|
| 631 | | continue |
|---|
| 632 | | if self.escaped and self.current not in '\\"': |
|---|
| 633 | | raise TemplateSyntaxError, "Unescaped backslash in '%s'" % self.s |
|---|
| 634 | | if self.current is None: |
|---|
| 635 | | raise TemplateSyntaxError, "Unexpected end of argument in '%s'" % self.s |
|---|
| 636 | | arg += self.current |
|---|
| 637 | | # self.current must now be '"' |
|---|
| 638 | | self.next_char() |
|---|
| 639 | | if translated: |
|---|
| 640 | | if self.current != ')': |
|---|
| 641 | | raise TemplateSyntaxError, "Bad character (expecting ')') '%s'" % self.current |
|---|
| 642 | | self.next_char() |
|---|
| 643 | | arg = _(arg) |
|---|
| 644 | | return arg |
|---|
| | 474 | |
|---|