Ticket #2594: template_whitespace.patch
File template_whitespace.patch, 11.9 KB (added by , 18 years ago) |
---|
-
django/template/__init__.py
66 66 TOKEN_TEXT = 0 67 67 TOKEN_VAR = 1 68 68 TOKEN_BLOCK = 2 69 TOKEN_WHITESPACE = 3 70 TOKEN_MULTIPART = 4 69 71 70 72 # template syntax constants 71 73 FILTER_SEPARATOR = '|' … … 85 87 UNKNOWN_SOURCE="<unknown source>" 86 88 87 89 # match a variable or block tag and capture the entire tag, including start/end delimiters 88 tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 90 tag_re = re.compile('((?:(?<![^\n])[\t ]*(?:%s.*?%s|%s.*?%s)[\t ]*\n)|(?:%s.*?%s|%s.*?%s))' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 91 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), 92 re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 89 93 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) 94 whitespace_tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 95 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) 96 ends_with_newline_re = re.compile('\n[\t ]*$') 90 97 91 98 # global dictionary of libraries that have been loaded using get_library 92 99 libraries = {} … … 163 170 164 171 class Token(object): 165 172 def __init__(self, token_type, contents): 166 "The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK" 173 """ 174 The token_type must be one of the following: TOKEN_TEXT, TOKEN_VAR, 175 TOKEN_BLOCK, TOKEN_WHITESPACE, TOKEN_MULTIPART. 176 """ 167 177 self.token_type, self.contents = token_type, contents 168 178 169 179 def __str__(self): 170 return '<%s token: "%s...">' % \ 171 ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block'}[self.token_type], 172 self.contents[:20].replace('\n', '')) 180 if self.token_type == TOKEN_MULTIPART: 181 contents = ', '.join([str(token) for token in self.contents]) 182 elif self.token_type == TOKEN_WHITESPACE: 183 contents = self.contents.replace('\n', '\\n') 184 else: 185 contents = self.contents.replace('\n', '') 186 if len(self.contents) > 23: 187 contents = contents[:20] + '...' 188 return '<%s token: "%s">' % \ 189 ({TOKEN_TEXT: 'Text', 190 TOKEN_VAR: 'Var', 191 TOKEN_BLOCK: 'Block', 192 TOKEN_WHITESPACE: 'Whitespace', 193 TOKEN_MULTIPART: 'Multipart'}[self.token_type], 194 contents) 173 195 174 196 def split_contents(self): 175 197 return list(smart_split(self.contents)) … … 187 209 188 210 def create_token(self,token_string): 189 211 "Convert the given token string into a new Token object and return it" 190 if token_string.startswith(VARIABLE_TAG_START): 191 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 192 elif token_string.startswith(BLOCK_TAG_START): 193 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 212 token_group = whitespace_tag_re.split(token_string) 213 multipart = len(token_group) > 1 214 if multipart: 215 # If there content in the last group then this is a whitespaced 216 # token. 217 has_whitespace = token_group[-1] 218 if not has_whitespace: 219 token_group = filter(None, token_group) 220 multipart = len(token_group) > 1 221 if not multipart: 222 token_string = token_group[0].strip() 223 if multipart: 224 token = Token(TOKEN_MULTIPART, token_group) 225 if has_whitespace: 226 token.whitespace = True 227 self.multipart_token(token) 194 228 else: 195 token = Token(TOKEN_TEXT, token_string) 229 if token_string.startswith(VARIABLE_TAG_START): 230 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 231 elif token_string.startswith(BLOCK_TAG_START): 232 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 233 else: 234 token = Token(TOKEN_TEXT, token_string) 196 235 return token 236 237 def multipart_token(self, token): 238 token.contents = [self.create_token(token_string) for token_string in token.contents] 239 if getattr(token, 'whitespace', None): 240 before = token.contents[0] 241 after = token.contents[-1] 242 before.token_type = TOKEN_WHITESPACE 243 before.start = True 244 after.token_type = TOKEN_WHITESPACE 245 before.start = False 197 246 198 247 class DebugLexer(Lexer): 199 248 def __init__(self, template_string, origin): … … 217 266 def create_token(self, token_string, source): 218 267 token = super(DebugLexer, self).create_token(token_string) 219 268 token.source = source 269 if token.token_type == TOKEN_MULTIPART: 270 token.contents = [self.create_token(inner_token, source) for inner_token in token.contents] 271 if getattr(token, 'whitespace', None): 272 before = token.contents[0] 273 after = token.contents[-1] 274 before.token_type = TOKEN_WHITESPACE 275 before.start = True 276 after.token_type = TOKEN_WHITESPACE 277 after.start = False 220 278 return token 221 279 280 def multipart_token(self, token): 281 pass 282 222 283 class Parser(object): 223 284 def __init__(self, tokens): 224 285 self.tokens = tokens … … 232 293 nodelist = self.create_nodelist() 233 294 while self.tokens: 234 295 token = self.next_token() 235 if token.token_type == TOKEN_TEXT: 296 if token.token_type == TOKEN_WHITESPACE: 297 if token.start: 298 node_type = WhitespaceStartNode 299 else: 300 node_type = WhitespaceEndNode 301 self.extend_nodelist(nodelist, node_type(token.contents), token) 302 elif token.token_type == TOKEN_TEXT: 236 303 self.extend_nodelist(nodelist, TextNode(token.contents), token) 237 304 elif token.token_type == TOKEN_VAR: 238 305 if not token.contents: … … 307 374 pass 308 375 309 376 def next_token(self): 310 return self.tokens.pop(0) 377 token = self.tokens.pop(0) 378 if token.token_type == TOKEN_MULTIPART: 379 token_group = list(token.contents) 380 # Since we are prepending, we want the last token in first. 381 token_group.reverse() 382 map(self.prepend_token, token_group) 383 token = self.next_token() 384 return token 311 385 312 386 def prepend_token(self, token): 313 387 self.tokens.insert(0, token) 314 388 315 389 def delete_first_token(self): 316 del self.tokens[0]390 self.next_token() 317 391 318 392 def add_library(self, lib): 319 393 self.tags.update(lib.tags) … … 683 757 class NodeList(list): 684 758 def render(self, context): 685 759 bits = [] 760 whitespace = None 761 whitespace_needed = False 762 open_whitespace_block = False 686 763 for node in self: 687 764 if isinstance(node, Node): 688 bit s.append(self.render_node(node, context))765 bit = self.render_node(node, context) 689 766 else: 690 bits.append(node) 767 bit = node 768 if isinstance(node, WhitespaceStartNode): 769 # Remember the starting white space, but don't use it yet. 770 whitespace = bit 771 bit = None 772 whitespace_start_position = len(bits) 773 open_whitespace_block = True 774 whitespace_needed = False 775 elif isinstance(node, WhitespaceEndNode): 776 if not whitespace_needed: 777 # If there is stored starting white space and this is the 778 # end of the line, then the white space and this new line 779 # can be dropped. 780 open_whitespace_block = False 781 elif ends_with_newline_re.search(bits[-1]): 782 # If the content ended in a new line, don't use the 783 # surrounding white space. 784 open_whitespace_block = False 785 # Alternate behaviour could be to just strip the second 786 # new line: 787 #bit = bit[:-1] 788 if open_whitespace_block: 789 # Insert the starting white space. 790 if whitespace: 791 bits.insert(whitespace_start_position, whitespace) 792 open_whitespace_block = False 793 else: 794 # Drop the new line. 795 bit = None 796 if bit: 797 bits.append(bit) 798 if open_whitespace_block and not whitespace_needed: 799 whitespace_needed = True 691 800 return ''.join(bits) 692 801 693 802 def get_nodes_by_type(self, nodetype): … … 756 865 raise 757 866 return self.encode_output(output) 758 867 868 class WhitespaceStartNode(TextNode): 869 def __repr__(self): 870 return "<Whitespace Start Node: %r>" % self.s[:25] 871 872 class WhitespaceEndNode(TextNode): 873 def __repr__(self): 874 return "<Whitespace End Node: %r>" % self.s[:25] 875 759 876 def generic_tag_compiler(params, defaults, name, node_class, parser, token): 760 877 "Returns a template.Node subclass." 761 878 bits = token.split_contents()[1:] -
django/template/defaulttags.py
114 114 'parentloop': parentloop, 115 115 } 116 116 context[self.loopvar] = item 117 for node in self.nodelist_loop: 118 nodelist.append(node.render(context)) 117 nodelist.extend(self.nodelist_loop.render(context)) 119 118 context.pop() 120 119 return nodelist.render(context) 121 120 -
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