Ticket #2594: template_whitespace_2.patch

File template_whitespace_2.patch, 10.8 KB (added by Chris Beaven, 18 years ago)

Optimized version of my alternate patch

  • 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('((?<![^\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            has_whitespace = len(token_group) > 1
     218        else:
     219            has_whitespace = False
     220        if has_whitespace:
     221            token = self.multipart_token(token_group)
     222        elif token_string.startswith(VARIABLE_TAG_START):
    195223            token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
    196224        elif token_string.startswith(BLOCK_TAG_START):
    197225            token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
     
    201229            token = Token(TOKEN_TEXT, token_string)
    202230        return token
    203231
     232    def multipart_token(self, token_group):
     233        before = Token(TOKEN_WHITESPACE, token_group[0])
     234        before.start = True
     235        tag = self.create_token(token_group[1])
     236        after = Token(TOKEN_WHITESPACE, token_group[2])
     237        after.start = False
     238        return Token(TOKEN_MULTIPART, (before, tag, after))
     239
    204240class DebugLexer(Lexer):
    205241    def __init__(self, template_string, origin):
    206242        super(DebugLexer, self).__init__(template_string, origin)
     
    220256            token_tups.append( (last_bit, (upto, upto + len(last_bit))) )
    221257        return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups]
    222258
    223     def create_token(self, token_string, source):
     259    def create_token(self, token_string, source=''):
    224260        token = super(DebugLexer, self).create_token(token_string)
     261        if token.token_type == TOKEN_MULTIPART:
     262            for sub_token in token.contents:
     263                sub_token.source = source
    225264        token.source = source
    226265        return token
    227266
     
    238277        nodelist = self.create_nodelist()
    239278        while self.tokens:
    240279            token = self.next_token()
    241             if token.token_type == TOKEN_TEXT:
     280            if token.token_type == TOKEN_WHITESPACE:
     281                if token.start:
     282                    node_type = WhitespaceStartNode
     283                else:
     284                    node_type = WhitespaceEndNode
     285                self.extend_nodelist(nodelist, node_type(token.contents), token)
     286            elif token.token_type == TOKEN_TEXT:
    242287                self.extend_nodelist(nodelist, TextNode(token.contents), token)
    243288            elif token.token_type == TOKEN_VAR:
    244289                if not token.contents:
     
    313358        pass
    314359
    315360    def next_token(self):
    316         return self.tokens.pop(0)
     361        token = self.tokens.pop(0)
     362        if token.token_type == TOKEN_MULTIPART:
     363            token_group = list(token.contents)
     364            # Since we are prepending, we want the last token in first.
     365            token_group.reverse()
     366            map(self.prepend_token, token_group)
     367            token = self.next_token()
     368        return token
    317369
    318370    def prepend_token(self, token):
    319371        self.tokens.insert(0, token)
    320372
    321373    def delete_first_token(self):
    322         del self.tokens[0]
     374        self.next_token()
    323375
    324376    def add_library(self, lib):
    325377        self.tags.update(lib.tags)
     
    689741class NodeList(list):
    690742    def render(self, context):
    691743        bits = []
     744        whitespace = None
     745        whitespace_needed = False
     746        open_whitespace_block = False
    692747        for node in self:
    693748            if isinstance(node, Node):
    694                 bits.append(self.render_node(node, context))
     749                bit = self.render_node(node, context)
    695750            else:
    696                 bits.append(node)
     751                bit = node
     752            if isinstance(node, WhitespaceStartNode):
     753                # Remember the starting white space, but don't use it yet.
     754                whitespace = bit
     755                bit = None
     756                whitespace_start_position = len(bits)
     757                open_whitespace_block = True
     758                whitespace_needed = False
     759            elif isinstance(node, WhitespaceEndNode):
     760                if not whitespace_needed:
     761                    # If there is stored starting white space and this is the
     762                    # end of the line, then the white space and this new line
     763                    # can be dropped.
     764                    open_whitespace_block = False
     765                elif bits[-1].endswith('\n'):
     766                    # If the content ends in a new line, don't use the
     767                    # surrounding white space.
     768                    open_whitespace_block = False
     769                if open_whitespace_block:
     770                    # Insert the starting white space.
     771                    if whitespace:
     772                        bits.insert(whitespace_start_position, whitespace)
     773                    open_whitespace_block = False
     774                else:
     775                    # Drop the new line.
     776                    bit = None
     777            if bit:
     778                bits.append(bit)
     779                if open_whitespace_block and not whitespace_needed:
     780                    whitespace_needed = True
    697781        return ''.join(bits)
    698782
    699783    def get_nodes_by_type(self, nodetype):
     
    762846            raise
    763847        return self.encode_output(output)
    764848
     849class WhitespaceStartNode(TextNode):
     850    def __repr__(self):
     851        return "<Whitespace Start Node: %r>" % self.s[:25]
     852
     853class WhitespaceEndNode(TextNode):
     854    def __repr__(self):
     855        return "<Whitespace End Node: %r>" % self.s[:25]
     856
    765857def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    766858    "Returns a template.Node subclass."
    767859    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