Edgewall Software

source: tags/0.5.0/genshi/_speedups.c

Last change on this file was 861, checked in by cmlenz, 15 years ago

Implement the __html__ protocol as suggested in #202. This would allow Genshi to be used in combination with other markup generating tools, as long as they support the same protocol.

  • Property svn:eol-style set to native
File size: 17.0 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
46PyAPI_DATA(PyTypeObject) MarkupType;
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        return type->tp_new(type, args, NULL);
198    }
199    if (PyObject_TypeCheck(text, type)) {
200        Py_INCREF(text);
201        return text;
202    }
203    return escape(text, quotes);
204}
205
206static PyObject *
207Markup_html(PyObject *self)
208{
209    Py_INCREF(self);
210    return self;
211}
212
213PyDoc_STRVAR(join__doc__,
214"Return a `Markup` object which is the concatenation of the strings\n\
215in the given sequence, where this `Markup` object is the separator\n\
216between the joined elements.\n\
217\n\
218Any element in the sequence that is not a `Markup` instance is\n\
219automatically escaped.\n\
220\n\
221:param seq: the sequence of strings to join\n\
222:param escape_quotes: whether double quote characters in the elements\n\
223                      should be escaped\n\
224:return: the joined `Markup` object\n\
225:rtype: `Markup`\n\
226:see: `escape`\n\
227");
228
229static PyObject *
230Markup_join(PyObject *self, PyObject *args, PyObject *kwds)
231{
232    static char *kwlist[] = {"seq", "escape_quotes", 0};
233    PyObject *seq = NULL, *seq2, *tmp, *tmp2;
234    char quotes = 1;
235    int n, i;
236
237    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|b", kwlist, &seq, &quotes)) {
238        return NULL;
239    }
240    if (!PySequence_Check(seq)) {
241        return NULL;
242    }
243    n = PySequence_Size(seq);
244    if (n < 0) {
245        return NULL;
246    }
247    seq2 = PyTuple_New(n);
248    if (seq2 == NULL) {
249        return NULL;
250    }
251    for (i = 0; i < n; i++) {
252        tmp = PySequence_GetItem(seq, i);
253        if (tmp == NULL) {
254            Py_DECREF(seq2);
255            return NULL;
256        }
257        tmp2 = escape(tmp, quotes);
258        if (tmp2 == NULL) {
259            Py_DECREF(seq2);
260            return NULL;
261        }
262        PyTuple_SET_ITEM(seq2, i, tmp2);
263        Py_DECREF(tmp);
264    }
265    tmp = PyUnicode_Join(self, seq2);
266    Py_DECREF(seq2);
267    if (tmp == NULL)
268        return NULL;
269    args = PyTuple_New(1);
270    if (args == NULL) {
271        Py_DECREF(tmp);
272        return NULL;
273    }
274    PyTuple_SET_ITEM(args, 0, tmp);
275    tmp = MarkupType.tp_new(&MarkupType, args, NULL);
276    Py_DECREF(args);
277    return tmp;
278}
279
280static PyObject *
281Markup_add(PyObject *self, PyObject *other)
282{
283    PyObject *tmp, *tmp2, *args, *ret;
284    if (PyObject_TypeCheck(self, &MarkupType)) {
285        tmp = escape(other, 1);
286        if (tmp == NULL)
287            return NULL;
288        tmp2 = PyUnicode_Concat(self, tmp);
289    } else { // __radd__
290        tmp = escape(self, 1);
291        if (tmp == NULL)
292            return NULL;
293        tmp2 = PyUnicode_Concat(tmp, other);
294    }
295    Py_DECREF(tmp);
296    if (tmp2 == NULL)
297        return NULL;
298    args = PyTuple_New(1);
299    if (args == NULL) {
300        Py_DECREF(tmp2);
301        return NULL;
302    }
303    PyTuple_SET_ITEM(args, 0, tmp2);
304    ret = MarkupType.tp_new(&MarkupType, args, NULL);
305    Py_DECREF(args);
306    return ret;
307}
308
309static PyObject *
310Markup_mod(PyObject *self, PyObject *args)
311{
312    PyObject *tmp, *tmp2, *ret, *args2;
313    int i, nargs = 0;
314    PyObject *kwds = NULL;
315
316    if (PyDict_Check(args)) {
317        kwds = args;
318    }
319    if (kwds && PyDict_Size(kwds)) {
320        PyObject *kwcopy, *key, *value;
321        Py_ssize_t pos = 0;
322
323        kwcopy = PyDict_Copy( kwds );
324        if (kwcopy == NULL) {
325            return NULL;
326        }
327        while (PyDict_Next(kwcopy, &pos, &key, &value)) {
328            tmp = escape(value, 1);
329            if (tmp == NULL) {
330                Py_DECREF(kwcopy);
331                return NULL;
332            }
333            if (PyDict_SetItem(kwcopy, key, tmp) < 0) {
334                Py_DECREF(tmp);
335                Py_DECREF(kwcopy);
336                return NULL;
337            }
338        }
339        tmp = PyUnicode_Format(self, kwcopy);
340        Py_DECREF(kwcopy);
341        if (tmp == NULL) {
342            return NULL;
343        }
344    } else if (PyTuple_Check(args)) {
345        nargs = PyTuple_GET_SIZE(args);
346        args2 = PyTuple_New(nargs);
347        if (args2 == NULL) {
348            return NULL;
349        }
350        for (i = 0; i < nargs; i++) {
351            tmp = escape(PyTuple_GET_ITEM(args, i), 1);
352            if (tmp == NULL) {
353                Py_DECREF(args2);
354                return NULL;
355            }
356            PyTuple_SET_ITEM(args2, i, tmp);
357        }
358        tmp = PyUnicode_Format(self, args2);
359        Py_DECREF(args2);
360        if (tmp == NULL) {
361            return NULL;
362        }
363    } else {
364        tmp2 = escape(args, 1);
365        if (tmp2 == NULL) {
366            return NULL;
367        }
368        tmp = PyUnicode_Format(self, tmp2);
369        Py_DECREF(tmp2);
370        if (tmp == NULL) {
371            return NULL;
372        }
373    }
374    args = PyTuple_New(1);
375    if (args == NULL) {
376        Py_DECREF(tmp);
377        return NULL;
378    }
379    PyTuple_SET_ITEM(args, 0, tmp);
380    ret = PyUnicode_Type.tp_new(&MarkupType, args, NULL);
381    Py_DECREF(args);
382    return ret;
383}
384
385static PyObject *
386Markup_mul(PyObject *self, PyObject *num)
387{
388    PyObject *unicode, *result, *args;
389
390    if (PyObject_TypeCheck(self, &MarkupType)) {
391        unicode = PyObject_Unicode(self);
392        if (unicode == NULL) return NULL;
393        result = PyNumber_Multiply(unicode, num);
394    } else { // __rmul__
395        unicode = PyObject_Unicode(num);
396        if (unicode == NULL) return NULL;
397        result = PyNumber_Multiply(unicode, self);
398    }
399    Py_DECREF(unicode);
400
401    if (result == NULL) return NULL;
402    args = PyTuple_New(1);
403    if (args == NULL) {
404        Py_DECREF(result);
405        return NULL;
406    }
407    PyTuple_SET_ITEM(args, 0, result);
408    result = PyUnicode_Type.tp_new(&MarkupType, args, NULL);
409    Py_DECREF(args);
410
411    return result;
412}
413
414static PyObject *
415Markup_repr(PyObject *self)
416{
417    PyObject *format, *result, *args;
418
419    format = PyString_FromString("<Markup %r>");
420    if (format == NULL) return NULL;
421    result = PyObject_Unicode(self);
422    if (result == NULL) {
423        Py_DECREF(format);
424        return NULL;
425    }
426    args = PyTuple_New(1);
427    if (args == NULL) {
428        Py_DECREF(format);
429        Py_DECREF(result);
430        return NULL;
431    }
432    PyTuple_SET_ITEM(args, 0, result);
433    result = PyString_Format(format, args);
434    Py_DECREF(format);
435    Py_DECREF(args);
436    return result;
437}
438
439PyDoc_STRVAR(unescape__doc__,
440"Reverse-escapes &, <, >, and \" and returns a `unicode` object.\n\
441\n\
442>>> Markup('1 &lt; 2').unescape()\n\
443u'1 < 2'\n\
444\n\
445:return: the unescaped string\n\
446:rtype: `unicode`\n\
447:see: `genshi.core.unescape`\n\
448");
449
450static PyObject *
451Markup_unescape(PyObject* self)
452{
453    PyObject *tmp, *tmp2;
454
455    tmp = PyUnicode_Replace(self, qt2, qt1, -1);
456    if (tmp == NULL) return NULL;
457    tmp2 = PyUnicode_Replace(tmp, gt2, gt1, -1);
458    Py_DECREF(tmp);
459    if (tmp2 == NULL) return NULL;
460    tmp = PyUnicode_Replace(tmp2, lt2, lt1, -1);
461    Py_DECREF(tmp2);
462    if (tmp == NULL) return NULL;
463    tmp2 = PyUnicode_Replace(tmp, amp2, amp1, -1);
464    Py_DECREF(tmp);
465    return tmp2;
466}
467
468PyDoc_STRVAR(stripentities__doc__,
469"Return a copy of the text with any character or numeric entities\n\
470replaced by the equivalent UTF-8 characters.\n\
471\n\
472If the `keepxmlentities` parameter is provided and evaluates to `True`,\n\
473the core XML entities (``&amp;``, ``&apos;``, ``&gt;``, ``&lt;`` and\n\
474``&quot;``) are not stripped.\n\
475\n\
476:return: a `Markup` instance with entities removed\n\
477:rtype: `Markup`\n\
478:see: `genshi.util.stripentities`\n\
479");
480
481static PyObject *
482Markup_stripentities(PyObject* self, PyObject *args, PyObject *kwds)
483{
484    static char *kwlist[] = {"keepxmlentities", 0};
485    PyObject *result, *args2;
486    char keepxml = 0;
487
488    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|b", kwlist, &keepxml)) {
489        return NULL;
490    }
491
492    if (stripentities == NULL) return NULL;
493    result = PyObject_CallFunction(stripentities, "Ob", self, keepxml);
494    if (result == NULL) return NULL;
495    args2 = PyTuple_New(1);
496    if (args2 == NULL) {
497        Py_DECREF(result);
498        return NULL;
499    }
500    PyTuple_SET_ITEM(args2, 0, result);
501    result = MarkupType.tp_new(&MarkupType, args2, NULL);
502    Py_DECREF(args2);
503    return result;
504}
505
506PyDoc_STRVAR(striptags__doc__,
507"""Return a copy of the text with all XML/HTML tags removed.\n\
508\n\
509:return: a `Markup` instance with all tags removed\n\
510:rtype: `Markup`\n\
511:see: `genshi.util.striptags`\n\
512");
513
514static PyObject *
515Markup_striptags(PyObject* self)
516{
517    PyObject *result, *args;
518
519    if (striptags == NULL) return NULL;
520    result = PyObject_CallFunction(striptags, "O", self);
521    if (result == NULL) return NULL;
522    args = PyTuple_New(1);
523    if (args == NULL) {
524        Py_DECREF(result);
525        return NULL;
526    }
527    PyTuple_SET_ITEM(args, 0, result);
528    result = MarkupType.tp_new(&MarkupType, args, NULL);
529    Py_DECREF(args);
530    return result;
531}
532
533typedef struct {
534    PyUnicodeObject HEAD;
535} MarkupObject;
536
537static PyMethodDef Markup_methods[] = {
538    {"__html__", (PyCFunction) Markup_html, METH_NOARGS, NULL},
539    {"escape", (PyCFunction) Markup_escape,
540     METH_VARARGS|METH_CLASS|METH_KEYWORDS, escape__doc__},
541    {"join", (PyCFunction)Markup_join, METH_VARARGS|METH_KEYWORDS, join__doc__},
542    {"unescape", (PyCFunction)Markup_unescape, METH_NOARGS, unescape__doc__},
543    {"stripentities", (PyCFunction) Markup_stripentities,
544     METH_VARARGS|METH_KEYWORDS, stripentities__doc__},
545    {"striptags", (PyCFunction) Markup_striptags, METH_NOARGS,
546     striptags__doc__},
547    {NULL}  /* Sentinel */
548};
549
550static PyNumberMethods Markup_as_number = {
551    Markup_add, /*nb_add*/
552    0, /*nb_subtract*/
553    Markup_mul, /*nb_multiply*/
554    0, /*nb_divide*/
555    Markup_mod, /*nb_remainder*/
556};
557
558PyTypeObject MarkupType = {
559    PyObject_HEAD_INIT(NULL)
560    0,
561    "genshi._speedups.Markup",
562    sizeof(MarkupObject),
563    0,
564    0,          /*tp_dealloc*/
565    0,          /*tp_print*/
566    0,          /*tp_getattr*/
567    0,          /*tp_setattr*/
568    0,          /*tp_compare*/
569    Markup_repr, /*tp_repr*/
570    &Markup_as_number, /*tp_as_number*/
571    0,          /*tp_as_sequence*/
572    0,          /*tp_as_mapping*/
573    0,          /*tp_hash */
574
575    0,          /*tp_call*/
576    0,          /*tp_str*/
577    0,          /*tp_getattro*/
578    0,          /*tp_setattro*/
579    0,          /*tp_as_buffer*/
580
581    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/
582    Markup__doc__,/*tp_doc*/
583
584    0,          /*tp_traverse*/
585    0,          /*tp_clear*/
586
587    0,          /*tp_richcompare*/
588    0,          /*tp_weaklistoffset*/
589
590    0,          /*tp_iter*/
591    0,          /*tp_iternext*/
592
593    /* Attribute descriptor and subclassing stuff */
594
595    Markup_methods,/*tp_methods*/
596    0,          /*tp_members*/
597    0,          /*tp_getset*/
598    0,          /*tp_base*/
599    0,          /*tp_dict*/
600
601    0,          /*tp_descr_get*/
602    0,          /*tp_descr_set*/
603    0,          /*tp_dictoffset*/
604
605    0,          /*tp_init*/
606    0,          /*tp_alloc  will be set to PyType_GenericAlloc in module init*/
607    0,          /*tp_new*/
608    0,          /*tp_free  Low-level free-memory routine */
609    0,          /*tp_is_gc For PyObject_IS_GC */
610    0,          /*tp_bases*/
611    0,          /*tp_mro method resolution order */
612    0,          /*tp_cache*/
613    0,          /*tp_subclasses*/
614    0           /*tp_weaklist*/
615};
616
617PyMODINIT_FUNC
618init_speedups(void)
619{
620    PyObject *module;
621
622    /* Workaround for quirk in Visual Studio, see
623        <http://www.python.it/faq/faq-3.html#3.24> */
624    MarkupType.tp_base = &PyUnicode_Type;
625
626    if (PyType_Ready(&MarkupType) < 0)
627        return;
628
629    init_constants();
630
631    module = Py_InitModule("_speedups", NULL);
632    Py_INCREF(&MarkupType);
633    PyModule_AddObject(module, "Markup", (PyObject *) &MarkupType);
634}
Note: See TracBrowser for help on using the repository browser.