Edgewall Software

source: tags/0.7.0/genshi/_speedups.c

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

Fix bug in _speedups where it differed from behaviour of Python implementation and add a test for this case (fixes #439). Fix and test contributed by cboos.

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