Code

Ticket #2594: template_whitespace_2.patch

File template_whitespace_2.patch, 10.8 KB (added by SmileyChris, 8 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