From: Armin Ronacher Date: Sat, 21 Apr 2007 17:54:15 +0000 (+0200) Subject: [svn] added c implementation of cgi.escape to jinja (fast for unicode, awefully slow... X-Git-Tag: 2.0rc1~343 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=4a51133f4cab87a27be0ee34d9c8f2bc5bbb47a6;p=jinja2.git [svn] added c implementation of cgi.escape to jinja (fast for unicode, awefully slow for str) --HG-- branch : trunk --- diff --git a/jinja/_native.py b/jinja/_native.py index 6a9cb73..aec56ab 100644 --- a/jinja/_native.py +++ b/jinja/_native.py @@ -23,6 +23,8 @@ class BaseContext(object): self._silent = silent self.current = current = {} self.stack = [globals, initial, current] + self._push = self.stack.append + self._pop = self.stack.pop self.globals = globals self.initial = initial @@ -30,7 +32,7 @@ class BaseContext(object): """ Pop the last layer from the stack and return it. """ - rv = self.stack.pop() + rv = self._pop() self.current = self.stack[-1] return rv @@ -39,7 +41,7 @@ class BaseContext(object): Push one layer to the stack. Layer must be a dict or omitted. """ data = data or {} - self.stack.append(data) + self._push(data) self.current = self.stack[-1] return data diff --git a/jinja/_speedups.c b/jinja/_speedups.c index 108697f..d19be48 100644 --- a/jinja/_speedups.c +++ b/jinja/_speedups.c @@ -22,6 +22,7 @@ /* Set by init_constants to real values */ static PyObject *Undefined, *Deferred, *TemplateRuntimeError; +static Py_UNICODE *amp, *lt, *gt, *qt; /** * Internal struct used by BaseContext to store the @@ -62,11 +63,99 @@ init_constants(void) Undefined = PyObject_GetAttrString(datastructure, "Undefined"); Deferred = PyObject_GetAttrString(datastructure, "Deferred"); TemplateRuntimeError = PyObject_GetAttrString(exceptions, "TemplateRuntimeError"); + + amp = ((PyUnicodeObject*)PyUnicode_DecodeASCII("&", 5, NULL))->str; + lt = ((PyUnicodeObject*)PyUnicode_DecodeASCII("<", 4, NULL))->str; + gt = ((PyUnicodeObject*)PyUnicode_DecodeASCII(">", 4, NULL))->str; + qt = ((PyUnicodeObject*)PyUnicode_DecodeASCII(""", 5, NULL))->str; + Py_DECREF(datastructure); Py_DECREF(exceptions); return 1; } +/** + * SGML/XML escape something. + * + * XXX: this is awefully slow for non unicode objects because they + * get converted to unicode first. + */ +static PyObject* +escape(PyObject *self, PyObject *args) +{ + PyUnicodeObject *in, *out; + Py_UNICODE *outp; + int i, len; + + int quotes = 0; + PyObject *text = NULL; + + if (!PyArg_ParseTuple(args, "O|b", &text, "es)) + return NULL; + in = (PyUnicodeObject*)PyObject_Unicode(text); + if (!in) + return NULL; + + /* First we need to figure out how long the escaped string will be */ + len = 0; + for (i = 0;i < in->length; i++) { + switch (in->str[i]) { + case '&': + len += 5; + break; + case '"': + len += quotes ? 5 : 1; + break; + case '<': + case '>': + len += 4; + break; + default: + len++; + } + } + + /* Do we need to escape anything at all? */ + if (len == in->length) { + Py_INCREF(in); + return (PyObject*)in; + } + out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, len); + if (!out) { + return NULL; + } + + outp = out->str; + for (i = 0;i < in->length; i++) { + switch (in->str[i]) { + case '&': + Py_UNICODE_COPY(outp, amp, 5); + outp += 5; + break; + case '"': + if (quotes) { + Py_UNICODE_COPY(outp, qt, 5); + outp += 5; + } + else + *outp++ = in->str[i]; + break; + case '<': + Py_UNICODE_COPY(outp, lt, 4); + outp += 4; + break; + case '>': + Py_UNICODE_COPY(outp, gt, 4); + outp += 4; + break; + default: + *outp++ = in->str[i]; + }; + } + + return (PyObject*)out; +} + /** * Deallocator for BaseContext. * @@ -438,6 +527,9 @@ static PyTypeObject BaseContextType = { }; static PyMethodDef module_methods[] = { + {"escape", (PyCFunction)escape, METH_VARARGS, + "escape(s, quotes=False) -> string\n\n" + "SGML/XML a string."}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/jinja/environment.py b/jinja/environment.py index a1a08e1..026506d 100644 --- a/jinja/environment.py +++ b/jinja/environment.py @@ -197,7 +197,8 @@ class Environment(object): raise_syntax_error(e, self, source) else: # everything went well. attach the source and return it - # attach the source for debugging + # the attached source is used by the traceback system for + # debugging porposes rv._source = source return rv @@ -251,14 +252,15 @@ class Environment(object): """ Apply a list of filters on the variable. """ + cache = context.cache for key in filters: - if key in context.cache: - func = context.cache[key] + if key in cache: + func = cache[key] else: filtername, args = key if filtername not in self.filters: raise FilterNotFound(filtername) - context.cache[key] = func = self.filters[filtername](*args) + cache[key] = func = self.filters[filtername](*args) value = func(self, context, value) return value diff --git a/jinja/filters.py b/jinja/filters.py index cbb9cd2..49e07b4 100644 --- a/jinja/filters.py +++ b/jinja/filters.py @@ -114,7 +114,10 @@ def do_escape(attribute=False): return s elif hasattr(s, '__html__'): return s.__html__() - return e(env.to_unicode(s), attribute) + #: small speedup + if s.__class__ is not unicode: + s = env.to_unicode(s) + return e(s, attribute) return wrapped diff --git a/jinja/utils.py b/jinja/utils.py index c233945..349b8a5 100644 --- a/jinja/utils.py +++ b/jinja/utils.py @@ -14,7 +14,6 @@ import re import sys import string -import cgi from types import MethodType, FunctionType from compiler.ast import CallFunc, Name, Const from jinja.nodes import Trans @@ -51,9 +50,6 @@ _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$') #: used by from_string as cache _from_string_env = None -escape = cgi.escape - - def urlize(text, trim_url_limit=None, nofollow=False): """ Converts any URLs in text into clickable links. Works on http://, @@ -646,3 +642,12 @@ class CacheDict(object): rv._mapping = deepcopy(self._mapping) rv._queue = deepcopy(self._queue) return rv + + +# escaping function. Use this only if you escape unicode +# objects. in all other cases it's likely that the cgi.escape +# function performs better. +try: + from jinja._speedups import escape +except ImportError: + from cgi import escape