[svn] added c implementation of cgi.escape to jinja (fast for unicode, awefully slow...
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 21 Apr 2007 17:54:15 +0000 (19:54 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 21 Apr 2007 17:54:15 +0000 (19:54 +0200)
--HG--
branch : trunk

jinja/_native.py
jinja/_speedups.c
jinja/environment.py
jinja/filters.py
jinja/utils.py

index 6a9cb73f0471aec5c879113174cdb4dbcc59fba7..aec56ab2dd011a8be53b154f003eb09cb6808e22 100644 (file)
@@ -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
 
index 108697fb636b87fee3b556706560d73d3cc30ffd..d19be48a349a2624cff9af7b60c1fbb15ade9fde 100644 (file)
@@ -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("&amp;", 5, NULL))->str;
+       lt = ((PyUnicodeObject*)PyUnicode_DecodeASCII("&lt;", 4, NULL))->str;
+       gt = ((PyUnicodeObject*)PyUnicode_DecodeASCII("&gt;", 4, NULL))->str;
+       qt = ((PyUnicodeObject*)PyUnicode_DecodeASCII("&#34;", 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, &quotes))
+               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 */
 };
 
index a1a08e1f93c89fb361380a56d62d26de983db191..026506d4579199f63ec152172bb7edbb2f9767da 100644 (file)
@@ -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
 
index cbb9cd266522be5cecabf1738e33239766b4fa2a..49e07b4dbd3cce500cbe96a984fde0d44f551315 100644 (file)
@@ -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
 
 
index c233945a010ed0ba811e5a6c1fde97ce48b46ff2..349b8a59543c59f87d5c649914b81ba66c6b4423 100644 (file)
@@ -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