Ticket #7806: 7806.tplrf-r11593.diff
File 7806.tplrf-r11593.diff, 129.1 KB (added by , 15 years ago) |
---|
-
django/templatetags/i18n.py
1 1 import re 2 2 3 from django.template import Node, Variable, VariableNode,_render_value_in_context3 from django.template import Node, _render_value_in_context 4 4 from django.template import TemplateSyntaxError, TokenParser, Library 5 5 from django.template import TOKEN_TEXT, TOKEN_VAR 6 from django.template.utils import parse_context_bindings, parse_as, resolve_dict 7 from django.template.parser import uses_token_stream 6 8 from django.utils import translation 7 9 from django.utils.encoding import force_unicode 8 10 … … 35 37 36 38 class TranslateNode(Node): 37 39 def __init__(self, value, noop): 38 self.value = Variable(value)40 self.value = value 39 41 self.noop = noop 40 42 41 43 def render(self, context): … … 66 68 return ''.join(result), vars 67 69 68 70 def render(self, context): 69 tmp_context = {} 70 for var, val in self.extra_context.items(): 71 tmp_context[var] = val.render(context) 71 tmp_context = resolve_dict(self.extra_context, context) 72 72 # Update() works like a push(), so corresponding context.pop() is at 73 73 # the end of function 74 74 context.update(tmp_context) … … 141 141 raise TemplateSyntaxError, "'get_current_language_bidi' requires 'as variable' (got %r)" % args 142 142 return GetCurrentLanguageBidiNode(args[2]) 143 143 144 def do_translate(parser, token): 144 #@uses_token_stream 145 def do_translate(parser, bits): 145 146 """ 146 147 This will mark a string for translation and will 147 148 translate the string for the current language. … … 171 172 the variable ``variable``. Make sure that the string 172 173 in there is something that is in the .po file. 173 174 """ 174 class TranslateParser(TokenParser): 175 def top(self): 176 value = self.value() 177 if self.more(): 178 if self.tag() == 'noop': 179 noop = True 180 else: 181 raise TemplateSyntaxError, "only option for 'trans' is 'noop'" 182 else: 183 noop = False 184 return (value, noop) 185 value, noop = TranslateParser(token.contents).top() 175 value = bits.parse_expression(required=True) 176 noop = bits.pop_lexeme('noop') 186 177 return TranslateNode(value, noop) 187 178 188 def do_block_translate(parser, token): 179 #@uses_token_stream 180 def do_block_translate(parser, bits): 189 181 """ 190 182 This will translate a block of text with parameters. 191 183 … … 205 197 206 198 This is much like ngettext, only in template syntax. 207 199 """ 208 class BlockTranslateParser(TokenParser): 209 def top(self): 210 countervar = None 211 counter = None 212 extra_context = {} 213 while self.more(): 214 tag = self.tag() 215 if tag == 'with' or tag == 'and': 216 value = self.value() 217 if self.tag() != 'as': 218 raise TemplateSyntaxError, "variable bindings in 'blocktrans' must be 'with value as variable'" 219 extra_context[self.tag()] = VariableNode( 220 parser.compile_filter(value)) 221 elif tag == 'count': 222 counter = parser.compile_filter(self.value()) 223 if self.tag() != 'as': 224 raise TemplateSyntaxError, "counter specification in 'blocktrans' must be 'count value as variable'" 225 countervar = self.tag() 226 else: 227 raise TemplateSyntaxError, "unknown subtag %s for 'blocktrans' found" % tag 228 return (countervar, counter, extra_context) 200 countervar = None 201 counter = None 202 extra_context = {} 229 203 230 countervar, counter, extra_context = BlockTranslateParser(token.contents).top() 204 if bits.pop_lexeme('with'): 205 extra_context = parse_context_bindings(bits) 206 elif bits.pop_lexeme('count'): 207 counter = bits.parse_expression(required=True) 208 countervar = parse_as(bits, required=True) 231 209 232 210 singular = [] 233 211 plural = [] … … 255 233 register.tag('get_available_languages', do_get_available_languages) 256 234 register.tag('get_current_language', do_get_current_language) 257 235 register.tag('get_current_language_bidi', do_get_current_language_bidi) 258 register.tag('trans', do_translate)259 register.tag('blocktrans', do_block_translate)236 register.tag('trans', uses_token_stream(do_translate)) 237 register.tag('blocktrans', uses_token_stream(do_block_translate)) -
django/templatetags/cache.py
1 from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist 2 from django.template import resolve_variable 1 from django.template import Library, Node, TemplateSyntaxError 2 from django.template.parser import uses_token_stream 3 from django.template.expressions import LookupError 3 4 from django.core.cache import cache 4 from django.utils.encoding import force_unicode5 5 from django.utils.http import urlquote 6 6 from django.utils.hashcompat import md5_constructor 7 7 8 8 register = Library() 9 9 10 10 class CacheNode(Node): 11 def __init__(self, nodelist, expire_time _var, fragment_name, vary_on):11 def __init__(self, nodelist, expire_time, fragment_name, vary_on): 12 12 self.nodelist = nodelist 13 self.expire_time _var = Variable(expire_time_var)13 self.expire_time = expire_time 14 14 self.fragment_name = fragment_name 15 15 self.vary_on = vary_on 16 16 17 17 def render(self, context): 18 18 try: 19 expire_time = self.expire_time _var.resolve(context)20 except VariableDoesNotExist:21 raise TemplateSyntaxError('"cache" tag got an unknkown variable: %r' % self.expire_time _var.var)19 expire_time = self.expire_time.resolve(context) 20 except LookupError: 21 raise TemplateSyntaxError('"cache" tag got an unknkown variable: %r' % self.expire_time) 22 22 try: 23 23 expire_time = int(expire_time) 24 24 except (ValueError, TypeError): 25 25 raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) 26 26 # Build a unicode key for this fragment and all vary-on's. 27 args = md5_constructor(u':'.join([urlquote( resolve_variable(var, context)) for varin self.vary_on]))27 args = md5_constructor(u':'.join([urlquote(exp.resolve(context)) for exp in self.vary_on])) 28 28 cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest()) 29 29 value = cache.get(cache_key) 30 30 if value is None: … … 32 32 cache.set(cache_key, value, expire_time) 33 33 return value 34 34 35 def do_cache(parser, token): 35 #@uses_token_stream 36 def do_cache(parser, bits): 36 37 """ 37 38 This will cache the contents of a template fragment for a given amount 38 39 of time. … … 53 54 54 55 Each unique set of arguments will result in a unique cache entry. 55 56 """ 56 nodelist = parser.parse(('endcache',)) 57 parser.delete_first_token() 58 tokens = token.contents.split() 59 if len(tokens) < 3: 60 raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) 61 return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:]) 57 nodelist = parser.parse_nodelist('endcache') 58 expire_time = bits.parse_expression(required=True) 59 fragment_name = bits.pop_name(required=True) 60 vary_on = bits.parse_expression_list() 61 return CacheNode(nodelist, expire_time, fragment_name, vary_on) 62 62 63 register.tag('cache', do_cache)63 register.tag('cache', uses_token_stream(do_cache)) -
django/contrib/comments/templatetags/comments.py
1 1 from django import template 2 from django.template.parser import uses_token_stream, TokenSyntaxError 2 3 from django.template.loader import render_to_string 4 from django.template.utils import parse_as 3 5 from django.conf import settings 4 6 from django.contrib.contenttypes.models import ContentType 5 7 from django.contrib import comments … … 13 15 Looks a bit strange, but the subclasses below should make this a bit more 14 16 obvious. 15 17 """ 16 17 18 #@classmethod 18 def handle_token(cls, parser, token):19 def handle_token(cls, parser, bits): 19 20 """Class method to parse get_comment_list/count/form and return a Node.""" 20 tokens = token.contents.split() 21 if tokens[1] != 'for': 22 raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0]) 23 24 # {% get_whatever for obj as varname %} 25 if len(tokens) == 5: 26 if tokens[3] != 'as': 27 raise template.TemplateSyntaxError("Third argument in %r must be 'as'" % tokens[0]) 21 bits.pop_lexeme('for', required=True) 22 try: 23 # {% get_whatever for app.model pk as varname %} 24 app_and_model = bits.parse_string(bare=True) 25 if bits.pop_lexeme('as'): 26 bits.pushback() # pushback "as" 27 bits.pushback() # pushback app_and_model 28 raise TokenSyntaxError 29 pk = bits.parse_expression(required=True) 30 varname = parse_as(bits, required=True) 28 31 return cls( 29 object_expr = parser.compile_filter(tokens[2]), 30 as_varname = tokens[4], 31 ) 32 ctype=BaseCommentNode.lookup_content_type(app_and_model, bits.name), 33 object_pk_expr = pk, 34 as_varname = varname 35 ) 36 except TokenSyntaxError: 37 # {% get_whatever for obj as varname %} 38 obj = bits.parse_expression(required=True) 39 varname = parse_as(bits, required=True) 40 return cls(object_expr=obj, as_varname = varname) 32 41 33 # {% get_whatever for app.model pk as varname %}34 elif len(tokens) == 6:35 if tokens[4] != 'as':36 raise template.TemplateSyntaxError("Fourth argument in %r must be 'as'" % tokens[0])37 return cls(38 ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),39 object_pk_expr = parser.compile_filter(tokens[3]),40 as_varname = tokens[5]41 )42 43 else:44 raise template.TemplateSyntaxError("%r tag requires 4 or 5 arguments" % tokens[0])45 46 42 handle_token = classmethod(handle_token) 47 43 48 44 #@staticmethod … … 136 132 """Render the comment form directly""" 137 133 138 134 #@classmethod 139 def handle_token(cls, parser, token):135 def handle_token(cls, parser, bits): 140 136 """Class method to parse render_comment_form and return a Node.""" 141 tokens = token.contents.split() 142 if tokens[1] != 'for': 143 raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0]) 144 145 # {% render_comment_form for obj %} 146 if len(tokens) == 3: 147 return cls(object_expr=parser.compile_filter(tokens[2])) 148 149 # {% render_comment_form for app.models pk %} 150 elif len(tokens) == 4: 151 return cls( 152 ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]), 153 object_pk_expr = parser.compile_filter(tokens[3]) 154 ) 137 bits.pop_lexeme('for', required=True) 138 try: 139 # {% render_comment_form for app.models pk %} 140 app_and_model = bits.parse_string(bare=True) 141 try: 142 pk = bits.parse_expression() 143 return cls( 144 ctype = BaseCommentNode.lookup_content_type(app_and_model, bits.name), 145 object_pk_expr = pk 146 ) 147 except TokenSyntaxError: 148 bits.pushback() 149 raise 150 except TokenSyntaxError: 151 # {% render_comment_form for obj %} 152 return cls(object_expr=bits.parse_expression(required=True)) 155 153 handle_token = classmethod(handle_token) 156 154 157 155 def render(self, context): … … 174 172 # wrapper function that just exists to hold the docstring. 175 173 176 174 #@register.tag 175 #@uses_token_stream 177 176 def get_comment_count(parser, token): 178 177 """ 179 178 Gets the comment count for the given params and populates the template … … 195 194 return CommentCountNode.handle_token(parser, token) 196 195 197 196 #@register.tag 197 #@uses_token_stream 198 198 def get_comment_list(parser, token): 199 199 """ 200 200 Gets the list of comments for the given params and populates the template … … 217 217 return CommentListNode.handle_token(parser, token) 218 218 219 219 #@register.tag 220 #@uses_token_stream 220 221 def get_comment_form(parser, token): 221 222 """ 222 223 Get a (new) form object to post a new comment. … … 252 253 """ 253 254 return comments.get_form_target() 254 255 255 register.tag( get_comment_count)256 register.tag( get_comment_list)257 register.tag( get_comment_form)258 register.tag( render_comment_form)256 register.tag(uses_token_stream(get_comment_count)) 257 register.tag(uses_token_stream(get_comment_list)) 258 register.tag(uses_token_stream(get_comment_form)) 259 register.tag(uses_token_stream(render_comment_form)) 259 260 register.simple_tag(comment_form_target) -
django/template/nodes.py
1 from django.utils.encoding import force_unicode 2 from django.utils.safestring import SafeData, EscapeData, mark_safe 3 from django.utils.html import escape 4 from django.template.expressions import LookupError 5 from django.conf import settings 6 7 class Node(object): 8 # Set this to True for nodes that must be first in the template (although 9 # they can be preceded by text nodes. 10 must_be_first = False 11 12 def render(self, context): 13 "Return the node rendered as a string" 14 pass 15 16 def __repr__(self): 17 return "<%s>" % self.__class__.__name__ 18 19 def __iter__(self): 20 yield self 21 22 def get_nodes_by_type(self, nodetype): 23 "Return a list of all nodes (within this node and its nodelist) of the given type" 24 nodes = [] 25 if isinstance(self, nodetype): 26 nodes.append(self) 27 if hasattr(self, 'nodelist'): 28 nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) 29 return nodes 30 31 class NodeList(list): 32 # Set to True the first time a non-TextNode is inserted by 33 # extend_nodelist(). 34 contains_nontext = False 35 36 def render(self, context): 37 bits = [] 38 for node in self: 39 if isinstance(node, Node): 40 bits.append(self.render_node(node, context)) 41 else: 42 bits.append(node) 43 return mark_safe(''.join([force_unicode(b) for b in bits])) 44 45 def get_nodes_by_type(self, nodetype): 46 "Return a list of all nodes of the given type" 47 nodes = [] 48 for node in self: 49 nodes.extend(node.get_nodes_by_type(nodetype)) 50 return nodes 51 52 def render_node(self, node, context): 53 return node.render(context) 54 55 class TextNode(Node): 56 def __init__(self, s): 57 self.s = s 58 59 def __repr__(self): 60 return "<Text Node: '%s'>" % self.s[:25] 61 62 def render(self, context): 63 return self.s 64 65 class ExpressionNode(Node): 66 def __init__(self, expression): 67 self.expression = expression 68 69 def __repr__(self): 70 return "<Variable Node: %s>" % self.expression 71 72 def render(self, context): 73 try: 74 output = force_unicode(self.expression.resolve(context)) 75 except LookupError: 76 if settings.TEMPLATE_STRING_IF_INVALID: 77 from django.template import invalid_var_format_string 78 if invalid_var_format_string is None: 79 invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID 80 if invalid_var_format_string: 81 return settings.TEMPLATE_STRING_IF_INVALID % self.expression 82 return settings.TEMPLATE_STRING_IF_INVALID 83 else: 84 return u'' 85 except UnicodeDecodeError: 86 # Unicode conversion can fail sometimes for reasons out of our 87 # control (e.g. exception rendering). In that case, we fail quietly. 88 return u'' 89 if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData): 90 output = escape(output) 91 return output -
django/template/parser.py
1 import re 2 from inspect import getargspec 3 from django.conf import settings 4 from django.utils.functional import wraps 5 from django.utils.translation import ugettext 6 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping 7 from django.utils.html import escape 8 from django.utils.encoding import smart_unicode, force_unicode, smart_str 9 from django.utils.text import smart_split 10 from django.template.nodes import Node, ExpressionNode, NodeList, TextNode 11 from django.template.expressions import LookupError, Literal, FilterExpression, Lookup 12 13 14 # what to report as the origin for templates that come from non-loader sources 15 # (e.g. strings) 16 UNKNOWN_SOURCE="<unknown source>" 17 18 TOKEN_TEXT = 0 19 TOKEN_VAR = 1 20 TOKEN_BLOCK = 2 21 TOKEN_COMMENT = 3 22 23 # template syntax constants 24 FILTER_SEPARATOR = '|' 25 FILTER_ARGUMENT_SEPARATOR = ':' 26 VARIABLE_ATTRIBUTE_SEPARATOR = '.' 27 BLOCK_TAG_START = '{%' 28 BLOCK_TAG_END = '%}' 29 VARIABLE_TAG_START = '{{' 30 VARIABLE_TAG_END = '}}' 31 COMMENT_TAG_START = '{#' 32 COMMENT_TAG_END = '#}' 33 SINGLE_BRACE_START = '{' 34 SINGLE_BRACE_END = '}' 35 36 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' 37 38 # match a variable or block tag and capture the entire tag, including start/end delimiters 39 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 40 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), 41 re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))) 42 43 def _render_value_in_context(value, context): 44 """ 45 Converts any value to a string to become part of a rendered template. This 46 means escaping, if required, and conversion to a unicode object. If value 47 is a string, it is expected to have already been translated. 48 """ 49 value = force_unicode(value) 50 if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData): 51 return escape(value) 52 else: 53 return value 54 55 class Origin(object): 56 def __init__(self, name): 57 self.name = name 58 59 def reload(self): 60 raise NotImplementedError 61 62 def __str__(self): 63 return self.name 64 65 class StringOrigin(Origin): 66 def __init__(self, source): 67 super(StringOrigin, self).__init__(UNKNOWN_SOURCE) 68 self.source = source 69 70 def reload(self): 71 return self.source 72 73 class TemplateSyntaxError(Exception): 74 def __str__(self): 75 try: 76 import cStringIO as StringIO 77 except ImportError: 78 import StringIO 79 output = StringIO.StringIO() 80 output.write(Exception.__str__(self)) 81 # Check if we wrapped an exception and print that too. 82 if hasattr(self, 'exc_info'): 83 import traceback 84 output.write('\n\nOriginal ') 85 e = self.exc_info 86 traceback.print_exception(e[0], e[1], e[2], 500, output) 87 return output.getvalue() 88 89 class TemplateEncodingError(Exception): 90 pass 91 92 class Template(object): 93 def __init__(self, template_string, origin=None, name='<Unknown Template>'): 94 try: 95 template_string = smart_unicode(template_string) 96 except UnicodeDecodeError: 97 raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.") 98 if settings.TEMPLATE_DEBUG and origin is None: 99 origin = StringOrigin(template_string) 100 self.nodelist = compile_string(template_string, origin) 101 self.name = name 102 103 def __iter__(self): 104 for node in self.nodelist: 105 for subnode in node: 106 yield subnode 107 108 def render(self, context): 109 "Display stage -- can be called many times" 110 return self.nodelist.render(context) 111 112 def compile_string(template_string, origin): 113 "Compiles template_string into NodeList ready for rendering" 114 if settings.TEMPLATE_DEBUG: 115 from debug import DebugLexer, DebugParser 116 lexer_class, parser_class = DebugLexer, DebugParser 117 else: 118 lexer_class, parser_class = Lexer, Parser 119 lexer = lexer_class(template_string, origin) 120 parser = parser_class(lexer.tokenize()) 121 return parser.parse() 122 123 class Token(object): 124 def __init__(self, token_type, contents): 125 # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT. 126 self.token_type, self.contents = token_type, contents 127 128 def __str__(self): 129 return '<%s token: "%s...">' % \ 130 ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type], 131 self.contents[:20].replace('\n', '')) 132 133 def split_contents(self): 134 split = [] 135 bits = iter(smart_split(self.contents)) 136 for bit in bits: 137 # Handle translation-marked template pieces 138 if bit.startswith('_("') or bit.startswith("_('"): 139 sentinal = bit[2] + ')' 140 trans_bit = [bit] 141 while not bit.endswith(sentinal): 142 bit = bits.next() 143 trans_bit.append(bit) 144 bit = ' '.join(trans_bit) 145 split.append(bit) 146 return split 147 148 class Lexer(object): 149 def __init__(self, template_string, origin): 150 self.template_string = template_string 151 self.origin = origin 152 153 def tokenize(self): 154 "Return a list of tokens from a given template_string." 155 in_tag = False 156 result = [] 157 for bit in tag_re.split(self.template_string): 158 if bit: 159 result.append(self.create_token(bit, in_tag)) 160 in_tag = not in_tag 161 return result 162 163 def create_token(self, token_string, in_tag): 164 """ 165 Convert the given token string into a new Token object and return it. 166 If in_tag is True, we are processing something that matched a tag, 167 otherwise it should be treated as a literal string. 168 """ 169 if in_tag: 170 if token_string.startswith(VARIABLE_TAG_START): 171 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 172 elif token_string.startswith(BLOCK_TAG_START): 173 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 174 elif token_string.startswith(COMMENT_TAG_START): 175 token = Token(TOKEN_COMMENT, '') 176 else: 177 token = Token(TOKEN_TEXT, token_string) 178 return token 179 180 class Parser(object): 181 def __init__(self, tokens): 182 self.tokens = tokens 183 self.tags = {} 184 self.filters = {} 185 from django.template.library import get_builtins 186 for lib in get_builtins(): 187 self.add_library(lib) 188 189 def parse(self, parse_until=None): 190 if parse_until is None: parse_until = [] 191 nodelist = self.create_nodelist() 192 while self.tokens: 193 token = self.next_token() 194 if token.token_type == TOKEN_TEXT: 195 self.extend_nodelist(nodelist, TextNode(token.contents), token) 196 elif token.token_type == TOKEN_VAR: 197 if not token.contents: 198 self.empty_variable(token) 199 filter_expression = self.compile_filter(token.contents) 200 var_node = self.create_variable_node(filter_expression) 201 self.extend_nodelist(nodelist, var_node,token) 202 elif token.token_type == TOKEN_BLOCK: 203 if token.contents in parse_until: 204 # put token back on token list so calling code knows why it terminated 205 self.prepend_token(token) 206 return nodelist 207 try: 208 command = token.contents.split()[0] 209 except IndexError: 210 self.empty_block_tag(token) 211 # execute callback function for this tag and append resulting node 212 self.enter_command(command, token) 213 try: 214 compile_func = self.tags[command] 215 except KeyError: 216 self.invalid_block_tag(token, command) 217 try: 218 compiled_result = compile_func(self, token) 219 except TemplateSyntaxError, e: 220 if not self.compile_function_error(token, e): 221 raise 222 self.extend_nodelist(nodelist, compiled_result, token) 223 self.exit_command() 224 if parse_until: 225 self.unclosed_block_tag(parse_until) 226 return nodelist 227 228 def parse_nodelist(self, until): 229 nodelist = self.parse((until,)) 230 self.delete_first_token() 231 return nodelist 232 233 def skip_past(self, endtag): 234 while self.tokens: 235 token = self.next_token() 236 if token.token_type == TOKEN_BLOCK and token.contents == endtag: 237 return 238 self.unclosed_block_tag([endtag]) 239 240 def create_variable_node(self, expression): 241 return ExpressionNode(expression) 242 243 def create_nodelist(self): 244 return NodeList() 245 246 def extend_nodelist(self, nodelist, node, token): 247 if node.must_be_first and nodelist: 248 try: 249 if nodelist.contains_nontext: 250 raise AttributeError 251 except AttributeError: 252 raise TemplateSyntaxError("%r must be the first tag in the template." % node) 253 if isinstance(nodelist, NodeList) and not isinstance(node, TextNode): 254 nodelist.contains_nontext = True 255 nodelist.append(node) 256 257 def enter_command(self, command, token): 258 pass 259 260 def exit_command(self): 261 pass 262 263 def error(self, token, msg): 264 return TemplateSyntaxError(msg) 265 266 def empty_variable(self, token): 267 raise self.error(token, "Empty variable tag") 268 269 def empty_block_tag(self, token): 270 raise self.error(token, "Empty block tag") 271 272 def invalid_block_tag(self, token, command): 273 raise self.error(token, "Invalid block tag: '%s'" % command) 274 275 def unclosed_block_tag(self, parse_until): 276 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until)) 277 278 def compile_function_error(self, token, e): 279 pass 280 281 def next_token(self): 282 return self.tokens.pop(0) 283 284 def prepend_token(self, token): 285 self.tokens.insert(0, token) 286 287 def delete_first_token(self): 288 del self.tokens[0] 289 290 def add_library(self, lib): 291 self.tags.update(lib.tags) 292 self.filters.update(lib.filters) 293 294 def token_stream(self, token): 295 return TokenStream(self, token) 296 297 def compile_filter(self, token): 298 stream = self.token_stream(token) 299 expr = stream.parse_expression(required=True) 300 if not stream.consumed(): 301 raise TemplateSyntaxError("Invalid filter expression") 302 return expr 303 304 def find_filter(self, filter_name): 305 if filter_name in self.filters: 306 return self.filters[filter_name] 307 else: 308 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name) 309 310 def filter_args_check(name, func, provided): 311 provided = list(provided) 312 plen = len(provided) 313 # Check to see if a decorator is providing the real function. 314 func = getattr(func, '_decorated_function', func) 315 args, varargs, varkw, defaults = getargspec(func) 316 317 if plen + 1 == len(args) or (defaults and plen + 1 <= len(args) + len(defaults)): 318 return True 319 320 # First argument is filter input. 321 args.pop(0) 322 if defaults: 323 nondefs = args[:-len(defaults)] 324 else: 325 nondefs = args 326 # Args without defaults must be provided. 327 try: 328 for arg in nondefs: 329 provided.pop(0) 330 except IndexError: 331 # Not enough 332 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) 333 334 # Defaults can be overridden. 335 defaults = defaults and list(defaults) or [] 336 try: 337 for parg in provided: 338 defaults.pop(0) 339 except IndexError: 340 # Too many. 341 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) 342 343 return True 344 345 346 def parse_lookup(lookup): 347 return Lookup(tuple(lookup.split(VARIABLE_ATTRIBUTE_SEPARATOR)), var=lookup) 348 349 EOT = '<EOT>' 350 punctuation_chars = r':|=,;<>!?%&@"\'/()\[\]{}`*+' 351 bit_re = re.compile(r""" 352 (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)') 353 |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*) 354 |(?P<char>[%s]) # punctuation 355 |(?P<name>[^\s%s]+) # keyword or variable 356 """ % ((punctuation_chars,) * 2), re.VERBOSE) 357 STRING_LITERAL = "string_literal" #1 358 NUMERIC_LITERAL = "numeric_literal" #2 359 CHAR = "char" #3 360 NAME = "name" #4 361 362 def _unescape_string_literal(literal): 363 q = literal[0] 364 return literal.replace(r'\%s' % q, q).replace(r'\\', '')[1:-1] 365 366 class TokenSyntaxError(Exception): 367 pass 368 369 def token_stream_parser(func): 370 def wrapper(self, required=False, *args, **kwargs): 371 mark = self.offset 372 try: 373 return func(self, *args, **kwargs) 374 except TokenSyntaxError: 375 if required: 376 #FIXME: hack 377 self.expected("<%s>" % ' '.join(func.__name__.split('_')[1:])) 378 self.offset = mark 379 raise 380 return wraps(func)(wrapper) 381 382 class TokenStream(object): 383 def __init__(self, parser, source): 384 self.parser = parser 385 self.source = source 386 self.offset = 0 387 self.name = None 388 self.token = None 389 if isinstance(source, Token): 390 bits = source.contents.split(None, 1) 391 self.source = len(bits) == 2 and bits[1] or '' 392 self.token = source 393 self.name = bits[0] 394 self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)] 395 396 def consumed(self): 397 return self.offset == len(self.tokens) 398 399 def pop(self): 400 if self.offset == len(self.tokens): 401 raise TokenSyntaxError 402 next = self.tokens[self.offset] 403 self.offset += 1 404 return next 405 406 def pop_lexeme(self, match, required=False): 407 if self.offset == len(self.tokens): 408 if required: 409 self.expected(match) 410 return False 411 tokentype, lexeme = self.tokens[self.offset] 412 if lexeme == match: 413 self.offset += 1 414 return True 415 if required: 416 self.expected(match) 417 return False 418 419 def pop_name(self, required=False): 420 if self.offset == len(self.tokens): 421 if required: 422 self.expected('<name>') 423 return None 424 tokentype, lexeme = self.tokens[self.offset] 425 if tokentype == NAME: 426 self.offset += 1 427 return lexeme 428 if required: 429 self.expected('<name>') 430 return None 431 432 def pushback(self): 433 self.offset -= 1 434 435 def syntax_error(self, msg): 436 if self.name: 437 msg = u"{%% %s %%} %s" % (self.name, msg) 438 raise TemplateSyntaxError(msg) 439 440 def expected(self, what): 441 if self.consumed(): 442 found = EOT 443 else: 444 found = "<%s> %s" % self.tokens[self.offset] 445 self.syntax_error("expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace'))) 446 447 #@token_stream_parser 448 def parse_string(self, bare=False): 449 tokentype, lexeme = self.pop() 450 if tokentype == STRING_LITERAL: 451 return _unescape_string_literal(lexeme) 452 if bare and tokentype == NAME: 453 return lexeme 454 raise TokenSyntaxError 455 parse_string = token_stream_parser(parse_string) 456 457 #@token_stream_parser 458 def parse_int(self): 459 token_type, lexeme = self.pop() 460 if token_type == NUMERIC_LITERAL: 461 try: 462 return int(lexeme) 463 except ValueError: 464 pass 465 raise TokenSyntaxError 466 parse_int = token_stream_parser(parse_int) 467 468 #@token_stream_parser 469 def parse_value(self): 470 translate = False 471 if self.pop_lexeme('_'): 472 if not self.pop_lexeme('('): 473 raise TokenSyntaxError 474 translate = True 475 476 tokentype, lexeme = self.pop() 477 if tokentype == NAME: 478 if lexeme.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexeme[0] == '_': 479 raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexeme) 480 value = parse_lookup(lexeme) 481 elif tokentype == STRING_LITERAL: 482 value = Literal(mark_safe(_unescape_string_literal(lexeme))) 483 elif tokentype == NUMERIC_LITERAL: 484 try: 485 value = float(lexeme) 486 except ValueError: 487 raise TokenSyntaxError 488 if '.' not in lexeme and 'e' not in lexeme.lower(): 489 value = int(value) 490 # This is a backwards compatibility hack: Literals with a trailing dot 491 # have been treated as an invalid variable before, thus resolved as None. 492 if lexeme.endswith('.'): 493 value = None 494 value = Literal(value) 495 elif tokentype == CHAR: 496 raise TokenSyntaxError 497 498 if translate: 499 if not self.pop_lexeme(')'): 500 raise TokenSyntaxError 501 # Don't pass the empty string to gettext, because the empty 502 # string translates to meta information 503 value = FilterExpression(value, [(lambda x: x and ugettext(x) or u'', ())]) 504 return value 505 parse_value = token_stream_parser(parse_value) 506 507 #@token_stream_parser 508 def parse_filter(self): 509 if not self.pop_lexeme('|'): 510 raise TokenSyntaxError 511 name = self.pop_name() 512 if not name: 513 raise TokenSyntaxError 514 args = [] 515 if self.pop_lexeme(':'): 516 args.append(self.parse_value()) 517 func = self.parser.find_filter(name) 518 filter_args_check(name, func, args) 519 return func, args 520 parse_filter = token_stream_parser(parse_filter) 521 522 #@token_stream_parser 523 def parse_expression(self): 524 var = self.parse_value() 525 filters = [] 526 try: 527 while True: 528 filters.append(self.parse_filter()) 529 except TokenSyntaxError: 530 pass 531 if filters: 532 return FilterExpression(var, filters) 533 return var 534 parse_expression = token_stream_parser(parse_expression) 535 536 #@token_stream_parser 537 def parse_expression_list(self, minimum=0, maximum=None, count=None): 538 expressions = [] 539 if count: 540 minimum = count 541 maximum = count 542 try: 543 while True: 544 if len(expressions) == maximum: 545 break 546 expressions.append(self.parse_expression()) 547 except TokenSyntaxError: 548 pass 549 if len(expressions) < minimum: 550 self.expected("expression") 551 return expressions 552 parse_expression_list = token_stream_parser(parse_expression_list) 553 554 def uses_token_stream(func): 555 def decorator(parser, token): 556 bits = parser.token_stream(token) 557 result = func(parser, bits) 558 if bits.offset != len(bits.tokens): 559 bits.expected(EOT) 560 return result 561 return wraps(func)(decorator) -
django/template/defaultfilters.py
13 13 except ImportError: 14 14 from django.utils.functional import wraps # Python 2.3, 2.4 fallback. 15 15 16 from django.template import Variable, Library 16 from django.template import Library 17 from django.template.parser import parse_lookup 17 18 from django.conf import settings 18 19 from django.utils.translation import ugettext, ungettext 19 20 from django.utils.encoding import force_unicode, iri_to_uri … … 467 468 Takes a list of dicts, returns that list sorted by the property given in 468 469 the argument. 469 470 """ 470 var_resolve = Variable(arg).resolve471 decorated = [( var_resolve(item), item) for item in value]471 lookup = parse_lookup(arg) 472 decorated = [(lookup.resolve(item), item) for item in value] 472 473 decorated.sort() 473 474 return [item[1] for item in decorated] 474 475 dictsort.is_safe = False … … 478 479 Takes a list of dicts, returns that list sorted in reverse order by the 479 480 property given in the argument. 480 481 """ 481 var_resolve = Variable(arg).resolve482 decorated = [( var_resolve(item), item) for item in value]482 lookup = parse_lookup(arg) 483 decorated = [(lookup.resolve(item), item) for item in value] 483 484 decorated.sort() 484 485 decorated.reverse() 485 486 return [item[1] for item in decorated] -
django/template/__init__.py
48 48 >>> t.render(c) 49 49 u'<html></html>' 50 50 """ 51 import re52 from inspect import getargspec53 54 from django.conf import settings55 51 from django.template.context import Context, RequestContext, ContextPopException 56 from django. utils.importlib import import_module57 from django. utils.itercompat import is_iterable58 from django. utils.functional import curry, Promise59 from django.utils.text import smart_split, unescape_string_literal 60 from django.utils.encoding import smart_unicode, force_unicode, smart_str 61 from django. utils.translation import ugettext as _62 from django. utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping63 from django. utils.html import escape52 from django.template.nodes import Node, NodeList, TextNode, ExpressionNode 53 from django.template.parser import Parser, Lexer, Token, Template, Origin, StringOrigin, TemplateSyntaxError, TemplateEncodingError, TokenStream, _render_value_in_context, compile_string 54 from django.template.parser import (BLOCK_TAG_START, BLOCK_TAG_END, 55 VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, 56 COMMENT_TAG_START, COMMENT_TAG_END, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK, TOKEN_COMMENT) 57 from django.template.loader import TemplateDoesNotExist 58 from django.template.library import Library, InvalidTemplateLibrary, get_library, add_to_builtins, libraries, builtins 59 from django.template.compat import Variable, resolve_variable, TokenParser, VariableDoesNotExist, VariableNode 64 60 65 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string' )61 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string', 'Library', 'Parser', 'TemplateDoesNotExist') 66 62 67 TOKEN_TEXT = 068 TOKEN_VAR = 169 TOKEN_BLOCK = 270 TOKEN_COMMENT = 371 72 # template syntax constants73 FILTER_SEPARATOR = '|'74 FILTER_ARGUMENT_SEPARATOR = ':'75 VARIABLE_ATTRIBUTE_SEPARATOR = '.'76 BLOCK_TAG_START = '{%'77 BLOCK_TAG_END = '%}'78 VARIABLE_TAG_START = '{{'79 VARIABLE_TAG_END = '}}'80 COMMENT_TAG_START = '{#'81 COMMENT_TAG_END = '#}'82 SINGLE_BRACE_START = '{'83 SINGLE_BRACE_END = '}'84 85 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'86 87 # what to report as the origin for templates that come from non-loader sources88 # (e.g. strings)89 UNKNOWN_SOURCE="<unknown source>"90 91 # match a variable or block tag and capture the entire tag, including start/end delimiters92 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),93 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),94 re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))95 96 # global dictionary of libraries that have been loaded using get_library97 libraries = {}98 # global list of libraries to load by default for a new parser99 builtins = []100 101 63 # True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means 102 64 # uninitialised. 103 65 invalid_var_format_string = None 104 105 class TemplateSyntaxError(Exception):106 def __str__(self):107 try:108 import cStringIO as StringIO109 except ImportError:110 import StringIO111 output = StringIO.StringIO()112 output.write(Exception.__str__(self))113 # Check if we wrapped an exception and print that too.114 if hasattr(self, 'exc_info'):115 import traceback116 output.write('\n\nOriginal ')117 e = self.exc_info118 traceback.print_exception(e[0], e[1], e[2], 500, output)119 return output.getvalue()120 121 class TemplateDoesNotExist(Exception):122 pass123 124 class TemplateEncodingError(Exception):125 pass126 127 class VariableDoesNotExist(Exception):128 129 def __init__(self, msg, params=()):130 self.msg = msg131 self.params = params132 133 def __str__(self):134 return unicode(self).encode('utf-8')135 136 def __unicode__(self):137 return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])138 139 class InvalidTemplateLibrary(Exception):140 pass141 142 class Origin(object):143 def __init__(self, name):144 self.name = name145 146 def reload(self):147 raise NotImplementedError148 149 def __str__(self):150 return self.name151 152 class StringOrigin(Origin):153 def __init__(self, source):154 super(StringOrigin, self).__init__(UNKNOWN_SOURCE)155 self.source = source156 157 def reload(self):158 return self.source159 160 class Template(object):161 def __init__(self, template_string, origin=None, name='<Unknown Template>'):162 try:163 template_string = smart_unicode(template_string)164 except UnicodeDecodeError:165 raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")166 if settings.TEMPLATE_DEBUG and origin is None:167 origin = StringOrigin(template_string)168 self.nodelist = compile_string(template_string, origin)169 self.name = name170 171 def __iter__(self):172 for node in self.nodelist:173 for subnode in node:174 yield subnode175 176 def render(self, context):177 "Display stage -- can be called many times"178 return self.nodelist.render(context)179 180 def compile_string(template_string, origin):181 "Compiles template_string into NodeList ready for rendering"182 if settings.TEMPLATE_DEBUG:183 from debug import DebugLexer, DebugParser184 lexer_class, parser_class = DebugLexer, DebugParser185 else:186 lexer_class, parser_class = Lexer, Parser187 lexer = lexer_class(template_string, origin)188 parser = parser_class(lexer.tokenize())189 return parser.parse()190 191 class Token(object):192 def __init__(self, token_type, contents):193 # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.194 self.token_type, self.contents = token_type, contents195 196 def __str__(self):197 return '<%s token: "%s...">' % \198 ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],199 self.contents[:20].replace('\n', ''))200 201 def split_contents(self):202 split = []203 bits = iter(smart_split(self.contents))204 for bit in bits:205 # Handle translation-marked template pieces206 if bit.startswith('_("') or bit.startswith("_('"):207 sentinal = bit[2] + ')'208 trans_bit = [bit]209 while not bit.endswith(sentinal):210 bit = bits.next()211 trans_bit.append(bit)212 bit = ' '.join(trans_bit)213 split.append(bit)214 return split215 216 class Lexer(object):217 def __init__(self, template_string, origin):218 self.template_string = template_string219 self.origin = origin220 221 def tokenize(self):222 "Return a list of tokens from a given template_string."223 in_tag = False224 result = []225 for bit in tag_re.split(self.template_string):226 if bit:227 result.append(self.create_token(bit, in_tag))228 in_tag = not in_tag229 return result230 231 def create_token(self, token_string, in_tag):232 """233 Convert the given token string into a new Token object and return it.234 If in_tag is True, we are processing something that matched a tag,235 otherwise it should be treated as a literal string.236 """237 if in_tag:238 if token_string.startswith(VARIABLE_TAG_START):239 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())240 elif token_string.startswith(BLOCK_TAG_START):241 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())242 elif token_string.startswith(COMMENT_TAG_START):243 token = Token(TOKEN_COMMENT, '')244 else:245 token = Token(TOKEN_TEXT, token_string)246 return token247 248 class Parser(object):249 def __init__(self, tokens):250 self.tokens = tokens251 self.tags = {}252 self.filters = {}253 for lib in builtins:254 self.add_library(lib)255 256 def parse(self, parse_until=None):257 if parse_until is None: parse_until = []258 nodelist = self.create_nodelist()259 while self.tokens:260 token = self.next_token()261 if token.token_type == TOKEN_TEXT:262 self.extend_nodelist(nodelist, TextNode(token.contents), token)263 elif token.token_type == TOKEN_VAR:264 if not token.contents:265 self.empty_variable(token)266 filter_expression = self.compile_filter(token.contents)267 var_node = self.create_variable_node(filter_expression)268 self.extend_nodelist(nodelist, var_node,token)269 elif token.token_type == TOKEN_BLOCK:270 if token.contents in parse_until:271 # put token back on token list so calling code knows why it terminated272 self.prepend_token(token)273 return nodelist274 try:275 command = token.contents.split()[0]276 except IndexError:277 self.empty_block_tag(token)278 # execute callback function for this tag and append resulting node279 self.enter_command(command, token)280 try:281 compile_func = self.tags[command]282 except KeyError:283 self.invalid_block_tag(token, command)284 try:285 compiled_result = compile_func(self, token)286 except TemplateSyntaxError, e:287 if not self.compile_function_error(token, e):288 raise289 self.extend_nodelist(nodelist, compiled_result, token)290 self.exit_command()291 if parse_until:292 self.unclosed_block_tag(parse_until)293 return nodelist294 295 def skip_past(self, endtag):296 while self.tokens:297 token = self.next_token()298 if token.token_type == TOKEN_BLOCK and token.contents == endtag:299 return300 self.unclosed_block_tag([endtag])301 302 def create_variable_node(self, filter_expression):303 return VariableNode(filter_expression)304 305 def create_nodelist(self):306 return NodeList()307 308 def extend_nodelist(self, nodelist, node, token):309 if node.must_be_first and nodelist:310 try:311 if nodelist.contains_nontext:312 raise AttributeError313 except AttributeError:314 raise TemplateSyntaxError("%r must be the first tag in the template." % node)315 if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):316 nodelist.contains_nontext = True317 nodelist.append(node)318 319 def enter_command(self, command, token):320 pass321 322 def exit_command(self):323 pass324 325 def error(self, token, msg):326 return TemplateSyntaxError(msg)327 328 def empty_variable(self, token):329 raise self.error(token, "Empty variable tag")330 331 def empty_block_tag(self, token):332 raise self.error(token, "Empty block tag")333 334 def invalid_block_tag(self, token, command):335 raise self.error(token, "Invalid block tag: '%s'" % command)336 337 def unclosed_block_tag(self, parse_until):338 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))339 340 def compile_function_error(self, token, e):341 pass342 343 def next_token(self):344 return self.tokens.pop(0)345 346 def prepend_token(self, token):347 self.tokens.insert(0, token)348 349 def delete_first_token(self):350 del self.tokens[0]351 352 def add_library(self, lib):353 self.tags.update(lib.tags)354 self.filters.update(lib.filters)355 356 def compile_filter(self, token):357 "Convenient wrapper for FilterExpression"358 return FilterExpression(token, self)359 360 def find_filter(self, filter_name):361 if filter_name in self.filters:362 return self.filters[filter_name]363 else:364 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)365 366 class TokenParser(object):367 """368 Subclass this and implement the top() method to parse a template line. When369 instantiating the parser, pass in the line from the Django template parser.370 371 The parser's "tagname" instance-variable stores the name of the tag that372 the filter was called with.373 """374 def __init__(self, subject):375 self.subject = subject376 self.pointer = 0377 self.backout = []378 self.tagname = self.tag()379 380 def top(self):381 "Overload this method to do the actual parsing and return the result."382 raise NotImplementedError()383 384 def more(self):385 "Returns True if there is more stuff in the tag."386 return self.pointer < len(self.subject)387 388 def back(self):389 "Undoes the last microparser. Use this for lookahead and backtracking."390 if not len(self.backout):391 raise TemplateSyntaxError("back called without some previous parsing")392 self.pointer = self.backout.pop()393 394 def tag(self):395 "A microparser that just returns the next tag from the line."396 subject = self.subject397 i = self.pointer398 if i >= len(subject):399 raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)400 p = i401 while i < len(subject) and subject[i] not in (' ', '\t'):402 i += 1403 s = subject[p:i]404 while i < len(subject) and subject[i] in (' ', '\t'):405 i += 1406 self.backout.append(self.pointer)407 self.pointer = i408 return s409 410 def value(self):411 "A microparser that parses for a value: some string constant or variable name."412 subject = self.subject413 i = self.pointer414 if i >= len(subject):415 raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)416 if subject[i] in ('"', "'"):417 p = i418 i += 1419 while i < len(subject) and subject[i] != subject[p]:420 i += 1421 if i >= len(subject):422 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))423 i += 1424 res = subject[p:i]425 while i < len(subject) and subject[i] in (' ', '\t'):426 i += 1427 self.backout.append(self.pointer)428 self.pointer = i429 return res430 else:431 p = i432 while i < len(subject) and subject[i] not in (' ', '\t'):433 if subject[i] in ('"', "'"):434 c = subject[i]435 i += 1436 while i < len(subject) and subject[i] != c:437 i += 1438 if i >= len(subject):439 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))440 i += 1441 s = subject[p:i]442 while i < len(subject) and subject[i] in (' ', '\t'):443 i += 1444 self.backout.append(self.pointer)445 self.pointer = i446 return s447 448 # This only matches constant *strings* (things in quotes or marked for449 # translation). Numbers are treated as variables for implementation reasons450 # (so that they retain their type when passed to filters).451 constant_string = r"""452 (?:%(i18n_open)s%(strdq)s%(i18n_close)s|453 %(i18n_open)s%(strsq)s%(i18n_close)s|454 %(strdq)s|455 %(strsq)s)456 """ % {457 'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string458 'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string459 'i18n_open' : re.escape("_("),460 'i18n_close' : re.escape(")"),461 }462 constant_string = constant_string.replace("\n", "")463 464 filter_raw_string = r"""465 ^(?P<constant>%(constant)s)|466 ^(?P<var>[%(var_chars)s]+|%(num)s)|467 (?:%(filter_sep)s468 (?P<filter_name>\w+)469 (?:%(arg_sep)s470 (?:471 (?P<constant_arg>%(constant)s)|472 (?P<var_arg>[%(var_chars)s]+|%(num)s)473 )474 )?475 )""" % {476 'constant': constant_string,477 'num': r'[-+\.]?\d[\d\.e]*',478 'var_chars': "\w\." ,479 'filter_sep': re.escape(FILTER_SEPARATOR),480 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),481 }482 483 filter_re = re.compile(filter_raw_string, re.UNICODE|re.VERBOSE)484 485 class FilterExpression(object):486 r"""487 Parses a variable token and its optional filters (all as a single string),488 and return a list of tuples of the filter name and arguments.489 Sample:490 >>> token = 'variable|default:"Default value"|date:"Y-m-d"'491 >>> p = Parser('')492 >>> fe = FilterExpression(token, p)493 >>> len(fe.filters)494 2495 >>> fe.var496 <Variable: 'variable'>497 498 This class should never be instantiated outside of the499 get_filters_from_token helper function.500 """501 def __init__(self, token, parser):502 self.token = token503 matches = filter_re.finditer(token)504 var_obj = None505 filters = []506 upto = 0507 for match in matches:508 start = match.start()509 if upto != start:510 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \511 (token[:upto], token[upto:start], token[start:]))512 if var_obj is None:513 var, constant = match.group("var", "constant")514 if constant:515 try:516 var_obj = Variable(constant).resolve({})517 except VariableDoesNotExist:518 var_obj = None519 elif var is None:520 raise TemplateSyntaxError("Could not find variable at start of %s." % token)521 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':522 raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)523 else:524 var_obj = Variable(var)525 else:526 filter_name = match.group("filter_name")527 args = []528 constant_arg, var_arg = match.group("constant_arg", "var_arg")529 if constant_arg:530 args.append((False, Variable(constant_arg).resolve({})))531 elif var_arg:532 args.append((True, Variable(var_arg)))533 filter_func = parser.find_filter(filter_name)534 self.args_check(filter_name,filter_func, args)535 filters.append( (filter_func,args))536 upto = match.end()537 if upto != len(token):538 raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))539 540 self.filters = filters541 self.var = var_obj542 543 def resolve(self, context, ignore_failures=False):544 if isinstance(self.var, Variable):545 try:546 obj = self.var.resolve(context)547 except VariableDoesNotExist:548 if ignore_failures:549 obj = None550 else:551 if settings.TEMPLATE_STRING_IF_INVALID:552 global invalid_var_format_string553 if invalid_var_format_string is None:554 invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID555 if invalid_var_format_string:556 return settings.TEMPLATE_STRING_IF_INVALID % self.var557 return settings.TEMPLATE_STRING_IF_INVALID558 else:559 obj = settings.TEMPLATE_STRING_IF_INVALID560 else:561 obj = self.var562 for func, args in self.filters:563 arg_vals = []564 for lookup, arg in args:565 if not lookup:566 arg_vals.append(mark_safe(arg))567 else:568 arg_vals.append(arg.resolve(context))569 if getattr(func, 'needs_autoescape', False):570 new_obj = func(obj, autoescape=context.autoescape, *arg_vals)571 else:572 new_obj = func(obj, *arg_vals)573 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):574 obj = mark_safe(new_obj)575 elif isinstance(obj, EscapeData):576 obj = mark_for_escaping(new_obj)577 else:578 obj = new_obj579 return obj580 581 def args_check(name, func, provided):582 provided = list(provided)583 plen = len(provided)584 # Check to see if a decorator is providing the real function.585 func = getattr(func, '_decorated_function', func)586 args, varargs, varkw, defaults = getargspec(func)587 # First argument is filter input.588 args.pop(0)589 if defaults:590 nondefs = args[:-len(defaults)]591 else:592 nondefs = args593 # Args without defaults must be provided.594 try:595 for arg in nondefs:596 provided.pop(0)597 except IndexError:598 # Not enough599 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))600 601 # Defaults can be overridden.602 defaults = defaults and list(defaults) or []603 try:604 for parg in provided:605 defaults.pop(0)606 except IndexError:607 # Too many.608 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))609 610 return True611 args_check = staticmethod(args_check)612 613 def __str__(self):614 return self.token615 616 def resolve_variable(path, context):617 """618 Returns the resolved variable, which may contain attribute syntax, within619 the given context.620 621 Deprecated; use the Variable class instead.622 """623 return Variable(path).resolve(context)624 625 class Variable(object):626 r"""627 A template variable, resolvable against a given context. The variable may be628 a hard-coded string (if it begins and ends with single or double quote629 marks)::630 631 >>> c = {'article': {'section':u'News'}}632 >>> Variable('article.section').resolve(c)633 u'News'634 >>> Variable('article').resolve(c)635 {'section': u'News'}636 >>> class AClass: pass637 >>> c = AClass()638 >>> c.article = AClass()639 >>> c.article.section = u'News'640 641 (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')642 """643 644 def __init__(self, var):645 self.var = var646 self.literal = None647 self.lookups = None648 self.translate = False649 650 try:651 # First try to treat this variable as a number.652 #653 # Note that this could cause an OverflowError here that we're not654 # catching. Since this should only happen at compile time, that's655 # probably OK.656 self.literal = float(var)657 658 # So it's a float... is it an int? If the original value contained a659 # dot or an "e" then it was a float, not an int.660 if '.' not in var and 'e' not in var.lower():661 self.literal = int(self.literal)662 663 # "2." is invalid664 if var.endswith('.'):665 raise ValueError666 667 except ValueError:668 # A ValueError means that the variable isn't a number.669 if var.startswith('_(') and var.endswith(')'):670 # The result of the lookup should be translated at rendering671 # time.672 self.translate = True673 var = var[2:-1]674 # If it's wrapped with quotes (single or double), then675 # we're also dealing with a literal.676 try:677 self.literal = mark_safe(unescape_string_literal(var))678 except ValueError:679 # Otherwise we'll set self.lookups so that resolve() knows we're680 # dealing with a bonafide variable681 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))682 683 def resolve(self, context):684 """Resolve this variable against a given context."""685 if self.lookups is not None:686 # We're dealing with a variable that needs to be resolved687 value = self._resolve_lookup(context)688 else:689 # We're dealing with a literal, so it's already been "resolved"690 value = self.literal691 if self.translate:692 return _(value)693 return value694 695 def __repr__(self):696 return "<%s: %r>" % (self.__class__.__name__, self.var)697 698 def __str__(self):699 return self.var700 701 def _resolve_lookup(self, context):702 """703 Performs resolution of a real variable (i.e. not a literal) against the704 given context.705 706 As indicated by the method's name, this method is an implementation707 detail and shouldn't be called by external code. Use Variable.resolve()708 instead.709 """710 current = context711 for bit in self.lookups:712 try: # dictionary lookup713 current = current[bit]714 except (TypeError, AttributeError, KeyError):715 try: # attribute lookup716 current = getattr(current, bit)717 if callable(current):718 if getattr(current, 'alters_data', False):719 current = settings.TEMPLATE_STRING_IF_INVALID720 else:721 try: # method call (assuming no args required)722 current = current()723 except TypeError: # arguments *were* required724 # GOTCHA: This will also catch any TypeError725 # raised in the function itself.726 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call727 except Exception, e:728 if getattr(e, 'silent_variable_failure', False):729 current = settings.TEMPLATE_STRING_IF_INVALID730 else:731 raise732 except (TypeError, AttributeError):733 try: # list-index lookup734 current = current[int(bit)]735 except (IndexError, # list index out of range736 ValueError, # invalid literal for int()737 KeyError, # current is a dict without `int(bit)` key738 TypeError, # unsubscriptable object739 ):740 raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute741 except Exception, e:742 if getattr(e, 'silent_variable_failure', False):743 current = settings.TEMPLATE_STRING_IF_INVALID744 else:745 raise746 747 return current748 749 class Node(object):750 # Set this to True for nodes that must be first in the template (although751 # they can be preceded by text nodes.752 must_be_first = False753 754 def render(self, context):755 "Return the node rendered as a string"756 pass757 758 def __iter__(self):759 yield self760 761 def get_nodes_by_type(self, nodetype):762 "Return a list of all nodes (within this node and its nodelist) of the given type"763 nodes = []764 if isinstance(self, nodetype):765 nodes.append(self)766 if hasattr(self, 'nodelist'):767 nodes.extend(self.nodelist.get_nodes_by_type(nodetype))768 return nodes769 770 class NodeList(list):771 # Set to True the first time a non-TextNode is inserted by772 # extend_nodelist().773 contains_nontext = False774 775 def render(self, context):776 bits = []777 for node in self:778 if isinstance(node, Node):779 bits.append(self.render_node(node, context))780 else:781 bits.append(node)782 return mark_safe(''.join([force_unicode(b) for b in bits]))783 784 def get_nodes_by_type(self, nodetype):785 "Return a list of all nodes of the given type"786 nodes = []787 for node in self:788 nodes.extend(node.get_nodes_by_type(nodetype))789 return nodes790 791 def render_node(self, node, context):792 return node.render(context)793 794 class TextNode(Node):795 def __init__(self, s):796 self.s = s797 798 def __repr__(self):799 return "<Text Node: '%s'>" % smart_str(self.s[:25], 'ascii',800 errors='replace')801 802 def render(self, context):803 return self.s804 805 def _render_value_in_context(value, context):806 """807 Converts any value to a string to become part of a rendered template. This808 means escaping, if required, and conversion to a unicode object. If value809 is a string, it is expected to have already been translated.810 """811 value = force_unicode(value)812 if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):813 return escape(value)814 else:815 return value816 817 class VariableNode(Node):818 def __init__(self, filter_expression):819 self.filter_expression = filter_expression820 821 def __repr__(self):822 return "<Variable Node: %s>" % self.filter_expression823 824 def render(self, context):825 try:826 output = self.filter_expression.resolve(context)827 except UnicodeDecodeError:828 # Unicode conversion can fail sometimes for reasons out of our829 # control (e.g. exception rendering). In that case, we fail quietly.830 return ''831 return _render_value_in_context(output, context)832 833 def generic_tag_compiler(params, defaults, name, node_class, parser, token):834 "Returns a template.Node subclass."835 bits = token.split_contents()[1:]836 bmax = len(params)837 def_len = defaults and len(defaults) or 0838 bmin = bmax - def_len839 if(len(bits) < bmin or len(bits) > bmax):840 if bmin == bmax:841 message = "%s takes %s arguments" % (name, bmin)842 else:843 message = "%s takes between %s and %s arguments" % (name, bmin, bmax)844 raise TemplateSyntaxError(message)845 return node_class(bits)846 847 class Library(object):848 def __init__(self):849 self.filters = {}850 self.tags = {}851 852 def tag(self, name=None, compile_function=None):853 if name == None and compile_function == None:854 # @register.tag()855 return self.tag_function856 elif name != None and compile_function == None:857 if(callable(name)):858 # @register.tag859 return self.tag_function(name)860 else:861 # @register.tag('somename') or @register.tag(name='somename')862 def dec(func):863 return self.tag(name, func)864 return dec865 elif name != None and compile_function != None:866 # register.tag('somename', somefunc)867 self.tags[name] = compile_function868 return compile_function869 else:870 raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))871 872 def tag_function(self,func):873 self.tags[getattr(func, "_decorated_function", func).__name__] = func874 return func875 876 def filter(self, name=None, filter_func=None):877 if name == None and filter_func == None:878 # @register.filter()879 return self.filter_function880 elif filter_func == None:881 if(callable(name)):882 # @register.filter883 return self.filter_function(name)884 else:885 # @register.filter('somename') or @register.filter(name='somename')886 def dec(func):887 return self.filter(name, func)888 return dec889 elif name != None and filter_func != None:890 # register.filter('somename', somefunc)891 self.filters[name] = filter_func892 return filter_func893 else:894 raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))895 896 def filter_function(self, func):897 self.filters[getattr(func, "_decorated_function", func).__name__] = func898 return func899 900 def simple_tag(self,func):901 params, xx, xxx, defaults = getargspec(func)902 903 class SimpleNode(Node):904 def __init__(self, vars_to_resolve):905 self.vars_to_resolve = map(Variable, vars_to_resolve)906 907 def render(self, context):908 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]909 return func(*resolved_vars)910 911 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)912 compile_func.__doc__ = func.__doc__913 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)914 return func915 916 def inclusion_tag(self, file_name, context_class=Context, takes_context=False):917 def dec(func):918 params, xx, xxx, defaults = getargspec(func)919 if takes_context:920 if params[0] == 'context':921 params = params[1:]922 else:923 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")924 925 class InclusionNode(Node):926 def __init__(self, vars_to_resolve):927 self.vars_to_resolve = map(Variable, vars_to_resolve)928 929 def render(self, context):930 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]931 if takes_context:932 args = [context] + resolved_vars933 else:934 args = resolved_vars935 936 dict = func(*args)937 938 if not getattr(self, 'nodelist', False):939 from django.template.loader import get_template, select_template940 if not isinstance(file_name, basestring) and is_iterable(file_name):941 t = select_template(file_name)942 else:943 t = get_template(file_name)944 self.nodelist = t.nodelist945 return self.nodelist.render(context_class(dict,946 autoescape=context.autoescape))947 948 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)949 compile_func.__doc__ = func.__doc__950 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)951 return func952 return dec953 954 def get_library(module_name):955 lib = libraries.get(module_name, None)956 if not lib:957 try:958 mod = import_module(module_name)959 except ImportError, e:960 raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))961 try:962 lib = mod.register963 libraries[module_name] = lib964 except AttributeError:965 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)966 return lib967 968 def add_to_builtins(module_name):969 builtins.append(get_library(module_name))970 971 add_to_builtins('django.template.defaulttags')972 add_to_builtins('django.template.defaultfilters') -
django/template/utils.py
1 import re 2 from django.utils.encoding import smart_str 3 from django.template.nodes import Node, NodeList 4 from django.template.parser import TokenSyntaxError, TemplateSyntaxError 5 6 class EmptyNode(Node): 7 def render(self, context): 8 return u'' 9 10 class ConditionalNode(Node): 11 def __init__(self, nodelist_true, nodelist_false): 12 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 13 14 def __iter__(self): 15 for node in self.nodelist_true: 16 yield node 17 for node in self.nodelist_false: 18 yield node 19 20 def get_nodes_by_type(self, nodetype): 21 nodes = [] 22 if isinstance(self, nodetype): 23 nodes.append(self) 24 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) 25 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) 26 return nodes 27 28 def check_condition(self, context): 29 return False 30 31 def render(self, context): 32 if self.check_condition(context): 33 return self.nodelist_true.render(context) 34 elif self.nodelist_false: 35 return self.nodelist_false.render(context) 36 return u"" 37 38 def parse_conditional_nodelists(parser, name): 39 end_tag = 'end' + name 40 nodelist_true = parser.parse(('else', end_tag)) 41 token = parser.next_token() 42 if token.contents == 'else': 43 nodelist_false = parser.parse((end_tag,)) 44 parser.delete_first_token() 45 else: 46 nodelist_false = NodeList() 47 return nodelist_true, nodelist_false 48 49 def parse_args_and_kwargs(bits, until=()): 50 args = [] 51 kwargs = {} 52 while True: 53 name = bits.pop_name() 54 if name in until: 55 bits.pushback() 56 break 57 if name and bits.pop_lexeme('='): 58 kwargs[name] = bits.parse_expression(required=True) 59 else: 60 if name: 61 bits.pushback() 62 try: 63 args.append(bits.parse_expression()) 64 except TokenSyntaxError: 65 break 66 if not bits.pop_lexeme(','): 67 break 68 return args, kwargs 69 70 def parse_as(bits, required=False): 71 if bits.pop_lexeme('as', required=required): 72 return bits.pop_name(required=required) 73 return None 74 75 def parse_context_bindings(bits, empty=False): 76 bindings = {} 77 try: 78 while True: 79 expression = bits.parse_expression() 80 name = parse_as(bits, required=True) 81 bindings[name] = expression 82 if not bits.pop_lexeme('and'): 83 break 84 except TokenSyntaxError: 85 if bindings or not bindings and not empty: 86 bits.expected('<expression> as <name>') 87 return bindings 88 89 def resolve_list(expressions, context, safe=False, default=None, ignore_errors=False): 90 result = [] 91 for exp in expressions: 92 try: 93 value = exp.resolve(context) 94 except LookupError: 95 if not safe and not ignore_errors: 96 raise 97 if safe: 98 value = default 99 else: 100 result.append(value) 101 return result 102 103 def resolve_dict(expressions, context, kwargs=False, safe=False, default=None, ignore_errors=False): 104 result = {} 105 for key, exp in expressions.items(): 106 try: 107 value = exp.resolve(context) 108 except LookupError: 109 if not safe and not ignore_errors: 110 raise 111 if safe: 112 value = default 113 else: 114 result[kwargs and smart_str(key, 'ascii') or key] = value 115 return result -
django/template/expressions.py
1 from django.conf import settings 2 from django.utils.encoding import force_unicode 3 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping 4 5 __all__ = ('LookupError', 'Expression', 'Variable', 'Literal', 'Lookup', 'FilterExpression') 6 7 class LookupError(Exception): 8 def __init__(self, var, msg, params=()): 9 self.var = var 10 self.msg = msg 11 self.params = params 12 13 def __str__(self): 14 return unicode(self).encode('utf-8') 15 16 def __unicode__(self): 17 return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params]) 18 19 20 class Expression(object): 21 def __init__(self, token=None): 22 self.token = token 23 24 def resolve_safe(self, context, default=None): 25 try: 26 return self.resolve(context) 27 except LookupError: 28 return default 29 30 def resolve(self, context, ignore_failures=False): 31 return None 32 33 34 class Literal(Expression): 35 def __init__(self, value, token=None): 36 super(Literal, self).__init__(token=token) 37 self.value = value 38 39 def resolve(self, context, ignore_failures=False): 40 return self.value 41 42 def __repr__(self): 43 return '<Literal %s>' % repr(self.value) 44 45 46 def handle_lookup_exception(e): 47 if getattr(e, 'silent_variable_failure', False): 48 return settings.TEMPLATE_STRING_IF_INVALID 49 raise e 50 51 class Lookup(Expression): 52 def __init__(self, lookups, var=None, token=None): 53 super(Lookup, self).__init__(token=token) 54 self.var = var 55 self.lookups = lookups 56 57 def __str__(self): 58 return "%s" % self.var 59 60 def __repr__(self): 61 return "<Lookup %s>" % self.var 62 63 def resolve(self, context, ignore_failures=False): 64 current = context 65 try: 66 for bit in self.lookups: 67 try: # dictionary lookup 68 current = current[bit] 69 except (TypeError, AttributeError, KeyError): 70 try: # attribute lookup 71 current = getattr(current, bit) 72 if callable(current): 73 if getattr(current, 'alters_data', False): 74 current = settings.TEMPLATE_STRING_IF_INVALID 75 else: 76 try: # method call (assuming no args required) 77 current = current() 78 except TypeError: # arguments *were* required 79 # GOTCHA: This will also catch any TypeError 80 # raised in the function itself. 81 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call 82 except Exception, e: 83 current = handle_lookup_exception(e) 84 except (TypeError, AttributeError): 85 try: # list-index lookup 86 current = current[int(bit)] 87 except (IndexError, # list index out of range 88 ValueError, # invalid literal for int() 89 KeyError, # current is a dict without `int(bit)` key 90 TypeError, # unsubscriptable object 91 ): 92 raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current)) 93 except Exception, e: 94 current = handle_lookup_exception(e) 95 except LookupError: 96 if ignore_failures: 97 return None 98 raise 99 return current 100 101 class FilterExpression(Expression): 102 def __init__(self, root, filters, token=None): 103 super(FilterExpression, self).__init__(token=token) 104 self.root = root 105 self.filters = filters 106 107 def __repr__(self): 108 return "<FilterExpression %s>" % "|".join(["%s:%s" % (func.__name__, arg) for func, arg in self.filters]) 109 110 def resolve(self, context, ignore_failures=False): 111 try: 112 obj = self.root.resolve(context) 113 except LookupError: 114 if ignore_failures: 115 obj = None 116 elif not self.filters: 117 raise 118 else: 119 obj = settings.TEMPLATE_STRING_IF_INVALID 120 for func, args in self.filters: 121 arg_vals = [] 122 for arg in args: 123 arg_vals.append(arg.resolve(context)) 124 if getattr(func, 'needs_autoescape', False): 125 new_obj = func(obj, autoescape=context.autoescape, *arg_vals) 126 else: 127 new_obj = func(obj, *arg_vals) 128 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData): 129 obj = mark_safe(new_obj) 130 elif isinstance(obj, EscapeData): 131 obj = mark_for_escaping(new_obj) 132 else: 133 obj = new_obj 134 return obj 135 136 def __str__(self): 137 return str(self.root)+'|<filtered>' -
django/template/defaulttags.py
8 8 except NameError: 9 9 from django.utils.itercompat import reversed # Python 2.3 fallback 10 10 11 from django.template import Node, NodeList, Template, Context, Variable 12 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END 13 from django.template import get_library, Library, InvalidTemplateLibrary 11 from django.template import Node, NodeList, Template, Context 12 from django.template.library import get_library, Library, InvalidTemplateLibrary 13 from django.template.parser import uses_token_stream, TokenSyntaxError, TemplateSyntaxError, parse_lookup 14 from django.template.parser import BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END 15 from django.template.utils import EmptyNode, ConditionalNode, parse_conditional_nodelists, parse_as, parse_args_and_kwargs, resolve_list, resolve_dict 14 16 from django.conf import settings 15 from django.utils.encoding import smart_str, smart_unicode16 17 from django.utils.itercompat import groupby 17 18 from django.utils.safestring import mark_safe 18 19 … … 33 34 else: 34 35 return output 35 36 36 class CommentNode(Node): 37 def render(self, context): 38 return '' 37 class CommentNode(EmptyNode): pass 39 38 40 39 class CycleNode(Node): 41 40 def __init__(self, cyclevars, variable_name=None): … … 74 73 75 74 def render(self, context): 76 75 for var in self.vars: 77 value = var.resolve (context, True)76 value = var.resolve_safe(context) 78 77 if value: 79 return smart_unicode(value)78 return value 80 79 return u'' 81 80 82 81 class ForNode(Node): … … 114 113 parentloop = context['forloop'] 115 114 else: 116 115 parentloop = {} 117 context.push() 118 try: 119 values = self.sequence.resolve(context, True) 120 except VariableDoesNotExist: 121 values = [] 116 context.push() 117 values = self.sequence.resolve_safe(context, []) 122 118 if values is None: 123 119 values = [] 124 120 if not hasattr(values, '__len__'): … … 163 159 context.pop() 164 160 return nodelist.render(context) 165 161 166 class IfChangedNode(Node): 167 def __init__(self, nodelist_true, nodelist_false, *varlist): 168 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 162 class IfChangedNode(ConditionalNode): 163 def __init__(self, nodelist_true, nodelist_false, expressions): 164 super(IfChangedNode, self).__init__(nodelist_true, nodelist_false) 165 self.expressions = expressions 169 166 self._last_seen = None 170 self._varlist = varlist171 167 self._id = str(id(self)) 172 168 173 def render(self, context):169 def check_condition(self, context): 174 170 if 'forloop' in context and self._id not in context['forloop']: 175 171 self._last_seen = None 176 172 context['forloop'][self._id] = 1 177 try:178 if self._varlist:179 # Consider multiple parameters. This automatically behaves180 # like an OR evaluation of the multiple variables.181 compare_to = [var.resolve(context, True) for var in self. _varlist]182 e lse:183 compare_to = self.nodelist_true.render(context)184 e xcept VariableDoesNotExist:185 compare_to = None173 if self.expressions: 174 # Consider multiple parameters. This automatically behaves 175 # like an OR evaluation of the multiple variables. 176 try: 177 compare_to = [var.resolve(context, True) for var in self.expressions] 178 except LookupError: 179 compare_to = None 180 else: 181 compare_to = self.nodelist_true.render(context) 186 182 187 183 if compare_to != self._last_seen: 188 firstloop = (self._last_seen == None)189 184 self._last_seen = compare_to 190 content = self.nodelist_true.render(context) 191 return content 192 elif self.nodelist_false: 193 return self.nodelist_false.render(context) 194 return '' 185 return True 186 return False 195 187 196 class IfEqualNode( Node):188 class IfEqualNode(ConditionalNode): 197 189 def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): 190 super(IfEqualNode, self).__init__(nodelist_true, nodelist_false) 198 191 self.var1, self.var2 = var1, var2 199 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false200 192 self.negate = negate 201 193 202 def __repr__(self): 203 return "<IfEqualNode>" 194 def check_condition(self, context): 195 val1 = self.var1.resolve_safe(context) 196 val2 = self.var2.resolve_safe(context) 197 return (self.negate and val1 != val2) or (not self.negate and val1 == val2) 204 198 205 def render(self, context): 206 val1 = self.var1.resolve(context, True) 207 val2 = self.var2.resolve(context, True) 208 if (self.negate and val1 != val2) or (not self.negate and val1 == val2): 209 return self.nodelist_true.render(context) 210 return self.nodelist_false.render(context) 211 212 class IfNode(Node): 199 class IfNode(ConditionalNode): 213 200 def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type): 201 super(IfNode, self).__init__(nodelist_true, nodelist_false) 214 202 self.bool_exprs = bool_exprs 215 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false216 203 self.link_type = link_type 217 204 218 def __repr__(self): 219 return "<If node>" 205 def check_condition(self, context): 206 or_link = self.link_type == IfNode.LinkTypes.or_ 207 for ifnot, bool_expr in self.bool_exprs: 208 value = bool(bool_expr.resolve_safe(context, False)) 209 if (ifnot != value) == or_link: 210 return or_link 211 return not or_link 220 212 221 def __iter__(self):222 for node in self.nodelist_true:223 yield node224 for node in self.nodelist_false:225 yield node226 227 def get_nodes_by_type(self, nodetype):228 nodes = []229 if isinstance(self, nodetype):230 nodes.append(self)231 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))232 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))233 return nodes234 235 def render(self, context):236 if self.link_type == IfNode.LinkTypes.or_:237 for ifnot, bool_expr in self.bool_exprs:238 try:239 value = bool_expr.resolve(context, True)240 except VariableDoesNotExist:241 value = None242 if (value and not ifnot) or (ifnot and not value):243 return self.nodelist_true.render(context)244 return self.nodelist_false.render(context)245 else:246 for ifnot, bool_expr in self.bool_exprs:247 try:248 value = bool_expr.resolve(context, True)249 except VariableDoesNotExist:250 value = None251 if not ((value and not ifnot) or (ifnot and not value)):252 return self.nodelist_false.render(context)253 return self.nodelist_true.render(context)254 255 213 class LinkTypes: 256 214 and_ = 0, 257 215 or_ = 1 … … 309 267 return '' # Fail silently for invalid included templates. 310 268 return output 311 269 312 class LoadNode(Node): 313 def render(self, context): 314 return '' 270 class LoadNode(EmptyNode): pass 315 271 316 272 class NowNode(Node): 317 273 def __init__(self, format_string): … … 321 277 from datetime import datetime 322 278 from django.utils.dateformat import DateFormat 323 279 df = DateFormat(datetime.now()) 324 return df.format(self.format_string )280 return df.format(self.format_string.resolve_safe(context, "")) 325 281 326 282 class SpacelessNode(Node): 327 283 def __init__(self, nodelist): … … 357 313 358 314 def render(self, context): 359 315 from django.core.urlresolvers import reverse, NoReverseMatch 360 args = [arg.resolve(context) for arg in self.args] 361 kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) 362 for k, v in self.kwargs.items()]) 316 args = resolve_list(self.args, context) 317 kwargs = resolve_dict(self.kwargs, context, kwargs=True) 363 318 364 319 # Try to look up the URL twice: once given the view name, and again 365 320 # relative to what we guess is the "main" app. If they both fail, … … 401 356 value = self.val_expr.resolve(context) 402 357 maxvalue = self.max_expr.resolve(context) 403 358 max_width = int(self.max_width.resolve(context)) 404 except VariableDoesNotExist:405 return ''359 except LookupError: 360 return u"" 406 361 except ValueError: 407 362 raise TemplateSyntaxError("widthratio final argument must be an number") 408 363 try: … … 441 396 arg = args[1] 442 397 if arg not in (u'on', u'off'): 443 398 raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'") 444 nodelist = parser.parse(('endautoescape',)) 445 parser.delete_first_token() 399 nodelist = parser.parse_nodelist('endautoescape') 446 400 return AutoEscapeControlNode((arg == 'on'), nodelist) 447 401 autoescape = register.tag(autoescape) 448 402 … … 556 510 for func, unused in filter_expr.filters: 557 511 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 558 512 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 559 nodelist = parser.parse(('endfilter',)) 560 parser.delete_first_token() 513 nodelist = parser.parse_nodelist('endfilter') 561 514 return FilterNode(filter_expr, nodelist) 562 515 do_filter = register.tag("filter", do_filter) 563 516 564 517 #@register.tag 565 def firstof(parser, token): 518 #@uses_token_stream 519 def firstof(parser, bits): 566 520 """ 567 521 Outputs the first variable passed that is not False, without escaping. 568 522 … … 596 550 {% endfilter %} 597 551 598 552 """ 599 bits = token.split_contents()[1:] 600 if len(bits) < 1: 601 raise TemplateSyntaxError("'firstof' statement requires at least one" 602 " argument") 603 return FirstOfNode([parser.compile_filter(bit) for bit in bits]) 604 firstof = register.tag(firstof) 553 return FirstOfNode(bits.parse_expression_list(minimum=1)) 554 firstof = register.tag(uses_token_stream(firstof)) 605 555 606 556 #@register.tag(name="for") 607 def do_for(parser, token): 557 #@uses_token_stream 558 def do_for(parser, bits): 608 559 """ 609 560 Loops over each item in an array. 610 561 … … 667 618 ========================== ================================================ 668 619 669 620 """ 670 bits = token.contents.split() 671 if len(bits) < 4: 672 raise TemplateSyntaxError("'for' statements should have at least four" 673 " words: %s" % token.contents) 621 loopvars = [] 622 while True: 623 name = bits.pop_name() 624 if not name: 625 if loopvars: 626 bits.pushback() #pushback the trailing comma 627 break 628 if name == 'in': 629 if loopvars: 630 bits.pushback() #pushback the trailing comma 631 bits.pushback() 632 break 633 loopvars.append(name) 634 if not bits.pop_lexeme(','): 635 break 636 if not loopvars: 637 raise TemplateSyntaxError("'for' statements should use the format: for loopvar[, ...] in expression") 674 638 675 is_reversed = bits[-1] == 'reversed' 676 in_index = is_reversed and -3 or -2 677 if bits[in_index] != 'in': 678 raise TemplateSyntaxError("'for' statements should use the format" 679 " 'for x in y': %s" % token.contents) 680 681 loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') 682 for var in loopvars: 683 if not var or ' ' in var: 684 raise TemplateSyntaxError("'for' tag received an invalid argument:" 685 " %s" % token.contents) 686 687 sequence = parser.compile_filter(bits[in_index+1]) 639 bits.pop_lexeme('in', required=True) 640 sequence = bits.parse_expression(required=True) 641 is_reversed = bits.pop_lexeme('reversed') 642 688 643 nodelist_loop = parser.parse(('empty', 'endfor',)) 689 644 token = parser.next_token() 690 645 if token.contents == 'empty': 691 nodelist_empty = parser.parse(('endfor',)) 692 parser.delete_first_token() 646 nodelist_empty = parser.parse_nodelist('endfor') 693 647 else: 694 648 nodelist_empty = None 695 649 return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty) 696 do_for = register.tag("for", do_for)650 do_for = register.tag("for", uses_token_stream(do_for)) 697 651 698 def do_ifequal(parser, token, negate):699 bits = list(token.split_contents())700 if len(bits) != 3:701 raise TemplateSyntaxError, "%r takes two arguments" % bits[0]702 end_tag = 'end' + bits[0]703 nodelist_true = parser.parse(('else', end_tag))704 token = parser.next_token()705 if token.contents == 'else':706 nodelist_false = parser.parse((end_tag,))707 parser.delete_first_token()708 else:709 nodelist_false = NodeList()710 val1 = parser.compile_filter(bits[1])711 val2 = parser.compile_filter(bits[2])712 return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)713 714 652 #@register.tag 715 def ifequal(parser, token): 653 #@uses_token_stream 654 def ifequal(parser, bits): 716 655 """ 717 656 Outputs the contents of the block if the two arguments equal each other. 718 657 … … 728 667 ... 729 668 {% endifnotequal %} 730 669 """ 731 return do_ifequal(parser, token, False) 732 ifequal = register.tag(ifequal) 670 exp1, exp2 = bits.parse_expression_list(count=2) 671 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name) 672 return IfEqualNode(exp1, exp2, nodelist_true, nodelist_false, False) 673 ifequal = register.tag(uses_token_stream(ifequal)) 733 674 734 675 #@register.tag 735 def ifnotequal(parser, token): 676 #@uses_token_stream 677 def ifnotequal(parser, bits): 736 678 """ 737 679 Outputs the contents of the block if the two arguments are not equal. 738 680 See ifequal. 739 681 """ 740 return do_ifequal(parser, token, True) 741 ifnotequal = register.tag(ifnotequal) 682 exp1, exp2 = bits.parse_expression_list(count=2) 683 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name) 684 return IfEqualNode(exp1, exp2, nodelist_true, nodelist_false, True) 685 ifnotequal = register.tag(uses_token_stream(ifnotequal)) 742 686 743 687 #@register.tag(name="if") 744 def do_if(parser, token): 688 #@uses_token_stream 689 def do_if(parser, bits): 745 690 """ 746 691 The ``{% if %}`` tag evaluates a variable, and if that variable is "true" 747 692 (i.e., exists, is not empty, and is not a false boolean value), the … … 799 744 {% endif %} 800 745 {% endif %} 801 746 """ 802 bits = token.contents.split()803 del bits[0]804 if not bits:805 raise TemplateSyntaxError("'if' statement requires at least one argument")806 # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']807 bitstr = ' '.join(bits)808 boolpairs = bitstr.split(' and ')809 747 boolvars = [] 810 if len(boolpairs) == 1: 811 link_type = IfNode.LinkTypes.or_ 812 boolpairs = bitstr.split(' or ') 813 else: 814 link_type = IfNode.LinkTypes.and_ 815 if ' or ' in bitstr: 748 link_type = None 749 while True: 750 negate = bits.pop_lexeme('not') 751 try: 752 boolvars.append((negate, bits.parse_expression())) 753 except TokenSyntaxError: 754 # If the variable is named "not" we have been too greedy 755 if negate: 756 boolvars.append((False, parse_lookup('not'))) 757 else: 758 bits.expected('<expression>') 759 link = bits.pop_name() 760 if not link: 761 break 762 if link not in ('and', 'or'): 763 bits.pushback() 764 break 765 if link_type and link != link_type: 816 766 raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'" 817 for boolpair in boolpairs:818 if ' ' in boolpair:819 try:820 not_, boolvar = boolpair.split()821 except ValueError:822 raise TemplateSyntaxError, "'if' statement improperly formatted"823 if not_ != 'not':824 raise TemplateSyntaxError, "Expected 'not' in if statement"825 boolvars.append((True, parser.compile_filter(boolvar)))826 767 else: 827 boolvars.append((False, parser.compile_filter(boolpair))) 828 nodelist_true = parser.parse(('else', 'endif')) 829 token = parser.next_token() 830 if token.contents == 'else': 831 nodelist_false = parser.parse(('endif',)) 832 parser.delete_first_token() 833 else: 834 nodelist_false = NodeList() 835 return IfNode(boolvars, nodelist_true, nodelist_false, link_type) 836 do_if = register.tag("if", do_if) 768 link_type = link 769 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if') 770 return IfNode(boolvars, nodelist_true, nodelist_false, link_type == 'and' and IfNode.LinkTypes.and_ or IfNode.LinkTypes.or_) 771 do_if = register.tag("if", uses_token_stream(do_if)) 837 772 838 773 #@register.tag 839 def ifchanged(parser, token): 774 #@uses_token_stream 775 def ifchanged(parser, bits): 840 776 """ 841 777 Checks if a value has changed from the last iteration of a loop. 842 778 … … 864 800 {% endifchanged %} 865 801 {% endfor %} 866 802 """ 867 bits = token.contents.split() 868 nodelist_true = parser.parse(('else', 'endifchanged')) 869 token = parser.next_token() 870 if token.contents == 'else': 871 nodelist_false = parser.parse(('endifchanged',)) 872 parser.delete_first_token() 873 else: 874 nodelist_false = NodeList() 875 values = [parser.compile_filter(bit) for bit in bits[1:]] 876 return IfChangedNode(nodelist_true, nodelist_false, *values) 877 ifchanged = register.tag(ifchanged) 803 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'ifchanged') 804 expressions = bits.parse_expression_list() 805 return IfChangedNode(nodelist_true, nodelist_false, expressions) 806 ifchanged = register.tag(uses_token_stream(ifchanged)) 878 807 879 808 #@register.tag 809 #@uses_token_stream 880 810 def ssi(parser, token): 881 811 """ 882 812 Outputs the contents of a given file into the page. … … 892 822 893 823 {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %} 894 824 """ 895 bits = token.contents.split() 896 parsed = False 897 if len(bits) not in (2, 3): 898 raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" 899 " the file to be included") 900 if len(bits) == 3: 901 if bits[2] == 'parsed': 902 parsed = True 903 else: 904 raise TemplateSyntaxError("Second (optional) argument to %s tag" 905 " must be 'parsed'" % bits[0]) 906 return SsiNode(bits[1], parsed) 907 ssi = register.tag(ssi) 825 path = bits.parse_string(bare=True, required=True) 826 parsed = bits.pop_lexeme('parsed') 827 828 return SsiNode(path, parsed) 829 ssi = register.tag(uses_token_stream(ssi)) 908 830 909 831 #@register.tag 910 832 def load(parser, token): … … 929 851 load = register.tag(load) 930 852 931 853 #@register.tag 932 def now(parser, token): 854 #@uses_token_stream 855 def now(parser, bits): 933 856 """ 934 857 Displays the date, formatted according to the given string. 935 858 … … 940 863 941 864 It is {% now "jS F Y H:i" %} 942 865 """ 943 bits = token.contents.split('"') 944 if len(bits) != 3: 945 raise TemplateSyntaxError, "'now' statement takes one argument" 946 format_string = bits[1] 947 return NowNode(format_string) 948 now = register.tag(now) 866 return NowNode(bits.parse_expression(required=True)) 867 now = register.tag(uses_token_stream(now)) 949 868 950 869 #@register.tag 951 def regroup(parser, token): 870 #@uses_token_stream 871 def regroup(parser, bits): 952 872 """ 953 873 Regroups a list of alike objects by a common attribute. 954 874 … … 994 914 {% regroup people|dictsort:"gender" by gender as grouped %} 995 915 996 916 """ 997 firstbits = token.contents.split(None, 3) 998 if len(firstbits) != 4: 999 raise TemplateSyntaxError, "'regroup' tag takes five arguments" 1000 target = parser.compile_filter(firstbits[1]) 1001 if firstbits[2] != 'by': 1002 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 1003 lastbits_reversed = firstbits[3][::-1].split(None, 2) 1004 if lastbits_reversed[1][::-1] != 'as': 1005 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" 1006 " be 'as'") 1007 1008 expression = parser.compile_filter(lastbits_reversed[2][::-1]) 1009 1010 var_name = lastbits_reversed[0][::-1] 917 target = bits.parse_expression(required=True) 918 bits.pop_lexeme('by', required=True) 919 expression = bits.parse_expression(required=True) 920 bits.pop_lexeme('as', required=True) 921 var_name = bits.pop_name(required=True) 1011 922 return RegroupNode(target, expression, var_name) 1012 regroup = register.tag( regroup)923 regroup = register.tag(uses_token_stream(regroup)) 1013 924 1014 925 def spaceless(parser, token): 1015 926 """ … … 1036 947 </strong> 1037 948 {% endspaceless %} 1038 949 """ 1039 nodelist = parser.parse(('endspaceless',)) 1040 parser.delete_first_token() 950 nodelist = parser.parse_nodelist('endspaceless') 1041 951 return SpacelessNode(nodelist) 1042 952 spaceless = register.tag(spaceless) 1043 953 … … 1075 985 return TemplateTagNode(tag) 1076 986 templatetag = register.tag(templatetag) 1077 987 1078 def url(parser, token): 988 #@register.tag 989 #@uses_token_stream 990 def url(parser, bits): 1079 991 """ 1080 992 Returns an absolute URL matching given view with its parameters. 1081 993 … … 1106 1018 1107 1019 The URL will look like ``/clients/client/123/``. 1108 1020 """ 1109 bits = token.split_contents() 1110 if len(bits) < 2: 1111 raise TemplateSyntaxError("'%s' takes at least one argument" 1112 " (path to a view)" % bits[0]) 1113 viewname = bits[1] 1114 args = [] 1115 kwargs = {} 1116 asvar = None 1117 1118 if len(bits) > 2: 1119 bits = iter(bits[2:]) 1120 for bit in bits: 1121 if bit == 'as': 1122 asvar = bits.next() 1123 break 1124 else: 1125 for arg in bit.split(","): 1126 if '=' in arg: 1127 k, v = arg.split('=', 1) 1128 k = k.strip() 1129 kwargs[k] = parser.compile_filter(v) 1130 elif arg: 1131 args.append(parser.compile_filter(arg)) 1021 viewname = bits.parse_string(bare=True, required=True) 1022 if bits.pop_lexeme(':'): 1023 viewname = "%s:%s" % (viewname, bits.parse_string(bare=True, required=True)) 1024 args, kwargs = parse_args_and_kwargs(bits, until=('as',)) 1025 asvar = parse_as(bits) 1132 1026 return URLNode(viewname, args, kwargs, asvar) 1133 url = register.tag(u rl)1027 url = register.tag(uses_token_stream(url)) 1134 1028 1135 1029 #@register.tag 1136 def widthratio(parser, token): 1030 #@uses_token_stream 1031 def widthratio(parser, bits): 1137 1032 """ 1138 1033 For creating bar charts and such, this tag calculates the ratio of a given 1139 1034 value to a maximum value, and then applies that ratio to a constant. … … 1146 1041 the above example will be 88 pixels wide (because 175/200 = .875; 1147 1042 .875 * 100 = 87.5 which is rounded up to 88). 1148 1043 """ 1149 bits = token.contents.split() 1150 if len(bits) != 4: 1151 raise TemplateSyntaxError("widthratio takes three arguments") 1152 tag, this_value_expr, max_value_expr, max_width = bits 1044 this_value_expr, max_value_expr, max_width = bits.parse_expression_list(count=3) 1153 1045 1154 return WidthRatioNode(parser.compile_filter(this_value_expr), 1155 parser.compile_filter(max_value_expr), 1156 parser.compile_filter(max_width)) 1157 widthratio = register.tag(widthratio) 1046 return WidthRatioNode(this_value_expr, max_value_expr, max_width) 1047 widthratio = register.tag(uses_token_stream(widthratio)) 1158 1048 1159 1049 #@register.tag 1160 def do_with(parser, token): 1050 #@uses_token_stream 1051 def do_with(parser, bits): 1161 1052 """ 1162 1053 Adds a value to the context (inside of this block) for caching and easy 1163 1054 access. … … 1168 1059 {{ total }} object{{ total|pluralize }} 1169 1060 {% endwith %} 1170 1061 """ 1171 bits = list(token.split_contents()) 1172 if len(bits) != 4 or bits[2] != "as": 1173 raise TemplateSyntaxError("%r expected format is 'value as name'" % 1174 bits[0]) 1175 var = parser.compile_filter(bits[1]) 1176 name = bits[3] 1177 nodelist = parser.parse(('endwith',)) 1178 parser.delete_first_token() 1179 return WithNode(var, name, nodelist) 1180 do_with = register.tag('with', do_with) 1062 expression = bits.parse_expression(required=True) 1063 name = parse_as(bits, required=True) 1064 nodelist = parser.parse_nodelist('endwith') 1065 return WithNode(expression, name, nodelist) 1066 do_with = register.tag('with', uses_token_stream(do_with)) -
django/template/compat.py
1 import warnings 2 from django.template.expressions import Expression, LookupError 3 from django.template.nodes import ExpressionNode 4 5 VariableDoesNotExist = LookupError 6 VariableNode = ExpressionNode 7 8 class Variable(Expression): 9 def __init__(self, var): 10 warnings.warn('Use Lookup instead of Variable.', DeprecationWarning, stacklevel=2) 11 self.var = var 12 from django.template.parser import TokenStream, TemplateSyntaxError 13 stream = TokenStream(None, var) 14 self.expression = stream.parse_value() 15 if not stream.consumed(): 16 raise TemplateSyntaxError("Invalid variable: %s" % var) 17 18 def resolve(self, context): 19 return self.expression.resolve(context) 20 21 def __repr__(self): 22 return "<%s: %r>" % (self.__class__.__name__, self.var) 23 24 def __str__(self): 25 return self.var 26 27 def resolve_variable(path, context): 28 """ 29 Returns the resolved variable, which may contain attribute syntax, within 30 the given context. 31 32 Deprecated. 33 """ 34 warnings.warn('Use Lookup instead of resolve_variable.', DeprecationWarning, stacklevel=2) 35 from django.template.parser import TokenStream, TemplateSyntaxError 36 stream = TokenStream(None, path) 37 val = stream.parse_value() 38 if not stream.consumed(): 39 raise TemplateSyntaxError("Invalid variable: %s" % path) 40 return val.resolve(context) 41 42 class TokenParser(object): 43 """ 44 Subclass this and implement the top() method to parse a template line. When 45 instantiating the parser, pass in the line from the Django template parser. 46 47 The parser's "tagname" instance-variable stores the name of the tag that 48 the filter was called with. 49 """ 50 def __init__(self, subject): 51 self.subject = subject 52 self.pointer = 0 53 self.backout = [] 54 self.tagname = self.tag() 55 56 def top(self): 57 "Overload this method to do the actual parsing and return the result." 58 raise NotImplementedError() 59 60 def more(self): 61 "Returns True if there is more stuff in the tag." 62 return self.pointer < len(self.subject) 63 64 def back(self): 65 "Undoes the last microparser. Use this for lookahead and backtracking." 66 if not len(self.backout): 67 # avoid cyclic imports 68 from django.template import TemplateSyntaxError 69 raise TemplateSyntaxError("back called without some previous parsing") 70 self.pointer = self.backout.pop() 71 72 def tag(self): 73 "A microparser that just returns the next tag from the line." 74 subject = self.subject 75 i = self.pointer 76 if i >= len(subject): 77 # avoid cyclic imports 78 from django.template import TemplateSyntaxError 79 raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject) 80 p = i 81 while i < len(subject) and subject[i] not in (' ', '\t'): 82 i += 1 83 s = subject[p:i] 84 while i < len(subject) and subject[i] in (' ', '\t'): 85 i += 1 86 self.backout.append(self.pointer) 87 self.pointer = i 88 return s 89 90 def value(self): 91 "A microparser that parses for a value: some string constant or variable name." 92 subject = self.subject 93 i = self.pointer 94 if i >= len(subject): 95 # avoid cyclic imports 96 from django.template import TemplateSyntaxError 97 raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject) 98 if subject[i] in ('"', "'"): 99 p = i 100 i += 1 101 while i < len(subject) and subject[i] != subject[p]: 102 i += 1 103 if i >= len(subject): 104 # avoid cyclic imports 105 from django.template import TemplateSyntaxError 106 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) 107 i += 1 108 res = subject[p:i] 109 while i < len(subject) and subject[i] in (' ', '\t'): 110 i += 1 111 self.backout.append(self.pointer) 112 self.pointer = i 113 return res 114 else: 115 p = i 116 while i < len(subject) and subject[i] not in (' ', '\t'): 117 if subject[i] in ('"', "'"): 118 c = subject[i] 119 i += 1 120 while i < len(subject) and subject[i] != c: 121 i += 1 122 if i >= len(subject): 123 # avoid cyclic imports 124 from django.template import TemplateSyntaxError 125 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) 126 i += 1 127 s = subject[p:i] 128 while i < len(subject) and subject[i] in (' ', '\t'): 129 i += 1 130 self.backout.append(self.pointer) 131 self.pointer = i 132 return s -
django/template/library.py
1 from inspect import getargspec 2 from django.template.context import Context 3 from django.template.nodes import Node 4 from django.template.utils import resolve_list 5 from django.utils.importlib import import_module 6 from django.utils.itercompat import is_iterable 7 from django.utils.functional import curry 8 9 # global dictionary of libraries that have been loaded using get_library 10 libraries = {} 11 # global list of libraries to load by default for a new parser 12 builtins = [] 13 14 class InvalidTemplateLibrary(Exception): 15 pass 16 17 def generic_tag_compiler(params, defaults, name, node_class, parser, bits): 18 "Returns a template.Node subclass." 19 bmax = len(params) 20 def_len = defaults and len(defaults) or 0 21 bmin = bmax - def_len 22 expressions = bits.parse_expression_list(minimum=bmin, maximum=bmax) 23 return node_class(expressions) 24 25 class Library(object): 26 def __init__(self): 27 self.filters = {} 28 self.tags = {} 29 30 def tag(self, name=None, compile_function=None): 31 if name == None and compile_function == None: 32 # @register.tag() 33 return self.tag_function 34 elif name != None and compile_function == None: 35 if(callable(name)): 36 # @register.tag 37 return self.tag_function(name) 38 else: 39 # @register.tag('somename') or @register.tag(name='somename') 40 def dec(func): 41 return self.tag(name, func) 42 return dec 43 elif name != None and compile_function != None: 44 # register.tag('somename', somefunc) 45 self.tags[name] = compile_function 46 return compile_function 47 else: 48 raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)) 49 50 def tag_function(self,func): 51 self.tags[getattr(func, "_decorated_function", func).__name__] = func 52 return func 53 54 def filter(self, name=None, filter_func=None): 55 if name == None and filter_func == None: 56 # @register.filter() 57 return self.filter_function 58 elif filter_func == None: 59 if(callable(name)): 60 # @register.filter 61 return self.filter_function(name) 62 else: 63 # @register.filter('somename') or @register.filter(name='somename') 64 def dec(func): 65 return self.filter(name, func) 66 return dec 67 elif name != None and filter_func != None: 68 # register.filter('somename', somefunc) 69 self.filters[name] = filter_func 70 return filter_func 71 else: 72 raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)) 73 74 def filter_function(self, func): 75 self.filters[getattr(func, "_decorated_function", func).__name__] = func 76 return func 77 78 def simple_tag(self,func): 79 params, xx, xxx, defaults = getargspec(func) 80 81 class SimpleNode(Node): 82 def __init__(self, args): 83 self.args = args 84 85 def render(self, context): 86 return func(*resolve_list(self.args, context)) 87 88 from django.template.parser import uses_token_stream 89 compile_func = uses_token_stream(curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)) 90 compile_func.__doc__ = func.__doc__ 91 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) 92 return func 93 94 def inclusion_tag(self, file_name, context_class=Context, takes_context=False): 95 def dec(func): 96 params, xx, xxx, defaults = getargspec(func) 97 if takes_context: 98 if params[0] == 'context': 99 params = params[1:] 100 else: 101 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'") 102 103 class InclusionNode(Node): 104 def __init__(self, args): 105 self.args = args 106 107 def render(self, context): 108 args = resolve_list(self.args, context) 109 if takes_context: 110 args = [context] + args 111 context_dict = func(*args) 112 113 if not getattr(self, 'nodelist', False): 114 from django.template.loader import get_template, select_template 115 if not isinstance(file_name, basestring) and is_iterable(file_name): 116 t = select_template(file_name) 117 else: 118 t = get_template(file_name) 119 self.nodelist = t.nodelist 120 return self.nodelist.render(context_class(context_dict, autoescape=context.autoescape)) 121 122 from django.template.parser import uses_token_stream 123 compile_func = uses_token_stream(curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)) 124 compile_func.__doc__ = func.__doc__ 125 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) 126 return func 127 return dec 128 129 def get_library(module_name): 130 lib = libraries.get(module_name, None) 131 if not lib: 132 try: 133 mod = import_module(module_name) 134 except ImportError, e: 135 raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e)) 136 try: 137 lib = mod.register 138 libraries[module_name] = lib 139 except AttributeError: 140 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name) 141 return lib 142 143 def add_to_builtins(module_name, lazy=False): 144 builtins.append(lazy and module_name or get_library(module_name)) 145 146 def get_builtins(): 147 for index, lib in enumerate(builtins): 148 if isinstance(lib, str): 149 builtins[index] = get_library(lib) 150 yield builtins[index] 151 152 add_to_builtins('django.template.defaulttags', True) 153 add_to_builtins('django.template.defaultfilters', True) 154 -
django/template/loader_tags.py
1 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable2 1 from django.template import Library, Node, TextNode 3 from django.template.loader import get_template, get_template_from_string, find_template_source 2 from django.template.loader import get_template, get_template_from_string, find_template_source, TemplateDoesNotExist 3 from django.template.parser import TemplateSyntaxError, uses_token_stream 4 from django.template.expressions import Literal 4 5 from django.conf import settings 5 6 from django.utils.safestring import mark_safe 6 7 … … 39 40 class ExtendsNode(Node): 40 41 must_be_first = True 41 42 42 def __init__(self, nodelist, parent_name , parent_name_expr, template_dirs=None):43 def __init__(self, nodelist, parent_name_expr, template_dirs=None): 43 44 self.nodelist = nodelist 44 self.parent_name , self.parent_name_expr = parent_name,parent_name_expr45 self.parent_name_expr = parent_name_expr 45 46 self.template_dirs = template_dirs 46 47 47 48 def __repr__(self): 48 if self.parent_name_expr: 49 return "<ExtendsNode: extends %s>" % self.parent_name_expr.token 50 return '<ExtendsNode: extends "%s">' % self.parent_name 49 return "<ExtendsNode: extends %s>" % self.parent_name_expr 51 50 52 51 def get_parent(self, context): 53 if self.parent_name_expr: 54 self.parent_name = self.parent_name_expr.resolve(context) 55 parent = self.parent_name 52 parent = self.parent_name_expr.resolve_safe(context) 56 53 if not parent: 57 error_msg = "Invalid template name in 'extends' tag: %r." % parent 58 if self.parent_name_expr: 59 error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token 54 error_msg = "Invalid template name in 'extends' tag: %r. Got this from '%s'." % (parent, self.parent_name_expr) 60 55 raise TemplateSyntaxError, error_msg 61 56 if hasattr(parent, 'render'): 62 57 return parent # parent is a Template object … … 114 109 115 110 class IncludeNode(Node): 116 111 def __init__(self, template_name): 117 self.template_name = Variable(template_name)112 self.template_name = template_name 118 113 119 114 def render(self, context): 120 115 try: … … 148 143 parser.delete_first_token() 149 144 return BlockNode(block_name, nodelist) 150 145 151 def do_extends(parser, token): 146 #@uses_token_stream 147 def do_extends(parser, bits): 152 148 """ 153 149 Signal that this template extends a parent template. 154 150 … … 158 154 name of the parent template to extend (if it evaluates to a string) or as 159 155 the parent tempate itelf (if it evaluates to a Template object). 160 156 """ 161 bits = token.split_contents() 162 if len(bits) != 2: 163 raise TemplateSyntaxError, "'%s' takes one argument" % bits[0] 164 parent_name, parent_name_expr = None, None 165 if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]: 166 parent_name = bits[1][1:-1] 167 else: 168 parent_name_expr = parser.compile_filter(bits[1]) 157 parent_name_expr = bits.parse_expression() 169 158 nodelist = parser.parse() 170 159 if nodelist.get_nodes_by_type(ExtendsNode): 171 160 raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0] 172 return ExtendsNode(nodelist, parent_name , parent_name_expr)161 return ExtendsNode(nodelist, parent_name_expr) 173 162 174 def do_include(parser, token): 163 #@uses_token_stream 164 def do_include(parser, bits): 175 165 """ 176 166 Loads a template and renders it with the current context. 177 167 … … 179 169 180 170 {% include "foo/some_include" %} 181 171 """ 182 bits = token.split_contents() 183 if len(bits) != 2: 184 raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0] 185 path = bits[1] 186 if path[0] in ('"', "'") and path[-1] == path[0]: 187 return ConstantIncludeNode(path[1:-1]) 188 return IncludeNode(bits[1]) 172 expression = bits.parse_expression(required=True) 173 if isinstance(expression, Literal): 174 return ConstantIncludeNode(expression.value) 175 return IncludeNode(expression) 189 176 190 177 register.tag('block', do_block) 191 register.tag('extends', do_extends)192 register.tag('include', do_include)178 register.tag('extends', uses_token_stream(do_extends)) 179 register.tag('include', uses_token_stream(do_include)) -
django/template/loader.py
21 21 # installed, because pkg_resources is necessary to read eggs. 22 22 23 23 from django.core.exceptions import ImproperlyConfigured 24 from django.template import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins 24 from django.template.parser import Origin, Template 25 from django.template.library import add_to_builtins 26 from django.template.context import Context 25 27 from django.utils.importlib import import_module 26 28 from django.conf import settings 27 29 28 30 template_source_loaders = None 29 31 32 class TemplateDoesNotExist(Exception): 33 pass 34 30 35 class LoaderOrigin(Origin): 31 36 def __init__(self, display_name, loader, name, dirs): 32 37 super(LoaderOrigin, self).__init__(display_name) … … 117 122 # If we get here, none of the templates could be loaded 118 123 raise TemplateDoesNotExist, ', '.join(template_name_list) 119 124 120 add_to_builtins('django.template.loader_tags') 125 add_to_builtins('django.template.loader_tags', True) 126 -
django/template/debug.py
1 from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError 1 from django.template import Lexer, Parser, NodeList, ExpressionNode, TemplateSyntaxError 2 from django.template.parser import tag_re 2 3 from django.utils.encoding import force_unicode 3 4 from django.utils.html import escape 4 5 from django.utils.safestring import SafeData, EscapeData … … 50 51 return DebugNodeList() 51 52 52 53 def create_variable_node(self, contents): 53 return Debug VariableNode(contents)54 return DebugExpressionNode(contents) 54 55 55 56 def extend_nodelist(self, nodelist, node, token): 56 57 node.source = token.source … … 81 82 raise wrapped 82 83 return result 83 84 84 class Debug VariableNode(VariableNode):85 class DebugExpressionNode(ExpressionNode): 85 86 def render(self, context): 86 87 try: 87 output = force_unicode(self.filter_expression.resolve(context))88 return super(DebugExpressionNode, self).render(context) 88 89 except TemplateSyntaxError, e: 89 90 if not hasattr(e, 'source'): 90 91 e.source = self.source 91 92 raise 92 93 except UnicodeDecodeError: 93 94 return '' 94 if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):95 return escape(output)96 else:97 return output -
tests/regressiontests/templates/parser.py
3 3 """ 4 4 5 5 filter_parsing = r""" 6 >>> from django.template import FilterExpression,Parser6 >>> from django.template import Parser 7 7 8 8 >>> c = {'article': {'section': u'News'}} 9 9 >>> p = Parser("") 10 >>> def fe_test(s): return FilterExpression(s, p).resolve(c)10 >>> def fe_test(s): return p.compile_filter(s).resolve(c) 11 11 12 12 >>> fe_test('article.section') 13 13 u'News' … … 22 22 >>> fe_test(ur"'Some \'Bad\' News'") 23 23 u"Some 'Bad' News" 24 24 25 >>> fe = FilterExpression(ur'"Some \"Good\" News"', p)26 >>> fe.filters27 []28 >>> fe.var29 u'Some "Good" News'30 25 """ 31 26 32 27 variable_parsing = r""" -
tests/regressiontests/templates/tests.py
370 370 # Chained filters 371 371 'filter-syntax02': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"), 372 372 373 # Raise TemplateSyntaxError forspace between a variable and filter pipe374 'filter-syntax03': ("{{ var |upper }}", { }, template.TemplateSyntaxError),373 # Basic filter usage with space between a variable and filter pipe 374 'filter-syntax03': ("{{ var |upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"), 375 375 376 # Raise TemplateSyntaxError forspace after a filter pipe377 'filter-syntax04': ("{{ var| upper }}", { }, template.TemplateSyntaxError),376 # Basic filter usage with space after a filter pipe 377 'filter-syntax04': ("{{ var| upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"), 378 378 379 379 # Raise TemplateSyntaxError for a nonexistent filter 380 380 'filter-syntax05': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),