added autoescaping
--HG--
branch : trunk
diff --git a/jinja2/__init__.py b/jinja2/__init__.py
index dcebefc..b3507a3 100644
--- a/jinja2/__init__.py
+++ b/jinja2/__init__.py
@@ -58,3 +58,4 @@
"""
from jinja2.environment import Environment
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
+from jinja2.utils import Markup, escape
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index d1025d4..64524ed 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -781,8 +781,8 @@
finalizer = 'unicode'
have_finalizer = False
else:
- finalizer = 'context.finalize'
- have_finalizer = False
+ finalizer = 'environment.finalize'
+ have_finalizer = True
# if we are in the toplevel scope and there was already an extends
# statement we have to add a check that disables our yield(s) here
@@ -846,7 +846,7 @@
for argument in arguments:
self.newline(argument)
if have_finalizer:
- self.write('(')
+ self.write(finalizer + '(')
self.visit(argument, frame)
if have_finalizer:
self.write(')')
diff --git a/jinja2/debug.py b/jinja2/debug.py
index ecd84ae..1b558f0 100644
--- a/jinja2/debug.py
+++ b/jinja2/debug.py
@@ -9,7 +9,6 @@
:license: BSD.
"""
import sys
-from jinja2.exceptions import TemplateNotFound
def translate_exception(exc_info):
@@ -44,6 +43,10 @@
if name.startswith('l_'):
locals[name[2:]] = value
+ # if there is a local called __jinja_exception__, we get
+ # rid of it to not break the debug functionality.
+ locals.pop('__jinja_exception__', None)
+
# assamble fake globals we need
globals = {
'__name__': filename,
diff --git a/jinja2/environment.py b/jinja2/environment.py
index d0bbb1d..9fdfdae 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -42,7 +42,8 @@
trim_blocks=False,
optimized=True,
undefined=Undefined,
- loader=None):
+ loader=None,
+ finalize=unicode):
"""Here the possible initialization parameters:
========================= ============================================
@@ -88,18 +89,13 @@
self.trim_blocks = trim_blocks
self.undefined = undefined
self.optimized = optimized
+ self.finalize = finalize
# defaults
self.filters = DEFAULT_FILTERS.copy()
self.tests = DEFAULT_TESTS.copy()
self.globals = DEFAULT_NAMESPACE.copy()
- # if no finalize function/method exists we default to unicode. The
- # compiler check if the finalize attribute *is* unicode, if yes no
- # finalizaion is written where it can be avoided.
- if not hasattr(self, 'finalize'):
- self.finalize = unicode
-
# set the loader provided
self.loader = loader
diff --git a/jinja2/filters.py b/jinja2/filters.py
index 45e4137..46a0e09 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -15,7 +15,7 @@
except ImportError:
itemgetter = lambda a: lambda b: b[a]
from urllib import urlencode, quote
-from jinja2.utils import escape, pformat, urlize
+from jinja2.utils import Markup, escape, pformat, urlize
from jinja2.runtime import Undefined
@@ -711,5 +711,6 @@
'abs': abs,
'round': do_round,
'sort': do_sort,
- 'groupby': do_groupby
+ 'groupby': do_groupby,
+ 'safe': Markup
}
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 3e3721d..247958f 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -12,19 +12,19 @@
from collections import defaultdict
except ImportError:
defaultdict = None
+from jinja2.utils import Markup
__all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext',
'Macro', 'IncludedTemplate', 'TemplateData']
-class TemplateData(unicode):
+class TemplateData(Markup):
"""Marks data as "coming from the template". This is used to let the
system know that this data is already processed if a finalization is
- used."""
-
- def __html__(self):
- return self
+ used.
+ """
+ __slots__ = ()
class TemplateContext(dict):
@@ -119,7 +119,10 @@
return self._context[name]
def __unicode__(self):
- return self._context
+ return self._rendered_body
+
+ def __html__(self):
+ return self._rendered_body
def __repr__(self):
return '<%s %r>' % (
diff --git a/jinja2/utils.py b/jinja2/utils.py
index b597ed0..af1066c 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -10,17 +10,20 @@
"""
import re
import string
+from functools import update_wrapper
+from itertools import imap
def escape(obj, attribute=False):
"""HTML escape an object."""
if hasattr(obj, '__html__'):
return obj.__html__()
- return unicode(obj) \
- .replace('&', '&') \
- .replace('>', '>') \
- .replace('<', '<') \
+ return Markup(unicode(obj)
+ .replace('&', '&')
+ .replace('>', '>')
+ .replace('<', '<')
.replace('"', '"')
+ )
def pformat(obj, verbose=False):
@@ -39,9 +42,9 @@
_word_split_re = re.compile(r'(\s+)')
_punctuation_re = re.compile(
- '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
- '|'.join([re.escape(p) for p in ('(', '<', '<')]),
- '|'.join([re.escape(p) for p in ('.', ',', ')', '>', '\n', '>')])
+ '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
+ '|'.join(imap(re.escape, ('(', '<', '<'))),
+ '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '>')))
)
)
@@ -91,3 +94,94 @@
if lead + middle + trail != word:
words[i] = lead + middle + trail
return u''.join(words)
+
+
+class Markup(unicode):
+ """Marks a string as being safe for inclusion in HTML/XML output without
+ needing to be escaped. This implements the `__html__` interface a couple
+ of frameworks and web applications use.
+
+ The `escape` function returns markup objects so that double escaping can't
+ happen. If you want to use autoescaping in Jinja just set the finalizer
+ of the environment to `escape`.
+ """
+
+ __slots__ = ()
+
+ def __html__(self):
+ return self
+
+ def __add__(self, other):
+ if hasattr(other, '__html__') or isinstance(other, basestring):
+ return self.__class__(unicode(self) + unicode(escape(other)))
+ return NotImplemented
+
+ def __radd__(self, other):
+ if hasattr(other, '__html__') or isinstance(other, basestring):
+ return self.__class__(unicode(escape(other)) + unicode(self))
+ return NotImplemented
+
+ def __mul__(self, num):
+ if not isinstance(num, (int, long)):
+ return NotImplemented
+ return self.__class__(unicode.__mul__(self, num))
+ __rmul__ = __mul__
+
+ def __mod__(self, arg):
+ if isinstance(arg, tuple):
+ arg = tuple(imap(_MarkupEscapeHelper, arg))
+ else:
+ arg = _MarkupEscapeHelper(arg)
+ return self.__class__(unicode.__mod__(self, arg))
+
+ def __repr__(self):
+ return '%s(%s)' % (
+ self.__class__.__name__,
+ unicode.__repr__(self)
+ )
+
+ def join(self, seq):
+ return self.__class__(unicode.join(self, imap(escape, seq)))
+
+ def split(self, *args, **kwargs):
+ return map(self.__class__, unicode.split(self, *args, **kwargs))
+
+ def rsplit(self, *args, **kwargs):
+ return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
+
+ def splitlines(self, *args, **kwargs):
+ return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
+
+ def make_wrapper(name):
+ orig = getattr(unicode, name)
+ def func(self, *args, **kwargs):
+ args = list(args)
+ for idx, arg in enumerate(args):
+ if hasattr(arg, '__html__') or isinstance(arg, basestring):
+ args[idx] = escape(arg)
+ for name, arg in kwargs.iteritems():
+ if hasattr(arg, '__html__') or isinstance(arg, basestring):
+ kwargs[name] = escape(arg)
+ return self.__class__(orig(self, *args, **kwargs))
+ return update_wrapper(func, orig, ('__name__', '__doc__'))
+ for method in '__getitem__', '__getslice__', 'capitalize', \
+ 'title', 'lower', 'upper', 'replace', 'ljust', \
+ 'rjust', 'lstrip', 'rstrip', 'partition', 'center', \
+ 'strip', 'translate', 'expandtabs', 'rpartition', \
+ 'swapcase', 'zfill':
+ locals()[method] = make_wrapper(method)
+ del method, make_wrapper
+
+
+class _MarkupEscapeHelper(object):
+ """Helper for Markup.__mod__"""
+
+ def __init__(self, obj):
+ self.obj = obj
+
+ __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
+ __unicode__ = lambda s: unicode(escape(s.obj))
+ __str__ = lambda s: str(escape(s.obj))
+ __repr__ = lambda s: str(repr(escape(s.obj)))
+ __int__ = lambda s: int(s.obj)
+ __float__ = lambda s: float(s.obj)