Ticket #2594: template_whitespace_2.patch
File template_whitespace_2.patch, 10.8 KB (added by , 18 years ago) |
---|
-
django/template/__init__.py
67 67 TOKEN_VAR = 1 68 68 TOKEN_BLOCK = 2 69 69 TOKEN_COMMENT = 3 70 TOKEN_WHITESPACE = 4 71 TOKEN_MULTIPART = 5 70 72 71 73 # template syntax constants 72 74 FILTER_SEPARATOR = '|' … … 87 89 # (e.g. strings) 88 90 UNKNOWN_SOURCE="<unknown source>" 89 91 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), 92 BLOCKS_RE = r'%s.*?%s|%s.*?%s|%s.*?%s' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 92 93 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). 97 tag_re = re.compile('((?<![^\n])[\t ]*(?:%s)[\t ]*\n|(?:%s))' % (BLOCKS_RE, BLOCKS_RE)) 98 whitespace_tag_re = re.compile('(%s)' % BLOCKS_RE) 94 99 95 100 # global dictionary of libraries that have been loaded using get_library 96 101 libraries = {} … … 167 172 168 173 class Token(object): 169 174 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 """ 171 179 self.token_type, self.contents = token_type, contents 172 180 173 181 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) 177 198 178 199 def split_contents(self): 179 200 return list(smart_split(self.contents)) … … 191 212 192 213 def create_token(self,token_string): 193 214 "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): 195 223 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 196 224 elif token_string.startswith(BLOCK_TAG_START): 197 225 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) … … 201 229 token = Token(TOKEN_TEXT, token_string) 202 230 return token 203 231 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 204 240 class DebugLexer(Lexer): 205 241 def __init__(self, template_string, origin): 206 242 super(DebugLexer, self).__init__(template_string, origin) … … 220 256 token_tups.append( (last_bit, (upto, upto + len(last_bit))) ) 221 257 return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups] 222 258 223 def create_token(self, token_string, source ):259 def create_token(self, token_string, source=''): 224 260 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 225 264 token.source = source 226 265 return token 227 266 … … 238 277 nodelist = self.create_nodelist() 239 278 while self.tokens: 240 279 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: 242 287 self.extend_nodelist(nodelist, TextNode(token.contents), token) 243 288 elif token.token_type == TOKEN_VAR: 244 289 if not token.contents: … … 313 358 pass 314 359 315 360 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 317 369 318 370 def prepend_token(self, token): 319 371 self.tokens.insert(0, token) 320 372 321 373 def delete_first_token(self): 322 del self.tokens[0]374 self.next_token() 323 375 324 376 def add_library(self, lib): 325 377 self.tags.update(lib.tags) … … 689 741 class NodeList(list): 690 742 def render(self, context): 691 743 bits = [] 744 whitespace = None 745 whitespace_needed = False 746 open_whitespace_block = False 692 747 for node in self: 693 748 if isinstance(node, Node): 694 bit s.append(self.render_node(node, context))749 bit = self.render_node(node, context) 695 750 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 697 781 return ''.join(bits) 698 782 699 783 def get_nodes_by_type(self, nodetype): … … 762 846 raise 763 847 return self.encode_output(output) 764 848 849 class WhitespaceStartNode(TextNode): 850 def __repr__(self): 851 return "<Whitespace Start Node: %r>" % self.s[:25] 852 853 class WhitespaceEndNode(TextNode): 854 def __repr__(self): 855 return "<Whitespace End Node: %r>" % self.s[:25] 856 765 857 def generic_tag_compiler(params, defaults, name, node_class, parser, token): 766 858 "Returns a template.Node subclass." 767 859 bits = token.split_contents()[1:] -
django/template/defaulttags.py
118 118 'parentloop': parentloop, 119 119 } 120 120 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)) 123 122 context.pop() 124 123 return nodelist.render(context) 125 124 -
django/template/loader_tags.py
59 59 else: 60 60 return get_template_from_string(source, origin, parent) 61 61 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 62 72 def render(self, context): 63 73 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) 65 75 parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)]) 66 76 for block_node in self.nodelist.get_nodes_by_type(BlockNode): 67 77 # Check for a BlockNode with this node's name, and replace it if found. … … 72 82 # parent block might be defined in the parent's *parent*, so we 73 83 # add this BlockNode to the parent's ExtendsNode nodelist, so 74 84 # 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) 77 87 else: 78 88 # Keep any existing parents and add a new one. Used by BlockNode. 79 89 parent_block.parent = block_node.parent