def test_markup_operations(self): # adding two strings should escape the unsafe one unsafe = '<script type="application/x-some-script">alert("foo");</script>' safe = Markup('<em>username</em>') assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe) # string interpolations are safe to use too assert Markup('<em>%s</em>') % '<bad user>' == \ '<em><bad user></em>' assert Markup('<em>%(username)s</em>') % { 'username': '******' } == '<em><bad user></em>' # an escaped object is markup too assert type(Markup('foo') + 'bar') is Markup # and it implements __html__ by returning itself x = Markup("foo") assert x.__html__() is x # it also knows how to treat __html__ objects class Foo(object): def __html__(self): return '<em>awesome</em>' def __unicode__(self): return 'awesome' assert Markup(Foo()) == '<em>awesome</em>' assert Markup('<strong>%s</strong>') % Foo() == \ '<strong><em>awesome</em></strong>' # escaping and unescaping assert escape('"<>&\'') == '"<>&'' assert Markup("<em>Foo & Bar</em>").striptags() == "Foo & Bar" assert Markup("<test>").unescape() == "<test>"
def do_replace(eval_ctx, s, old, new, count=None): """Return a copy of the value with all occurrences of a substring replaced with a new one. The first argument is the substring that should be replaced, the second is the replacement string. If the optional third argument ``count`` is given, only the first ``count`` occurrences are replaced: .. sourcecode:: jinja {{ "Hello World"|replace("Hello", "Goodbye") }} -> Goodbye World {{ "aaaaargh"|replace("a", "d'oh, ", 2) }} -> d'oh, d'oh, aaargh """ if count is None: count = -1 if not eval_ctx.autoescape: return text_type(s).replace(text_type(old), text_type(new), count) if hasattr(old, '__html__') or hasattr(new, '__html__') and \ not hasattr(s, '__html__'): s = escape(s) else: s = soft_unicode(s) return s.replace(soft_unicode(old), soft_unicode(new), count)
def unicode_urlencode(obj, charset='utf-8'): """URL escapes a single bytestring or unicode string with the given charset if applicable to URL safe quoting under all rules that need to be considered under all supported Python versions. If non strings are provided they are converted to their unicode representation first. """ if not isinstance(obj, string_types): obj = text_type(obj) if isinstance(obj, text_type): obj = obj.encode(charset) return text_type(url_quote(obj))
def preprocess(self, source, name=None, filename=None): """Preprocesses the source with all extensions. This is automatically called for all parsing and compiling methods but *not* for :meth:`lex` because there you usually only want the actual source tokenized. """ return reduce(lambda s, e: e.preprocess(s, name, filename), self.iter_extensions(), text_type(source))
def urlize(text, trim_url_limit=None, rel=None, target=None): """Converts any URLs in text into clickable links. Works on http://, https:// and www. links. Links can have trailing punctuation (periods, commas, close-parens) and leading punctuation (opening parens) and it'll still do the right thing. If trim_url_limit is not None, the URLs in link text will be limited to trim_url_limit characters. If nofollow is True, the URLs in link text will get a rel="nofollow" attribute. If target is not None, a target attribute will be added to the link. """ trim_url = lambda x, limit=trim_url_limit: limit is not None \ and (x[:limit] + (len(x) >=limit and '...' or '')) or x words = _word_split_re.split(text_type(escape(text))) rel_attr = rel and ' rel="%s"' % text_type(escape(rel)) or '' target_attr = target and ' target="%s"' % escape(target) or '' for i, word in enumerate(words): match = _punctuation_re.match(word) if match: lead, middle, trail = match.groups() if middle.startswith('www.') or ( '@' not in middle and not middle.startswith('http://') and not middle.startswith('https://') and len(middle) > 0 and middle[0] in _letters + _digits and ( middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com') )): middle = '<a href="http://%s"%s%s>%s</a>' % (middle, rel_attr, target_attr, trim_url(middle)) if middle.startswith('http://') or \ middle.startswith('https://'): middle = '<a href="%s"%s%s>%s</a>' % (middle, rel_attr, target_attr, trim_url(middle)) if '@' in middle and not middle.startswith('www.') and \ not ':' in middle and _simple_email_re.match(middle): middle = '<a href="mailto:%s">%s</a>' % (middle, middle) if lead + middle + trail != word: words[i] = lead + middle + trail return u''.join(words)
def do_join(eval_ctx, value, d=u'', attribute=None): """Return a string which is the concatenation of the strings in the sequence. The separator between elements is an empty string per default, you can define it with the optional parameter: .. sourcecode:: jinja {{ [1, 2, 3]|join('|') }} -> 1|2|3 {{ [1, 2, 3]|join }} -> 123 It is also possible to join certain attributes of an object: .. sourcecode:: jinja {{ users|join(', ', attribute='username') }} .. versionadded:: 2.6 The `attribute` parameter was added. """ if attribute is not None: value = imap(make_attrgetter(eval_ctx.environment, attribute), value) # no automatic escaping? joining is a lot eaiser then if not eval_ctx.autoescape: return text_type(d).join(imap(text_type, value)) # if the delimiter doesn't have an html representation we check # if any of the items has. If yes we do a coercion to Markup if not hasattr(d, '__html__'): value = list(value) do_escape = False for idx, item in enumerate(value): if hasattr(item, '__html__'): do_escape = True else: value[idx] = text_type(item) if do_escape: d = escape(d) else: d = text_type(d) return d.join(value) # no html involved, to normal joining return soft_unicode(d).join(imap(soft_unicode, value))
def unicode_urlencode(obj, charset='utf-8', for_qs=False): """URL escapes a single bytestring or unicode string with the given charset if applicable to URL safe quoting under all rules that need to be considered under all supported Python versions. If non strings are provided they are converted to their unicode representation first. """ if not isinstance(obj, string_types): obj = text_type(obj) if isinstance(obj, text_type): obj = obj.encode(charset) safe = not for_qs and b'/' or b'' rv = text_type(url_quote(obj, safe)) if for_qs: rv = rv.replace('%20', '+') return rv
def test_template_data(self): env = Environment(autoescape=True) t = env.from_string('{% macro say_hello(name) %}' '<p>Hello {{ name }}!</p>{% endmacro %}' '{{ say_hello("<blink>foo</blink>") }}') escaped_out = '<p>Hello <blink>foo</blink>!</p>' assert t.render() == escaped_out assert text_type(t.module) == escaped_out assert escape(t.module) == escaped_out assert t.module.say_hello('<blink>foo</blink>') == escaped_out assert escape(t.module.say_hello('<blink>foo</blink>')) == escaped_out
def lex(self, source, name=None, filename=None): """Lex the given sourcecode and return a generator that yields tokens as tuples in the form ``(lineno, token_type, value)``. This can be useful for :ref:`extension development <writing-extensions>` and debugging templates. This does not perform preprocessing. If you want the preprocessing of the extensions to be applied you have to filter source through the :meth:`preprocess` method. """ source = text_type(source) try: return self.lexer.tokeniter(source, name, filename) except TemplateSyntaxError: exc_info = sys.exc_info() self.handle_exception(exc_info, source_hint=source)
def test_loop_call_loop(self): tmpl = env.from_string(''' {% macro test() %} {{ caller() }} {% endmacro %} {% for num1 in range(5) %} {% call test() %} {% for num2 in range(10) %} {{ loop.index }} {% endfor %} {% endcall %} {% endfor %} ''') assert tmpl.render().split() == [text_type(x) for x in range(1, 11)] * 5
def native_concat(nodes): """Return a native Python type from the list of compiled nodes. If the result is a single node, its value is returned. Otherwise, the nodes are concatenated as strings. If the result can be parsed with :func:`ast.literal_eval`, the parsed value is returned. Otherwise, the string is returned. """ head = list(islice(nodes, 2)) if not head: return None if len(head) == 1: out = head[0] else: out = u''.join([text_type(v) for v in chain(head, nodes)]) try: return literal_eval(out) except (ValueError, SyntaxError, MemoryError): return out
def tokeniter(self, source, name, filename=None, state=None): """This method tokenizes the text and returns the tokens in a generator. Use this method if you just want to tokenize a template. """ source = text_type(source) lines = source.splitlines() if self.keep_trailing_newline and source: for newline in ('\r\n', '\r', '\n'): if source.endswith(newline): lines.append('') break source = '\n'.join(lines) pos = 0 lineno = 1 stack = ['root'] if state is not None and state != 'root': assert state in ('variable', 'block'), 'invalid state' stack.append(state + '_begin') else: state = 'root' statetokens = self.rules[stack[-1]] source_length = len(source) balancing_stack = [] while 1: # tokenizer loop for regex, tokens, new_state in statetokens: m = regex.match(source, pos) # if no match we try again with the next rule if m is None: continue # we only match blocks and variables if braces / parentheses # are balanced. continue parsing with the lower rule which # is the operator rule. do this only if the end tags look # like operators if balancing_stack and \ tokens in ('variable_end', 'block_end', 'linestatement_end'): continue # tuples support more options if isinstance(tokens, tuple): for idx, token in enumerate(tokens): # failure group if token.__class__ is Failure: raise token(lineno, filename) # bygroup is a bit more complex, in that case we # yield for the current token the first named # group that matched elif token == '#bygroup': for key, value in iteritems(m.groupdict()): if value is not None: yield lineno, key, value lineno += value.count('\n') break else: raise RuntimeError('%r wanted to resolve ' 'the token dynamically' ' but no group matched' % regex) # normal group else: data = m.group(idx + 1) if data or token not in ignore_if_empty: yield lineno, token, data lineno += data.count('\n') # strings as token just are yielded as it. else: data = m.group() # update brace/parentheses balance if tokens == 'operator': if data == '{': balancing_stack.append('}') elif data == '(': balancing_stack.append(')') elif data == '[': balancing_stack.append(']') elif data in ('}', ')', ']'): if not balancing_stack: raise TemplateSyntaxError('unexpected \'%s\'' % data, lineno, name, filename) expected_op = balancing_stack.pop() if expected_op != data: raise TemplateSyntaxError('unexpected \'%s\', ' 'expected \'%s\'' % (data, expected_op), lineno, name, filename) # yield items if data or tokens not in ignore_if_empty: yield lineno, tokens, data lineno += data.count('\n') # fetch new position into new variable so that we can check # if there is a internal parsing error which would result # in an infinite loop pos2 = m.end() # handle state changes if new_state is not None: # remove the uppermost state if new_state == '#pop': stack.pop() # resolve the new state by group checking elif new_state == '#bygroup': for key, value in iteritems(m.groupdict()): if value is not None: stack.append(key) break else: raise RuntimeError('%r wanted to resolve the ' 'new state dynamically but' ' no group matched' % regex) # direct state name given else: stack.append(new_state) statetokens = self.rules[stack[-1]] # we are still at the same position and no stack change. # this means a loop without break condition, avoid that and # raise error elif pos2 == pos: raise RuntimeError('%r yielded empty string without ' 'stack change' % regex) # publish new function and start again pos = pos2 break # if loop terminated without break we haven't found a single match # either we are at the end of the file or we have a problem else: # end of text if pos >= source_length: return # something went wrong raise TemplateSyntaxError('unexpected char %r at %d' % (source[pos], pos), lineno, name, filename)
def escapeb64(s): s = text_type(s) s = b64encode(s.encode()).decode() return Markup(s)
def do_mark_unsafe(value): """Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" return text_type(value)
def do_striptags(value): """Strip SGML/XML tags and replace adjacent whitespace by one space. """ if hasattr(value, '__html__'): value = value.__html__() return Markup(text_type(value)).striptags()
def visit_Output(self, node, frame): # if we have a known extends statement, we don't output anything # if we are in a require_output_check section if self.has_known_extends and frame.require_output_check: return if self.environment.finalize: finalize = lambda x: text_type(self.environment.finalize(x)) else: finalize = text_type # if we are inside a frame that requires output checking, we do so outdent_later = False if frame.require_output_check: self.writeline('if parent_template is None:') self.indent() outdent_later = True # try to evaluate as many chunks as possible into a static # string at compile time. body = [] for child in node.nodes: try: const = child.as_const(frame.eval_ctx) except nodes.Impossible: body.append(child) continue # the frame can't be volatile here, becaus otherwise the # as_const() function would raise an Impossible exception # at that point. try: if frame.eval_ctx.autoescape: if hasattr(const, '__html__'): const = const.__html__() else: const = escape(const) const = finalize(const) except Exception: # if something goes wrong here we evaluate the node # at runtime for easier debugging body.append(child) continue if body and isinstance(body[-1], list): body[-1].append(const) else: body.append([const]) # if we have less than 3 nodes or a buffer we yield or extend/append if len(body) < 3 or frame.buffer is not None: if frame.buffer is not None: # for one item we append, for more we extend if len(body) == 1: self.writeline('%s.append(' % frame.buffer) else: self.writeline('%s.extend((' % frame.buffer) self.indent() for item in body: if isinstance(item, list): val = repr(concat(item)) if frame.buffer is None: self.writeline('yield ' + val) else: self.writeline(val + ', ') else: if frame.buffer is None: self.writeline('yield ', item) else: self.newline(item) close = 1 if frame.eval_ctx.volatile: self.write('(context.eval_ctx.autoescape and' ' escape or to_string)(') elif frame.eval_ctx.autoescape: self.write('escape(') else: self.write('to_string(') if self.environment.finalize is not None: self.write('environment.finalize(') close += 1 self.visit(item, frame) self.write(')' * close) if frame.buffer is not None: self.write(', ') if frame.buffer is not None:
def test_operator_precedence(self, env): tmpl = env.from_string('''{{ 2 * 3 + 4 % 2 + 1 - 2 }}''') assert tmpl.render() == text_type(2 * 3 + 4 % 2 + 1 - 2)
def __init__(self, message=None): if message is not None: message = text_type(message).encode('utf-8') Exception.__init__(self, message)
def test_string(self, env): x = [1, 2, 3, 4, 5] tmpl = env.from_string('''{{ obj|string }}''') assert tmpl.render(obj=x) == text_type(x)
def do_right(value, width=80): """Right-justifies the value in a field of a given width.""" return text_type(value).rjust(width)
def tokeniter(self, source, name, filename=None, state=None): """This method tokenizes the text and returns the tokens in a generator. Use this method if you just want to tokenize a template. """ source = text_type(source) lines = source.splitlines() if self.keep_trailing_newline and source: for newline in ('\r\n', '\r', '\n'): if source.endswith(newline): lines.append('') break source = '\n'.join(lines) pos = 0 lineno = 1 stack = ['root'] if state is not None and state != 'root': assert state in ('variable', 'block'), 'invalid state' stack.append(state + '_begin') else: state = 'root' statetokens = self.rules[stack[-1]] source_length = len(source) balancing_stack = [] while 1: # tokenizer loop for regex, tokens, new_state in statetokens: m = regex.match(source, pos) # if no match we try again with the next rule if m is None: continue # we only match blocks and variables if braces / parentheses # are balanced. continue parsing with the lower rule which # is the operator rule. do this only if the end tags look # like operators if balancing_stack and \ tokens in ('variable_end', 'block_end', 'linestatement_end'): continue # tuples support more options if isinstance(tokens, tuple): for idx, token in enumerate(tokens): # failure group if token.__class__ is Failure: raise token(lineno, filename) # bygroup is a bit more complex, in that case we # yield for the current token the first named # group that matched elif token == '#bygroup': for key, value in iteritems(m.groupdict()): if value is not None: yield lineno, key, value lineno += value.count('\n') break else: raise RuntimeError('%r wanted to resolve ' 'the token dynamically' ' but no group matched' % regex) # normal group else: data = m.group(idx + 1) if data or token not in ignore_if_empty: yield lineno, token, data lineno += data.count('\n') # strings as token just are yielded as it. else: data = m.group() # update brace/parentheses balance if tokens == 'operator': if data == '{': balancing_stack.append('}') elif data == '(': balancing_stack.append(')') elif data == '[': balancing_stack.append(']') elif data in ('}', ')', ']'): if not balancing_stack: raise TemplateSyntaxError( 'unexpected \'%s\'' % data, lineno, name, filename) expected_op = balancing_stack.pop() if expected_op != data: raise TemplateSyntaxError( 'unexpected \'%s\', ' 'expected \'%s\'' % (data, expected_op), lineno, name, filename) # yield items if data or tokens not in ignore_if_empty: yield lineno, tokens, data lineno += data.count('\n') # fetch new position into new variable so that we can check # if there is a internal parsing error which would result # in an infinite loop pos2 = m.end() # handle state changes if new_state is not None: # remove the uppermost state if new_state == '#pop': stack.pop() # resolve the new state by group checking elif new_state == '#bygroup': for key, value in iteritems(m.groupdict()): if value is not None: stack.append(key) break else: raise RuntimeError('%r wanted to resolve the ' 'new state dynamically but' ' no group matched' % regex) # direct state name given else: stack.append(new_state) statetokens = self.rules[stack[-1]] # we are still at the same position and no stack change. # this means a loop without break condition, avoid that and # raise error elif pos2 == pos: raise RuntimeError('%r yielded empty string without ' 'stack change' % regex) # publish new function and start again pos = pos2 break # if loop terminated without break we haven't found a single match # either we are at the end of the file or we have a problem else: # end of text if pos >= source_length: return # something went wrong raise TemplateSyntaxError( 'unexpected char %r at %d' % (source[pos], pos), lineno, name, filename)
def do_forceescape(value): """Enforce HTML escaping. This will probably double escape variables.""" if hasattr(value, '__html__'): value = value.__html__() return escape(text_type(value))
def test_upper(value): """Return true if the variable is uppercased.""" return text_type(value).isupper()
def test_string(self): x = [1, 2, 3, 4, 5] tmpl = env.from_string('''{{ obj|string }}''') assert tmpl.render(obj=x) == text_type(x)
def __str__(self): return text_type(self.value)
def __str__(self): return u'(%s,%s)' % (text_type(self.value1), text_type(self.value2))
def test_notin(self, env): bar = range(100) tmpl = env.from_string('''{{ not 42 in bar }}''') assert tmpl.render(bar=bar) == text_type(not 42 in bar)
def do_center(value, width=80): """Centers the value in a field of a given width.""" return text_type(value).center(width)
def as_const(self, eval_ctx=None): eval_ctx = get_eval_context(self, eval_ctx) return ''.join(text_type(x.as_const(eval_ctx)) for x in self.nodes)
def test_notin(self): bar = range(100) tmpl = env.from_string('''{{ not 42 in bar }}''') assert tmpl.render(bar=bar) == text_type(not 42 in bar)
def tokeniter(self, source, name, filename=None, state=None): """This method tokenizes the text and returns the tokens in a generator. Use this method if you just want to tokenize a template. """ source = text_type(source) lines = source.splitlines() if self.keep_trailing_newline and source: for newline in ('\r\n', '\r', '\n'): if source.endswith(newline): lines.append('') break source = '\n'.join(lines) pos = 0 lineno = 1 stack = ['root'] if state is not None and state != 'root': assert state in ('variable', 'block'), 'invalid state' stack.append(state + '_begin') statetokens = self.rules[stack[-1]] source_length = len(source) balancing_stack = [] lstrip_unless_re = self.lstrip_unless_re while 1: # tokenizer loop for regex, tokens, new_state in statetokens: m = regex.match(source, pos) # if no match we try again with the next rule if m is None: continue # we only match blocks and variables if braces / parentheses # are balanced. continue parsing with the lower rule which # is the operator rule. do this only if the end tags look # like operators if ( balancing_stack and tokens in ( TOKEN_VARIABLE_END, TOKEN_BLOCK_END, TOKEN_LINESTATEMENT_END ) ): continue # tuples support more options if isinstance(tokens, tuple): groups = m.groups() if isinstance(tokens, OptionalLStrip): # Rule supports lstrip. Match will look like # text, block type, whitespace control, type, control, ... text = groups[0] # Skipping the text and first type, every other group is the # whitespace control for each type. One of the groups will be # -, +, or empty string instead of None. strip_sign = next(g for g in groups[2::2] if g is not None) if strip_sign == "-": # Strip all whitespace between the text and the tag. groups = (text.rstrip(),) + groups[1:] elif ( # Not marked for preserving whitespace. strip_sign != "+" # lstrip is enabled. and lstrip_unless_re is not None # Not a variable expression. and not m.groupdict().get(TOKEN_VARIABLE_BEGIN) ): # The start of text between the last newline and the tag. l_pos = text.rfind('\n') + 1 # If there's only whitespace between the newline and the # tag, strip it. if not lstrip_unless_re.search(text, l_pos): groups = (text[:l_pos],) + groups[1:] for idx, token in enumerate(tokens): # failure group if token.__class__ is Failure: raise token(lineno, filename) # bygroup is a bit more complex, in that case we # yield for the current token the first named # group that matched elif token == '#bygroup': for key, value in iteritems(m.groupdict()): if value is not None: yield lineno, key, value lineno += value.count('\n') break else: raise RuntimeError('%r wanted to resolve ' 'the token dynamically' ' but no group matched' % regex) # normal group else: data = groups[idx] if data or token not in ignore_if_empty: yield lineno, token, data lineno += data.count('\n') # strings as token just are yielded as it. else: data = m.group() # update brace/parentheses balance if tokens == TOKEN_OPERATOR: if data == '{': balancing_stack.append('}') elif data == '(': balancing_stack.append(')') elif data == '[': balancing_stack.append(']') elif data in ('}', ')', ']'): if not balancing_stack: raise TemplateSyntaxError('unexpected \'%s\'' % data, lineno, name, filename) expected_op = balancing_stack.pop() if expected_op != data: raise TemplateSyntaxError('unexpected \'%s\', ' 'expected \'%s\'' % (data, expected_op), lineno, name, filename) # yield items if data or tokens not in ignore_if_empty: yield lineno, tokens, data lineno += data.count('\n') # fetch new position into new variable so that we can check # if there is a internal parsing error which would result # in an infinite loop pos2 = m.end() # handle state changes if new_state is not None: # remove the uppermost state if new_state == '#pop': stack.pop() # resolve the new state by group checking elif new_state == '#bygroup': for key, value in iteritems(m.groupdict()): if value is not None: stack.append(key) break else: raise RuntimeError('%r wanted to resolve the ' 'new state dynamically but' ' no group matched' % regex) # direct state name given else: stack.append(new_state) statetokens = self.rules[stack[-1]] # we are still at the same position and no stack change. # this means a loop without break condition, avoid that and # raise error elif pos2 == pos: raise RuntimeError('%r yielded empty string without ' 'stack change' % regex) # publish new function and start again pos = pos2 break # if loop terminated without break we haven't found a single match # either we are at the end of the file or we have a problem else: # end of text if pos >= source_length: return # something went wrong raise TemplateSyntaxError('unexpected char %r at %d' % (source[pos], pos), lineno, name, filename)