TemplateAssertionError
# decorators and public utilities
-from jinja2.filters import environmentfilter, contextfilter
+from jinja2.filters import environmentfilter, contextfilter, \
+ evalcontextfilter
from jinja2.utils import Markup, escape, clear_caches, \
- environmentfunction, contextfunction, is_undefined
+ environmentfunction, evalcontextfunction, contextfunction, \
+ is_undefined
__all__ = [
'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
- 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined'
+ 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
+ 'evalcontextfilter', 'evalcontextfunction'
]
from itertools import chain
from copy import deepcopy
from jinja2 import nodes
+from jinja2.nodes import EvalContext
from jinja2.visitor import NodeVisitor, NodeTransformer
from jinja2.exceptions import TemplateAssertionError
from jinja2.utils import Markup, concat, escape, is_python_keyword, next
class Frame(object):
"""Holds compile time information for us."""
- def __init__(self, parent=None):
+ def __init__(self, eval_ctx, parent=None):
+ self.eval_ctx = eval_ctx
self.identifiers = Identifiers()
# a toplevel frame is the root + soft frames such as if conditions.
def inner(self):
"""Return an inner frame."""
- return Frame(self)
+ return Frame(self.eval_ctx, self)
def soft(self):
"""Return a soft frame. A soft frame may not be modified as
# -- Various compilation helpers
def fail(self, msg, lineno):
- """Fail with a `TemplateAssertionError`."""
+ """Fail with a :exc:`TemplateAssertionError`."""
raise TemplateAssertionError(msg, lineno, self.name, self.filename)
def temporary_identifier(self):
def return_buffer_contents(self, frame):
"""Return the buffer contents of the frame."""
- if self.environment.autoescape:
- self.writeline('return Markup(concat(%s))' % frame.buffer)
+ self.writeline('return ')
+ if frame.eval_ctx.volatile:
+ self.write('(Markup(concat(%s)) if context.eval_ctx'
+ '.autoescape else concat(%s))' %
+ (frame.buffer, frame.buffer))
+ elif frame.eval_ctx.autoescape:
+ self.write('Markup(concat(%s))' % frame.buffer)
else:
- self.writeline('return concat(%s)' % frame.buffer)
+ self.write('concat(%s)' % frame.buffer)
def indent(self):
"""Indent by one."""
def visit_Template(self, node, frame=None):
assert frame is None, 'no root frame allowed'
+ eval_ctx = EvalContext(self.environment)
+
from jinja2.runtime import __all__ as exported
self.writeline('from __future__ import division')
self.writeline('from jinja2.runtime import ' + ', '.join(exported))
self.writeline('def root(context%s):' % envenv, extra=1)
# process the root
- frame = Frame()
+ frame = Frame(eval_ctx)
frame.inspect(node.body)
frame.toplevel = frame.rootlevel = True
frame.require_output_check = have_extends and not self.has_known_extends
# at this point we now have the blocks collected and can visit them too.
for name, block in self.blocks.iteritems():
- block_frame = Frame()
+ block_frame = Frame(eval_ctx)
block_frame.inspect(block.body)
block_frame.block = name
self.writeline('def block_%s(context%s):' % (name, envenv),
body = []
for child in node.nodes:
try:
- const = child.as_const()
+ 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 self.environment.autoescape:
+ if frame.eval_ctx.autoescape:
if hasattr(const, '__html__'):
const = const.__html__()
else:
else:
self.newline(item)
close = 1
- if self.environment.autoescape:
+ 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(')
for argument in arguments:
self.newline(argument)
close = 0
- if self.environment.autoescape:
+ if frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' escape or to_string)(')
+ elif frame.eval_ctx.autoescape:
self.write('escape(')
close += 1
if self.environment.finalize is not None:
self.write(repr(val))
def visit_TemplateData(self, node, frame):
- self.write(repr(node.as_const()))
+ self.write(repr(node.as_const(frame.eval_ctx)))
def visit_Tuple(self, node, frame):
self.write('(')
del binop, uaop
def visit_Concat(self, node, frame):
- self.write('%s((' % (self.environment.autoescape and
- 'markup_join' or 'unicode_join'))
+ if frame.eval_ctx.volatile:
+ func_name = '(context.eval_ctx.volatile and' \
+ ' markup_join or unicode_join)'
+ elif frame.eval_ctx.autoescape:
+ func_name = 'markup_join'
+ else:
+ func_name = 'unicode_join'
+ self.write('%s((' % func_name)
for arg in node.nodes:
self.visit(arg, frame)
self.write(', ')
self.fail('no filter named %r' % node.name, node.lineno)
if getattr(func, 'contextfilter', False):
self.write('context, ')
+ elif getattr(func, 'evalcontextfilter', False):
+ self.write('context.eval_ctx, ')
elif getattr(func, 'environmentfilter', False):
self.write('environment, ')
# and want to write to the current buffer
if node.node is not None:
self.visit(node.node, frame)
- elif self.environment.autoescape:
+ elif frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' Markup(concat(%s)) or concat(%s))' %
+ (frame.buffer, frame.buffer))
+ elif frame.eval_ctx.autoescape:
self.write('Markup(concat(%s))' % frame.buffer)
else:
self.write('concat(%s)' % frame.buffer)
self.pull_locals(scope_frame)
self.blockvisit(node.body, scope_frame)
self.pop_scope(aliases, scope_frame)
+
+ def visit_EvalContextModifier(self, node, frame):
+ for keyword in node.options:
+ self.writeline('context.eval_ctx.%s = ' % keyword.key)
+ self.visit(keyword.value, frame)
+ try:
+ val = keyword.value.as_const(frame.eval_ctx)
+ except nodes.Impossible:
+ frame.volatile = True
+ else:
+ setattr(frame.eval_ctx, keyword.key, val)
+
+ def visit_ScopedEvalContextModifier(self, node, frame):
+ old_ctx_name = self.temporary_identifier()
+ safed_ctx = frame.eval_ctx.save()
+ self.writeline('%s = context.eval_ctx.save()' % old_ctx_name)
+ self.visit_EvalContextModifier(node, frame)
+ for child in node.body:
+ self.visit(child, frame)
+ frame.eval_ctx.revert(safed_ctx)
+ self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name)
`None` implicitly into an empty string here.
`autoescape`
- If set to true the XML/HTML autoescaping feature is enabled.
- For more details about auto escaping see
+ If set to true the XML/HTML autoescaping feature is enabled by
+ default. For more details about auto escaping see
:class:`~jinja2.utils.Markup`.
`loader`
raise TemplateSyntaxError('chunk after expression',
parser.stream.current.lineno,
None, None)
+ expr.set_environment(self)
except TemplateSyntaxError:
exc_info = sys.exc_info()
if exc_info is not None:
return node
+class AutoEscapeExtension(Extension):
+ """Changes auto escape rules for a scope."""
+ tags = set(['autoescape'])
+
+ def parse(self, parser):
+ node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
+ node.options = [
+ nodes.Keyword('autoescape', parser.parse_expression())
+ ]
+ node.body = parser.parse_statements(('name:endautoescape',),
+ drop_needle=True)
+ return nodes.Scope([node])
+
+
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
babel_style=True):
"""Extract localizable strings from the given template node. Per
do = ExprStmtExtension
loopcontrols = LoopControlExtension
with_ = WithExtension
+autoescape = AutoEscapeExtension
"""Decorator for marking context dependent filters. The current
:class:`Context` will be passed as first argument.
"""
- if getattr(f, 'environmentfilter', False):
- raise TypeError('filter already marked as environment filter')
f.contextfilter = True
return f
+def evalcontextfilter(f):
+ """Decorator for marking eval-context dependent filters. An eval
+ context object is passed as first argument.
+
+ .. versionadded:: 2.4
+ """
+ f.evalcontextfilter = True
+ return f
+
+
def environmentfilter(f):
"""Decorator for marking evironment dependent filters. The current
:class:`Environment` is passed to the filter as first argument.
"""
- if getattr(f, 'contextfilter', False):
- raise TypeError('filter already marked as context filter')
f.environmentfilter = True
return f
return escape(unicode(value))
-@environmentfilter
-def do_replace(environment, s, old, new, count=None):
+@evalcontextfilter
+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 count is None:
count = -1
- if not environment.autoescape:
+ if not eval_ctx.autoescape:
return unicode(s).replace(unicode(old), unicode(new), count)
if hasattr(old, '__html__') or hasattr(new, '__html__') and \
not hasattr(s, '__html__'):
return soft_unicode(s).lower()
-@environmentfilter
-def do_xmlattr(_environment, d, autospace=True):
+@evalcontextfilter
+def do_xmlattr(_eval_ctx, d, autospace=True):
"""Create an SGML/XML attribute string based on the items in a dict.
All values that are neither `none` nor `undefined` are automatically
escaped:
)
if autospace and rv:
rv = u' ' + rv
- if _environment.autoescape:
+ if _eval_ctx.autoescape:
rv = Markup(rv)
return rv
return value
-@environmentfilter
-def do_join(environment, value, d=u''):
+@evalcontextfilter
+def do_join(eval_ctx, value, d=u''):
"""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:
-> 123
"""
# no automatic escaping? joining is a lot eaiser then
- if not environment.autoescape:
+ if not eval_ctx.autoescape:
return unicode(d).join(imap(unicode, value))
# if the delimiter doesn't have an html representation we check
return pformat(value, verbose=verbose)
-@environmentfilter
-def do_urlize(environment, value, trim_url_limit=None, nofollow=False):
+@evalcontextfilter
+def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
"""Converts URLs in plain text into clickable links.
If you pass the filter an additional integer it will shorten the urls
links are shortened to 40 chars and defined with rel="nofollow"
"""
rv = urlize(value, trim_url_limit, nofollow)
- if environment.autoescape:
+ if eval_ctx.autoescape:
rv = Markup(rv)
return rv
return type.__new__(cls, name, bases, d)
+class EvalContext(object):
+ """Holds evaluation time information"""
+
+ def __init__(self, environment):
+ self.autoescape = environment.autoescape
+ self.volatile = False
+
+ def save(self):
+ return self.__dict__.copy()
+
+ def revert(self, old):
+ self.__dict__.clear()
+ self.__dict__.update(old)
+
+
+def get_eval_context(node, ctx):
+ if ctx is None:
+ if node.environment is None:
+ raise RuntimeError('if no eval context is passed, the '
+ 'node must have an attached '
+ 'environment.')
+ return EvalContext(node.environment)
+ return ctx
+
+
class Node(object):
"""Baseclass for all Jinja2 nodes. There are a number of nodes available
of different types. There are three major types:
"""Baseclass for all expressions."""
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
"""Return the value of the expression as constant or raise
- :exc:`Impossible` if this was not possible:
+ :exc:`Impossible` if this was not possible.
- >>> Add(Const(23), Const(42)).as_const()
- 65
- >>> Add(Const(23), Name('var', 'load')).as_const()
- Traceback (most recent call last):
- ...
- Impossible
+ An :class:`EvalContext` can be provided, if none is given
+ a default context is created which requires the nodes to have
+ an attached environment.
- This requires the `environment` attribute of all nodes to be
- set to the environment that created the nodes.
+ .. versionchanged:: 2.4
+ the `eval_ctx` parameter was added.
"""
raise Impossible()
operator = None
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
f = _binop_to_func[self.operator]
try:
- return f(self.left.as_const(), self.right.as_const())
+ return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
except:
raise Impossible()
operator = None
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
f = _uaop_to_func[self.operator]
try:
- return f(self.node.as_const())
+ return f(self.node.as_const(eval_ctx))
except:
raise Impossible()
"""
fields = ('value',)
- def as_const(self):
+ def as_const(self, eval_ctx=None):
return self.value
@classmethod
"""A constant template string."""
fields = ('data',)
- def as_const(self):
- if self.environment.autoescape:
+ def as_const(self, eval_ctx=None):
+ if get_eval_context(self, eval_ctx).autoescape:
return Markup(self.data)
return self.data
"""
fields = ('items', 'ctx')
- def as_const(self):
- return tuple(x.as_const() for x in self.items)
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return tuple(x.as_const(eval_ctx) for x in self.items)
def can_assign(self):
for item in self.items:
"""Any list literal such as ``[1, 2, 3]``"""
fields = ('items',)
- def as_const(self):
- return [x.as_const() for x in self.items]
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return [x.as_const(eval_ctx) for x in self.items]
class Dict(Literal):
"""
fields = ('items',)
- def as_const(self):
- return dict(x.as_const() for x in self.items)
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return dict(x.as_const(eval_ctx) for x in self.items)
class Pair(Helper):
"""A key, value pair for dicts."""
fields = ('key', 'value')
- def as_const(self):
- return self.key.as_const(), self.value.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
class Keyword(Helper):
"""A key, value pair for keyword arguments where key is a string."""
fields = ('key', 'value')
- def as_const(self):
- return self.key, self.value.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.key, self.value.as_const(eval_ctx)
class CondExpr(Expr):
"""
fields = ('test', 'expr1', 'expr2')
- def as_const(self):
- if self.test.as_const():
- return self.expr1.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if self.test.as_const(eval_ctx):
+ return self.expr1.as_const(eval_ctx)
# if we evaluate to an undefined object, we better do that at runtime
if self.expr2 is None:
raise Impossible()
- return self.expr2.as_const()
+ return self.expr2.as_const(eval_ctx)
class Filter(Expr):
"""
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
- def as_const(self, obj=None):
- if self.node is obj is None:
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile or self.node is None:
raise Impossible()
# we have to be careful here because we call filter_ below.
# if this variable would be called filter, 2to3 would wrap the
filter_ = self.environment.filters.get(self.name)
if filter_ is None or getattr(filter_, 'contextfilter', False):
raise Impossible()
- if obj is None:
- obj = self.node.as_const()
- args = [x.as_const() for x in self.args]
- if getattr(filter_, 'environmentfilter', False):
+ obj = self.node.as_const(eval_ctx)
+ args = [x.as_const(eval_ctx) for x in self.args]
+ if getattr(filter_, 'evalcontextfilter', False):
+ args.insert(0, eval_ctx)
+ elif getattr(filter_, 'environmentfilter', False):
args.insert(0, self.environment)
- kwargs = dict(x.as_const() for x in self.kwargs)
+ kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
if self.dyn_args is not None:
try:
- args.extend(self.dyn_args.as_const())
+ args.extend(self.dyn_args.as_const(eval_ctx))
except:
raise Impossible()
if self.dyn_kwargs is not None:
try:
- kwargs.update(self.dyn_kwargs.as_const())
+ kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
except:
raise Impossible()
try:
"""
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
- def as_const(self):
- obj = self.node.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile:
+ raise Impossible()
+ obj = self.node.as_const(eval_ctx)
# don't evaluate context functions
- args = [x.as_const() for x in self.args]
+ args = [x.as_const(eval_ctx) for x in self.args]
if getattr(obj, 'contextfunction', False):
raise Impossible()
+ elif getattr(obj, 'evalcontextfunction', False):
+ args.insert(0, eval_ctx)
elif getattr(obj, 'environmentfunction', False):
args.insert(0, self.environment)
- kwargs = dict(x.as_const() for x in self.kwargs)
+ kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
if self.dyn_args is not None:
try:
- args.extend(self.dyn_args.as_const())
+ args.extend(self.dyn_args.as_const(eval_ctx))
except:
raise Impossible()
if self.dyn_kwargs is not None:
try:
- kwargs.update(self.dyn_kwargs.as_const())
+ kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
except:
raise Impossible()
try:
"""Get an attribute or item from an expression and prefer the item."""
fields = ('node', 'arg', 'ctx')
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
if self.ctx != 'load':
raise Impossible()
try:
- return self.environment.getitem(self.node.as_const(),
- self.arg.as_const())
+ return self.environment.getitem(self.node.as_const(eval_ctx),
+ self.arg.as_const(eval_ctx))
except:
raise Impossible()
"""
fields = ('node', 'attr', 'ctx')
- def as_const(self):
+ def as_const(self, eval_ctx=None):
if self.ctx != 'load':
raise Impossible()
try:
- return self.environment.getattr(self.node.as_const(), arg)
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.environment.getattr(self.node.as_const(eval_ctx), arg)
except:
raise Impossible()
"""
fields = ('start', 'stop', 'step')
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
def const(obj):
if obj is None:
- return obj
- return obj.as_const()
+ return None
+ return obj.as_const(eval_ctx)
return slice(const(self.start), const(self.stop), const(self.step))
"""
fields = ('nodes',)
- def as_const(self):
- return ''.join(unicode(x.as_const()) for x in self.nodes)
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return ''.join(unicode(x.as_const(eval_ctx)) for x in self.nodes)
class Compare(Expr):
"""
fields = ('expr', 'ops')
- def as_const(self):
- result = value = self.expr.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ result = value = self.expr.as_const(eval_ctx)
try:
for op in self.ops:
- new_value = op.expr.as_const()
+ new_value = op.expr.as_const(eval_ctx)
result = _cmpop_to_func[op.op](value, new_value)
value = new_value
except:
"""Short circuited AND."""
operator = 'and'
- def as_const(self):
- return self.left.as_const() and self.right.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
class Or(BinExpr):
"""Short circuited OR."""
operator = 'or'
- def as_const(self):
- return self.left.as_const() or self.right.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
class Not(UnaryExpr):
"""Mark the wrapped expression as safe (wrap it as `Markup`)."""
fields = ('expr',)
- def as_const(self):
- return Markup(self.expr.as_const())
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return Markup(self.expr.as_const(eval_ctx))
class ContextReference(Expr):
fields = ('body',)
+class EvalContextModifier(Stmt):
+ """Modifies the eval context"""
+ fields = ('options',)
+
+
+class ScopedEvalContextModifier(EvalContextModifier):
+ """Modifies the eval context and reverts it later."""
+ fields = ('body',)
+
+
# make sure nobody creates custom nodes
def _failing_new(*args, **kwargs):
raise TypeError('can\'t create custom node types')
"""
import sys
from itertools import chain, imap
+from jinja2.nodes import EvalContext
from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
concat, MethodType, FunctionType, internalcode, next
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
method that doesn't fail with a `KeyError` but returns an
:class:`Undefined` object for missing variables.
"""
- __slots__ = ('parent', 'vars', 'environment', 'exported_vars', 'name',
- 'blocks', '__weakref__')
+ __slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
+ 'name', 'blocks', '__weakref__')
def __init__(self, environment, parent, name, blocks):
self.parent = parent
self.vars = {}
self.environment = environment
+ self.eval_ctx = EvalContext(self.environment)
self.exported_vars = set()
self.name = name
if isinstance(__obj, _context_function_types):
if getattr(__obj, 'contextfunction', 0):
args = (__self,) + args
+ elif getattr(__obj, 'evalcontextfunction', 0):
+ args = (__self.eval_ctx,) + args
elif getattr(__obj, 'environmentfunction', 0):
args = (__self.environment,) + args
return __obj(*args, **kwargs)
"""Internal helper function to create a derived context."""
context = new_context(self.environment, self.name, {},
self.parent, True, None, locals)
+ context.eval_ctx = self.eval_ctx
context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems())
return context
]
+class AutoEscapeTestCase(JinjaTestCase):
+
+ def test_scoped_setting(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=True)
+ tmpl = env.from_string('''
+ {{ "<HelloWorld>" }}
+ {% autoescape false %}
+ {{ "<HelloWorld>" }}
+ {% endautoescape %}
+ {{ "<HelloWorld>" }}
+ ''')
+ assert tmpl.render().split() == \
+ [u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
+
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=False)
+ tmpl = env.from_string('''
+ {{ "<HelloWorld>" }}
+ {% autoescape true %}
+ {{ "<HelloWorld>" }}
+ {% endautoescape %}
+ {{ "<HelloWorld>" }}
+ ''')
+ assert tmpl.render().split() == \
+ [u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
+
+ def test_nonvolatile(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=True)
+ tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
+ assert tmpl.render() == ' foo="<test>"'
+ tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
+ '|xmlattr|escape }}{% endautoescape %}')
+ assert tmpl.render() == ' foo="&lt;test&gt;"'
+
+ def test_volatile(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=True)
+ tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
+ '|xmlattr|escape }}{% endautoescape %}')
+ assert tmpl.render(foo=False) == ' foo="&lt;test&gt;"'
+ assert tmpl.render(foo=True) == ' foo="<test>"'
+
+ def test_scoping(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'])
+ tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
+ '{% endautoescape %}{{ x }}{{ "<y>" }}')
+ assert tmpl.render(x=1) == '<x>1<y>'
+
+
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ExtensionsTestCase))
suite.addTest(unittest.makeSuite(InternationalizationTestCase))
+ suite.addTest(unittest.makeSuite(AutoEscapeTestCase))
return suite
return f
+def evalcontextfunction(f):
+ """This decoraotr can be used to mark a function or method as an eval
+ context callable. This is similar to the :func:`contextfunction`
+ but instead of passing the context, an evaluation context object is
+ passed.
+
+ .. versionadded:: 2.4
+ """
+ f.evalcontextfunction = True
+ return f
+
+
def environmentfunction(f):
"""This decorator can be used to mark a function or method as environment
callable. This decorator works exactly like the :func:`contextfunction`