Ticket #2594: template_whitespace_2.2.patch
File template_whitespace_2.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(r'((?<![^\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 # 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): 195 224 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 196 225 elif token_string.startswith(BLOCK_TAG_START): 197 226 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) … … 201 230 token = Token(TOKEN_TEXT, token_string) 202 231 return token 203 232 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 204 241 class DebugLexer(Lexer): 205 242 def __init__(self, template_string, origin): 206 243 super(DebugLexer, self).__init__(template_string, origin) … … 220 257 token_tups.append( (last_bit, (upto, upto + len(last_bit))) ) 221 258 return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups] 222 259 223 def create_token(self, token_string, source ):260 def create_token(self, token_string, source=''): 224 261 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 225 265 token.source = source 226 266 return token 227 267 … … 238 278 nodelist = self.create_nodelist() 239 279 while self.tokens: 240 280 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: 242 288 self.extend_nodelist(nodelist, TextNode(token.contents), token) 243 289 elif token.token_type == TOKEN_VAR: 244 290 if not token.contents: … … 313 359 pass 314 360 315 361 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 317 370 318 371 def prepend_token(self, token): 319 372 self.tokens.insert(0, token) 320 373 321 374 def delete_first_token(self): 322 del self.tokens[0]375 self.next_token() 323 376 324 377 def add_library(self, lib): 325 378 self.tags.update(lib.tags) … … 689 742 class NodeList(list): 690 743 def render(self, context): 691 744 bits = [] 745 whitespace = None 746 whitespace_needed = False 747 open_whitespace_block = False 692 748 for node in self: 693 749 if isinstance(node, Node): 694 bit s.append(self.render_node(node, context))750 bit = self.render_node(node, context) 695 751 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 697 782 return ''.join(bits) 698 783 699 784 def get_nodes_by_type(self, nodetype): … … 762 847 raise 763 848 return self.encode_output(output) 764 849 850 class WhitespaceStartNode(TextNode): 851 def __repr__(self): 852 return "<Whitespace Start Node: %r>" % self.s[:25] 853 854 class WhitespaceEndNode(TextNode): 855 def __repr__(self): 856 return "<Whitespace End Node: %r>" % self.s[:25] 857 765 858 def generic_tag_compiler(params, defaults, name, node_class, parser, token): 766 859 "Returns a template.Node subclass." 767 860 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