Ticket #2594: template_whitespace_2.2.patch

File template_whitespace_2.2.patch, 10.8 KB (added by SmileyChris, 8 years ago)

oops, this one i meant :P

  • django/template/__init__.py

     
    6767TOKEN_VAR = 1
    6868TOKEN_BLOCK = 2
    6969TOKEN_COMMENT = 3
     70TOKEN_WHITESPACE = 4
     71TOKEN_MULTIPART = 5
    7072
    7173# template syntax constants
    7274FILTER_SEPARATOR = '|'
     
    8789# (e.g. strings)
    8890UNKNOWN_SOURCE="<unknown source>"
    8991
    90 # match a variable or block tag and capture the entire tag, including start/end delimiters
    91 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
     92BLOCKS_RE = r'%s.*?%s|%s.*?%s|%s.*?%s' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
    9293                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
    93                                           re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
     94                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))
     95# Match a variable or block tag and capture the entire tag, including tag
     96# delimiters (and surrounding white space if the tag is on its own line).
     97tag_re = re.compile(r'((?<![^\n])[\t ]*(?:%s)[\t ]*\n|(?:%s))' % (BLOCKS_RE, BLOCKS_RE))
     98whitespace_tag_re = re.compile('(%s)' % BLOCKS_RE)
    9499
    95100# global dictionary of libraries that have been loaded using get_library
    96101libraries = {}
     
    167172
    168173class Token(object):
    169174    def __init__(self, token_type, contents):
    170         "The token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT"
     175        """
     176        The token_type must be one of the following: TOKEN_TEXT, TOKEN_VAR,
     177        TOKEN_BLOCK, TOKEN_COMMENT, TOKEN_WHITESPACE, TOKEN_MULTIPART.
     178        """
    171179        self.token_type, self.contents = token_type, contents
    172180
    173181    def __str__(self):
    174         return '<%s token: "%s...">' % \
    175             ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
    176             self.contents[:20].replace('\n', ''))
     182        if self.token_type == TOKEN_MULTIPART:
     183            contents = ', '.join([str(token) for token in self.contents])
     184        elif self.token_type == TOKEN_WHITESPACE:
     185            contents = self.contents.replace('\n', '\\n')
     186        else:
     187            contents = self.contents.replace('\n', '')
     188        if len(self.contents) > 23:
     189            contents = contents[:20] + '...'
     190        return '<%s token: "%s">' % \
     191            ({TOKEN_TEXT: 'Text',
     192              TOKEN_VAR: 'Var',
     193              TOKEN_BLOCK: 'Block',
     194              TOKEN_COMMENT: 'Comment',
     195              TOKEN_WHITESPACE: 'Whitespace',
     196              TOKEN_MULTIPART: 'Multipart'}[self.token_type],
     197            contents)
    177198
    178199    def split_contents(self):
    179200        return list(smart_split(self.contents))
     
    191212
    192213    def create_token(self,token_string):
    193214        "Convert the given token string into a new Token object and return it"
    194         if token_string.startswith(VARIABLE_TAG_START):
     215        if token_string.endswith('\n'):
     216            token_group = whitespace_tag_re.split(token_string)
     217            # Is only one tag on its own line.
     218            has_whitespace = len(token_group) == 3
     219        else:
     220            has_whitespace = False
     221        if has_whitespace:
     222            token = self.multipart_token(token_group)
     223        elif token_string.startswith(VARIABLE_TAG_START):
    195224            token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
    196225        elif token_string.startswith(BLOCK_TAG_START):
    197226            token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
     
    201230            token = Token(TOKEN_TEXT, token_string)
    202231        return token
    203232
     233    def multipart_token(self, token_group):
     234        before = Token(TOKEN_WHITESPACE, token_group[0])
     235        before.start = True
     236        tag = self.create_token(token_group[1])
     237        after = Token(TOKEN_WHITESPACE, token_group[2])
     238        after.start = False
     239        return Token(TOKEN_MULTIPART, (before, tag, after))
     240
    204241class DebugLexer(Lexer):
    205242    def __init__(self, template_string, origin):
    206243        super(DebugLexer, self).__init__(template_string, origin)
     
    220257            token_tups.append( (last_bit, (upto, upto + len(last_bit))) )
    221258        return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups]
    222259
    223     def create_token(self, token_string, source):
     260    def create_token(self, token_string, source=''):
    224261        token = super(DebugLexer, self).create_token(token_string)
     262        if token.token_type == TOKEN_MULTIPART:
     263            for sub_token in token.contents:
     264                sub_token.source = source
    225265        token.source = source
    226266        return token
    227267
     
    238278        nodelist = self.create_nodelist()
    239279        while self.tokens:
    240280            token = self.next_token()
    241             if token.token_type == TOKEN_TEXT:
     281            if token.token_type == TOKEN_WHITESPACE:
     282                if token.start:
     283                    node_type = WhitespaceStartNode
     284                else:
     285                    node_type = WhitespaceEndNode
     286                self.extend_nodelist(nodelist, node_type(token.contents), token)
     287            elif token.token_type == TOKEN_TEXT:
    242288                self.extend_nodelist(nodelist, TextNode(token.contents), token)
    243289            elif token.token_type == TOKEN_VAR:
    244290                if not token.contents:
     
    313359        pass
    314360
    315361    def next_token(self):
    316         return self.tokens.pop(0)
     362        token = self.tokens.pop(0)
     363        if token.token_type == TOKEN_MULTIPART:
     364            token_group = list(token.contents)
     365            # Since we are prepending, we want the last token in first.
     366            token_group.reverse()
     367            map(self.prepend_token, token_group)
     368            token = self.next_token()
     369        return token
    317370
    318371    def prepend_token(self, token):
    319372        self.tokens.insert(0, token)
    320373
    321374    def delete_first_token(self):
    322         del self.tokens[0]
     375        self.next_token()
    323376
    324377    def add_library(self, lib):
    325378        self.tags.update(lib.tags)
     
    689742class NodeList(list):
    690743    def render(self, context):
    691744        bits = []
     745        whitespace = None
     746        whitespace_needed = False
     747        open_whitespace_block = False
    692748        for node in self:
    693749            if isinstance(node, Node):
    694                 bits.append(self.render_node(node, context))
     750                bit = self.render_node(node, context)
    695751            else:
    696                 bits.append(node)
     752                bit = node
     753            if isinstance(node, WhitespaceStartNode):
     754                # Remember the starting white space, but don't use it yet.
     755                whitespace = bit
     756                bit = None
     757                whitespace_start_position = len(bits)
     758                open_whitespace_block = True
     759                whitespace_needed = False
     760            elif isinstance(node, WhitespaceEndNode):
     761                if not whitespace_needed:
     762                    # If there is stored starting white space and this is the
     763                    # end of the line, then the white space and this new line
     764                    # can be dropped.
     765                    open_whitespace_block = False
     766                elif bits[-1].endswith('\n'):
     767                    # If the content ends in a new line, don't use the
     768                    # surrounding white space.
     769                    open_whitespace_block = False
     770                if open_whitespace_block:
     771                    # Insert the starting white space.
     772                    if whitespace:
     773                        bits.insert(whitespace_start_position, whitespace)
     774                    open_whitespace_block = False
     775                else:
     776                    # Drop the new line.
     777                    bit = None
     778            if bit:
     779                bits.append(bit)
     780                if open_whitespace_block and not whitespace_needed:
     781                    whitespace_needed = True
    697782        return ''.join(bits)
    698783
    699784    def get_nodes_by_type(self, nodetype):
     
    762847            raise
    763848        return self.encode_output(output)
    764849
     850class WhitespaceStartNode(TextNode):
     851    def __repr__(self):
     852        return "<Whitespace Start Node: %r>" % self.s[:25]
     853
     854class WhitespaceEndNode(TextNode):
     855    def __repr__(self):
     856        return "<Whitespace End Node: %r>" % self.s[:25]
     857
    765858def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    766859    "Returns a template.Node subclass."
    767860    bits = token.split_contents()[1:]
  • django/template/defaulttags.py

     
    118118                'parentloop': parentloop,
    119119            }
    120120            context[self.loopvar] = item
    121             for node in self.nodelist_loop:
    122                 nodelist.append(node.render(context))
     121            nodelist.extend(self.nodelist_loop.render(context))
    123122        context.pop()
    124123        return nodelist.render(context)
    125124
  • django/template/loader_tags.py

     
    5959        else:
    6060            return get_template_from_string(source, origin, parent)
    6161
     62    def get_parent_extendsnode(self, compiled_parent):
     63        """
     64        Returns the ExtendsNode for the parent template.
     65        None is returned if a match is not found in the first 10 nodes.
     66        """
     67        for node in compiled_parent.nodelist[:10]:
     68            if isinstance(node, ExtendsNode):
     69                return node
     70        return None
     71
    6272    def render(self, context):
    6373        compiled_parent = self.get_parent(context)
    64         parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
     74        parent_extendsnode = self.get_parent_extendsnode(compiled_parent)
    6575        parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
    6676        for block_node in self.nodelist.get_nodes_by_type(BlockNode):
    6777            # Check for a BlockNode with this node's name, and replace it if found.
     
    7282                # parent block might be defined in the parent's *parent*, so we
    7383                # add this BlockNode to the parent's ExtendsNode nodelist, so
    7484                # it'll be checked when the parent node's render() is called.
    75                 if parent_is_child:
    76                     compiled_parent.nodelist[0].nodelist.append(block_node)
     85                if parent_extendsnode:
     86                    parent_extendsnode.nodelist.append(block_node)
    7787            else:
    7888                # Keep any existing parents and add a new one. Used by BlockNode.
    7989                parent_block.parent = block_node.parent
Back to Top