Edgewall Software

source: tags/0.6.1/genshi/_speedups.c

Last change on this file was 1169, checked in by hodgestar, 12 years ago

Merge r1168 from trunk (fix error in dealing with None in the implemenation of Markup.escape in _speedups.c, see #439).

  • Property svn:eol-style set to native
File size: 17.2 KB
Line 
1/*
2 * Copyright (C) 2006-2008 Edgewall Software
3 * All rights reserved.
4 *
5 * This software is licensed as described in the file COPYING, which
6 * you should have received as part of this distribution. The terms
7 * are also available at http://genshi.edgewall.org/wiki/License.
8 *
9 * This software consists of voluntary contributions made by many
10 * individuals. For the exact contribution history, see the revision
11 * history and logs, available at http://genshi.edgewall.org/log/.
12 */
13
14#include <Python.h>
15#include <structmember.h>
16
17#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
18typedef int Py_ssize_t;
19#define PY_SSIZE_T_MAX INT_MAX
20#define PY_SSIZE_T_MIN INT_MIN
21#endif
22
23static PyObject *amp1, *amp2, *lt1, *lt2, *gt1, *gt2, *qt1, *qt2;
24static PyObject *stripentities, *striptags;
25
26static void
27init_constants(void)
28{
29    PyObject *util = PyImport_ImportModule("genshi.util");
30    stripentities = PyObject_GetAttrString(util, "stripentities");
31    striptags = PyObject_GetAttrString(util, "striptags");
32    Py_DECREF(util);
33
34    amp1 = PyUnicode_DecodeASCII("&", 1, NULL);
35    amp2 = PyUnicode_DecodeASCII("&amp;", 5, NULL);
36    lt1 = PyUnicode_DecodeASCII("<", 1, NULL);
37    lt2 = PyUnicode_DecodeASCII("&lt;", 4, NULL);
38    gt1 = PyUnicode_DecodeASCII(">", 1, NULL);
39    gt2 = PyUnicode_DecodeASCII("&gt;", 4, NULL);
40    qt1 = PyUnicode_DecodeASCII("\"", 1, NULL);
41    qt2 = PyUnicode_DecodeASCII("&#34;", 5, NULL);
42}
43
44/* Markup class */
45
46PyTypeObject MarkupType; /* declared later */
47
48PyDoc_STRVAR(Markup__doc__,
49"Marks a string as being safe for inclusion in HTML/XML output without\n\
50needing to be escaped.");
51
52static PyObject *
53escape(PyObject *text, int quotes)
54{
55    PyObject *args, *ret;
56    PyUnicodeObject *in, *out;
57    Py_UNICODE *inp, *outp;
58    int len, inn, outn;
59
60    if (PyObject_TypeCheck(text, &MarkupType)) {
61        Py_INCREF(text);
62        return text;
63    }
64    if (PyObject_HasAttrString(text, "__html__")) {
65        ret = PyObject_CallMethod(text, "__html__", NULL);
66        args = PyTuple_New(1);
67        if (args == NULL) {
68            Py_DECREF(ret);
69            return NULL;
70        }
71        PyTuple_SET_ITEM(args, 0, ret);
72        ret = MarkupType.tp_new(&MarkupType, args, NULL);
73        Py_DECREF(args);
74        return ret;
75    }
76    in = (PyUnicodeObject *) PyObject_Unicode(text);
77    if (in == NULL) {
78        return NULL;
79    }
80    /* First we need to figure out how long the escaped string will be */
81    len = inn = 0;
82    inp = in->str;
83    while (*(inp) || in->length > inp - in->str) {
84        switch (*inp++) {
85            case '&': len += 5; inn++;                                 break;
86            case '"': len += quotes ? 5 : 1; inn += quotes ? 1 : 0;    break;
87            case '<':
88            case '>': len += 4; inn++;                                 break;
89            default:  len++;
90        }
91    }
92
93    /* Do we need to escape anything at all? */
94    if (!inn) {
95        args = PyTuple_New(1);
96        if (args == NULL) {
97            Py_DECREF((PyObject *) in);
98            return NULL;
99        }
100        PyTuple_SET_ITEM(args, 0, (PyObject *) in);
101        ret = MarkupType.tp_new(&MarkupType, args, NULL);
102        Py_DECREF(args);
103        return ret;
104    }
105
106    out = (PyUnicodeObject*) PyUnicode_FromUnicode(NULL, len);
107    if (out == NULL) {
108        Py_DECREF((PyObject *) in);
109        return NULL;
110    }
111
112    outn = 0;
113    inp = in->str;
114    outp = out->str;
115    while (*(inp) || in->length > inp - in->str) {
116        if (outn == inn) {
117            /* copy rest of string if we have already replaced everything */
118            Py_UNICODE_COPY(outp, inp, in->length - (inp - in->str));
119            break;
120        }
121        switch (*inp) {
122            case '&':
123                Py_UNICODE_COPY(outp, ((PyUnicodeObject *) amp2)->str, 5);
124                outp += 5;
125                outn++;
126                break;
127            case '"':
128                if (quotes) {
129                    Py_UNICODE_COPY(outp, ((PyUnicodeObject *) qt2)->str, 5);
130                    outp += 5;
131                    outn++;
132                } else {
133                    *outp++ = *inp;
134                }
135                break;
136            case '<':
137                Py_UNICODE_COPY(outp, ((PyUnicodeObject *) lt2)->str, 4);
138                outp += 4;
139                outn++;
140                break;
141            case '>':
142                Py_UNICODE_COPY(outp, ((PyUnicodeObject *) gt2)->str, 4);
143                outp += 4;
144                outn++;
145                break;
146            default:
147                *outp++ = *inp;
148        }
149        inp++;
150    }
151
152    Py_DECREF((PyObject *) in);
153
154    args = PyTuple_New(1);
155    if (args == NULL) {
156        Py_DECREF((PyObject *) out);
157        return NULL;
158    }
159    PyTuple_SET_ITEM(args, 0, (PyObject *) out);
160    ret = MarkupType.tp_new(&MarkupType, args, NULL);
161    Py_DECREF(args);
162    return ret;
163}
164
165PyDoc_STRVAR(escape__doc__,
166"Create a Markup instance from a string and escape special characters\n\
167it may contain (<, >, & and \").\n\
168\n\
169>>> escape('\"1 < 2\"')\n\
170<Markup u'&#34;1 &lt; 2&#34;'>\n\
171\n\
172If the `quotes` parameter is set to `False`, the \" character is left\n\
173as is. Escaping quotes is generally only required for strings that are\n\
174to be used in attribute values.\n\
175\n\
176>>> escape('\"1 < 2\"', quotes=False)\n\
177<Markup u'\"1 &lt; 2\"'>\n\
178\n\
179:param text: the text to escape\n\
180:param quotes: if ``True``, double quote characters are escaped in\n\
181               addition to the other special characters\n\
182:return: the escaped `Markup` string\n\
183:rtype: `Markup`\n\
184");
185
186static PyObject *
187Markup_escape(PyTypeObject* type, PyObject *args, PyObject *kwds)
188{
189    static char *kwlist[] = {"text", "quotes", 0};
190    PyObject *text = NULL;
191    char quotes = 1;
192
193    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|b", kwlist, &text, &quotes)) {
194        return NULL;
195    }
196    if (PyObject_Not(text)) {
197        args = PyTuple_New(0);
198        if (args == NULL)
199            return NULL;
200        text = type->tp_new(type, args, NULL);
201        Py_DECREF(args);
202        return text;
203    }
204    if (PyObject_TypeCheck(text, type)) {
205        Py_INCREF(text);
206        return text;
207    }
208    return escape(text, quotes);
209}
210
211static PyObject *
212Markup_html(PyObject *self)
213{
214    Py_INCREF(self);
215    return self;
216}
217
218PyDoc_STRVAR(join__doc__,
219"Return a `Markup` object which is the concatenation of the strings\n\
220in the given sequence, where this `Markup` object is the separator\n\
221between the joined elements.\n\
222\n\
223Any element in the sequence that is not a `Markup` instance is\n\
224automatically escaped.\n\
225\n\
226:param seq: the sequence of strings to join\n\
227:param escape_quotes: whether double quote characters in the elements\n\
228                      should be escaped\n\
229:return: the joined `Markup` object\n\
230:rtype: `Markup`\n\
231:see: `escape`\n\
232");
233
234static PyObject *
235Markup_join(PyObject *self, PyObject *args, PyObject *kwds)
236{
237    static char *kwlist[] = {"seq", "escape_quotes", 0};
238    PyObject *seq = NULL, *seq2, *tmp, *tmp2;
239    char quotes = 1;
240    Py_ssize_t n;
241    int i;
242
243    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|b", kwlist, &seq, &quotes)) {
244        return NULL;
245    }
246    if (!PySequence_Check(seq)) {
247        return NULL;
248    }
249    n = PySequence_Size(seq);
250    if (n < 0) {
251        return NULL;
252    }
253    seq2 = PyTuple_New(n);
254    if (seq2 == NULL) {
255        return NULL;
256    }
257    for (i = 0; i < n; i++) {
258        tmp = PySequence_GetItem(seq, i);
259        if (tmp == NULL) {
260            Py_DECREF(seq2);
261            return NULL;
262        }
263        tmp2 = escape(tmp, quotes);
264        if (tmp2 == NULL) {
265            Py_DECREF(seq2);
266            return NULL;
267        }
268        PyTuple_SET_ITEM(seq2, i, tmp2);
269        Py_DECREF(tmp);
270    }
271    tmp = PyUnicode_Join(self, seq2);
272    Py_DECREF(seq2);
273    if (tmp == NULL)
274        return NULL;
275    args = PyTuple_New(1);
276    if (args == NULL) {
277        Py_DECREF(tmp);
278        return NULL;
279    }
280    PyTuple_SET_ITEM(args, 0, tmp);
281    tmp = MarkupType.tp_new(&MarkupType, args, NULL);
282    Py_DECREF(args);
283    return tmp;
284}
285
286static PyObject *
287Markup_add(PyObject *self, PyObject *other)
288{
289    PyObject *tmp, *tmp2, *args, *ret;
290    if (PyObject_TypeCheck(self, &MarkupType)) {
291        tmp = escape(other, 1);
292        if (tmp == NULL)
293            return NULL;
294        tmp2 = PyUnicode_Concat(self, tmp);
295    } else { // __radd__
296        tmp = escape(self, 1);
297        if (tmp == NULL)
298            return NULL;
299        tmp2 = PyUnicode_Concat(tmp, other);
300    }
301    Py_DECREF(tmp);
302    if (tmp2 == NULL)
303        return NULL;
304    args = PyTuple_New(1);
305    if (args == NULL) {
306        Py_DECREF(tmp2);
307        return NULL;
308    }
309    PyTuple_SET_ITEM(args, 0, tmp2);
310    ret = MarkupType.tp_new(&MarkupType, args, NULL);
311    Py_DECREF(args);
312    return ret;
313}
314
315static PyObject *
316Markup_mod(PyObject *self, PyObject *args)
317{
318    PyObject *tmp, *tmp2, *ret, *args2;
319    int i;
320    Py_ssize_t nargs = 0;
321    PyObject *kwds = NULL;
322
323    if (PyDict_Check(args)) {
324        kwds = args;
325    }
326    if (kwds && PyDict_Size(kwds)) {
327        PyObject *kwcopy, *key, *value;
328        Py_ssize_t pos = 0;
329
330        kwcopy = PyDict_Copy( kwds );
331        if (kwcopy == NULL) {
332            return NULL;
333        }
334        while (PyDict_Next(kwcopy, &pos, &key, &value)) {
335            tmp = escape(value, 1);
336            if (tmp == NULL) {
337                Py_DECREF(kwcopy);
338                return NULL;
339            }
340            if (PyDict_SetItem(kwcopy, key, tmp) < 0) {
341                Py_DECREF(tmp);
342                Py_DECREF(kwcopy);
343                return NULL;
344            }
345        }
346        tmp = PyUnicode_Format(self, kwcopy);
347        Py_DECREF(kwcopy);
348        if (tmp == NULL) {
349            return NULL;
350        }
351    } else if (PyTuple_Check(args)) {
352        nargs = PyTuple_GET_SIZE(args);
353        args2 = PyTuple_New(nargs);
354        if (args2 == NULL) {
355            return NULL;
356        }
357        for (i = 0; i < nargs; i++) {
358            tmp = escape(PyTuple_GET_ITEM(args, i), 1);
359            if (tmp == NULL) {
360                Py_DECREF(args2);
361                return NULL;
362            }
363            PyTuple_SET_ITEM(args2, i, tmp);
364        }
365        tmp = PyUnicode_Format(self, args2);
366        Py_DECREF(args2);
367        if (tmp == NULL) {
368            return NULL;
369        }
370    } else {
371        tmp2 = escape(args, 1);
372        if (tmp2 == NULL) {
373            return NULL;
374        }
375        tmp = PyUnicode_Format(self, tmp2);
376        Py_DECREF(tmp2);
377        if (tmp == NULL) {
378            return NULL;
379        }
380    }
381    args = PyTuple_New(1);
382    if (args == NULL) {
383        Py_DECREF(tmp);
384        return NULL;
385    }
386    PyTuple_SET_ITEM(args, 0, tmp);
387    ret = PyUnicode_Type.tp_new(&MarkupType, args, NULL);
388    Py_DECREF(args);
389    return ret;
390}
391
392static PyObject *
393Markup_mul(PyObject *self, PyObject *num)
394{
395    PyObject *unicode, *result, *args;
396
397    if (PyObject_TypeCheck(self, &MarkupType)) {
398        unicode = PyObject_Unicode(self);
399        if (unicode == NULL) return NULL;
400        result = PyNumber_Multiply(unicode, num);
401    } else { // __rmul__
402        unicode = PyObject_Unicode(num);
403        if (unicode == NULL) return NULL;
404        result = PyNumber_Multiply(unicode, self);
405    }
406    Py_DECREF(unicode);
407
408    if (result == NULL) return NULL;
409    args = PyTuple_New(1);
410    if (args == NULL) {
411        Py_DECREF(result);
412        return NULL;
413    }
414    PyTuple_SET_ITEM(args, 0, result);
415    result = PyUnicode_Type.tp_new(&MarkupType, args, NULL);
416    Py_DECREF(args);
417
418    return result;
419}
420
421static PyObject *
422Markup_repr(PyObject *self)
423{
424    PyObject *format, *result, *args;
425
426    format = PyString_FromString("<Markup %r>");
427    if (format == NULL) return NULL;
428    result = PyObject_Unicode(self);
429    if (result == NULL) {
430        Py_DECREF(format);
431        return NULL;
432    }
433    args = PyTuple_New(1);
434    if (args == NULL) {
435        Py_DECREF(format);
436        Py_DECREF(result);
437        return NULL;
438    }
439    PyTuple_SET_ITEM(args, 0, result);
440    result = PyString_Format(format, args);
441    Py_DECREF(format);
442    Py_DECREF(args);
443    return result;
444}
445
446PyDoc_STRVAR(unescape__doc__,
447"Reverse-escapes &, <, >, and \" and returns a `unicode` object.\n\
448\n\
449>>> Markup('1 &lt; 2').unescape()\n\
450u'1 < 2'\n\
451\n\
452:return: the unescaped string\n\
453:rtype: `unicode`\n\
454:see: `genshi.core.unescape`\n\
455");
456
457static PyObject *
458Markup_unescape(PyObject* self)
459{
460    PyObject *tmp, *tmp2;
461
462    tmp = PyUnicode_Replace(self, qt2, qt1, -1);
463    if (tmp == NULL) return NULL;
464    tmp2 = PyUnicode_Replace(tmp, gt2, gt1, -1);
465    Py_DECREF(tmp);
466    if (tmp2 == NULL) return NULL;
467    tmp = PyUnicode_Replace(tmp2, lt2, lt1, -1);
468    Py_DECREF(tmp2);
469    if (tmp == NULL) return NULL;
470    tmp2 = PyUnicode_Replace(tmp, amp2, amp1, -1);
471    Py_DECREF(tmp);
472    return tmp2;
473}
474
475PyDoc_STRVAR(stripentities__doc__,
476"Return a copy of the text with any character or numeric entities\n\
477replaced by the equivalent UTF-8 characters.\n\
478\n\
479If the `keepxmlentities` parameter is provided and evaluates to `True`,\n\
480the core XML entities (``&amp;``, ``&apos;``, ``&gt;``, ``&lt;`` and\n\
481``&quot;``) are not stripped.\n\
482\n\
483:return: a `Markup` instance with entities removed\n\
484:rtype: `Markup`\n\
485:see: `genshi.util.stripentities`\n\
486");
487
488static PyObject *
489Markup_stripentities(PyObject* self, PyObject *args, PyObject *kwds)
490{
491    static char *kwlist[] = {"keepxmlentities", 0};
492    PyObject *result, *args2;
493    char keepxml = 0;
494
495    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|b", kwlist, &keepxml)) {
496        return NULL;
497    }
498
499    if (stripentities == NULL) return NULL;
500    result = PyObject_CallFunction(stripentities, "Ob", self, keepxml);
501    if (result == NULL) return NULL;
502    args2 = PyTuple_New(1);
503    if (args2 == NULL) {
504        Py_DECREF(result);
505        return NULL;
506    }
507    PyTuple_SET_ITEM(args2, 0, result);
508    result = MarkupType.tp_new(&MarkupType, args2, NULL);
509    Py_DECREF(args2);
510    return result;
511}
512
513PyDoc_STRVAR(striptags__doc__,
514"""Return a copy of the text with all XML/HTML tags removed.\n\
515\n\
516:return: a `Markup` instance with all tags removed\n\
517:rtype: `Markup`\n\
518:see: `genshi.util.striptags`\n\
519");
520
521static PyObject *
522Markup_striptags(PyObject* self)
523{
524    PyObject *result, *args;
525
526    if (striptags == NULL) return NULL;
527    result = PyObject_CallFunction(striptags, "O", self);
528    if (result == NULL) return NULL;
529    args = PyTuple_New(1);
530    if (args == NULL) {
531        Py_DECREF(result);
532        return NULL;
533    }
534    PyTuple_SET_ITEM(args, 0, result);
535    result = MarkupType.tp_new(&MarkupType, args, NULL);
536    Py_DECREF(args);
537    return result;
538}
539
540typedef struct {
541    PyUnicodeObject HEAD;
542} MarkupObject;
543
544static PyMethodDef Markup_methods[] = {
545    {"__html__", (PyCFunction) Markup_html, METH_NOARGS, NULL},
546    {"escape", (PyCFunction) Markup_escape,
547     METH_VARARGS|METH_CLASS|METH_KEYWORDS, escape__doc__},
548    {"join", (PyCFunction)Markup_join, METH_VARARGS|METH_KEYWORDS, join__doc__},
549    {"unescape", (PyCFunction)Markup_unescape, METH_NOARGS, unescape__doc__},
550    {"stripentities", (PyCFunction) Markup_stripentities,
551     METH_VARARGS|METH_KEYWORDS, stripentities__doc__},
552    {"striptags", (PyCFunction) Markup_striptags, METH_NOARGS,
553     striptags__doc__},
554    {NULL}  /* Sentinel */
555};
556
557static PyNumberMethods Markup_as_number = {
558    Markup_add, /*nb_add*/
559    0, /*nb_subtract*/
560    Markup_mul, /*nb_multiply*/
561    0, /*nb_divide*/
562    Markup_mod, /*nb_remainder*/
563};
564
565PyTypeObject MarkupType = {
566    PyObject_HEAD_INIT(NULL)
567    0,
568    "genshi._speedups.Markup",
569    sizeof(MarkupObject),
570    0,
571    0,          /*tp_dealloc*/
572    0,          /*tp_print*/
573    0,          /*tp_getattr*/
574    0,          /*tp_setattr*/
575    0,          /*tp_compare*/
576    Markup_repr, /*tp_repr*/
577    &Markup_as_number, /*tp_as_number*/
578    0,          /*tp_as_sequence*/
579    0,          /*tp_as_mapping*/
580    0,          /*tp_hash */
581
582    0,          /*tp_call*/
583    0,          /*tp_str*/
584    0,          /*tp_getattro*/
585    0,          /*tp_setattro*/
586    0,          /*tp_as_buffer*/
587
588    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/
589    Markup__doc__,/*tp_doc*/
590
591    0,          /*tp_traverse*/
592    0,          /*tp_clear*/
593
594    0,          /*tp_richcompare*/
595    0,          /*tp_weaklistoffset*/
596
597    0,          /*tp_iter*/
598    0,          /*tp_iternext*/
599
600    /* Attribute descriptor and subclassing stuff */
601
602    Markup_methods,/*tp_methods*/
603    0,          /*tp_members*/
604    0,          /*tp_getset*/
605    0,          /*tp_base*/
606    0,          /*tp_dict*/
607
608    0,          /*tp_descr_get*/
609    0,          /*tp_descr_set*/
610    0,          /*tp_dictoffset*/
611
612    0,          /*tp_init*/
613    0,          /*tp_alloc  will be set to PyType_GenericAlloc in module init*/
614    0,          /*tp_new*/
615    0,          /*tp_free  Low-level free-memory routine */
616    0,          /*tp_is_gc For PyObject_IS_GC */
617    0,          /*tp_bases*/
618    0,          /*tp_mro method resolution order */
619    0,          /*tp_cache*/
620    0,          /*tp_subclasses*/
621    0           /*tp_weaklist*/
622};
623
624PyMODINIT_FUNC
625init_speedups(void)
626{
627    PyObject *module;
628
629    /* Workaround for quirk in Visual Studio, see
630        <http://www.python.it/faq/faq-3.html#3.24> */
631    MarkupType.tp_base = &PyUnicode_Type;
632
633    if (PyType_Ready(&MarkupType) < 0)
634        return;
635
636    init_constants();
637
638    module = Py_InitModule("_speedups", NULL);
639    Py_INCREF(&MarkupType);
640    PyModule_AddObject(module, "Markup", (PyObject *) &MarkupType);
641}
Note: See TracBrowser for help on using the repository browser.