SVN: r25339 - in trunk/quixote: . form2 src test
Neil Schemenauer <nascheme-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Thu, 14 Oct 2004 13:18:06 -0400
| Newsgroups | gmane.comp.web.quixote.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: nascheme
Date: 2004-10-14 13:17:51 -0400 (Thu, 14 Oct 2004)
New Revision: 25339
Modified:
trunk/quixote/_py_htmltext.py
trunk/quixote/errors.py
trunk/quixote/form2/widget.py
trunk/quixote/html.py
trunk/quixote/http_response.py
trunk/quixote/publish.py
trunk/quixote/src/_c_htmltext.c
trunk/quixote/src/setup.py
trunk/quixote/test/utest_html.py
Log:
Add support for unicode.
Modified: trunk/quixote/_py_htmltext.py
===================================================================
--- trunk/quixote/_py_htmltext.py 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/_py_htmltext.py 2004-10-14 17:17:51 UTC (rev 25339)
@@ -5,37 +5,39 @@
#$HeadURL$
#$Id$
-import sys
-from types import UnicodeType, TupleType, StringType, IntType, FloatType, \
- LongType
import re
-if sys.hexversion < 0x20200b1:
- # 2.2 compatibility hacks
- class object:
- pass
-
- def classof(o):
- if hasattr(o, "__class__"):
- return o.__class__
- else:
- return type(o)
-
-else:
- classof = type
-
_format_codes = 'diouxXeEfFgGcrs%'
_format_re = re.compile(r'%%[^%s]*[%s]' % (_format_codes, _format_codes))
def _escape_string(s):
- if not isinstance(s, StringType):
- raise TypeError, 'string required'
+ if not isinstance(s, basestring):
+ raise TypeError, 'string object required'
s = s.replace("&", "&")
s = s.replace("<", "<")
s = s.replace(">", ">")
s = s.replace('"', """)
return s
+def stringify(obj):
+ """Return 'obj' as a string or unicode object. Tries to prevent
+ turning strings into unicode objects.
+ """
+ if isinstance(obj, basestring):
+ return obj
+ elif hasattr(obj, '__unicode__'):
+ s = obj.__unicode__()
+ if not isinstance(s, basestring):
+ raise TypeError, '__unicode__ did not return a string'
+ return s
+ elif hasattr(obj, '__str__'):
+ s = obj.__str__()
+ if not isinstance(s, basestring):
+ raise TypeError, '__str__ did not return a string'
+ return s
+ else:
+ return str(obj)
+
class htmltext(object):
"""The htmltext string-like type. This type serves as a tag
signifying that HTML special characters do not need to be escaped
@@ -45,7 +47,7 @@
__slots__ = ['s']
def __init__(self, s):
- self.s = str(s)
+ self.s = stringify(s)
# XXX make read-only
#def __setattr__(self, name, value):
@@ -80,39 +82,39 @@
if usedict:
args = _DictWrapper(args)
else:
- if len(codes) == 1 and not isinstance(args, TupleType):
+ if len(codes) == 1 and not isinstance(args, tuple):
args = (args,)
- args = tuple([_wraparg(arg) for arg in args])
- return self.__class__(self.s % args)
+ args = tuple(map(_wraparg, args))
+ return htmltext(self.s % args)
def __add__(self, other):
- if isinstance(other, StringType):
- return self.__class__(self.s + _escape_string(other))
- elif classof(other) is self.__class__:
- return self.__class__(self.s + other.s)
+ if isinstance(other, basestring):
+ return htmltext(self.s + _escape_string(other))
+ elif isinstance(other, htmltext):
+ return htmltext(self.s + other.s)
else:
return NotImplemented
def __radd__(self, other):
- if isinstance(other, StringType):
- return self.__class__(_escape_string(other) + self.s)
+ if isinstance(other, basestring):
+ return htmltext(_escape_string(other) + self.s)
else:
return NotImplemented
def __mul__(self, n):
- return self.__class__(self.s * n)
+ return htmltext(self.s * n)
def join(self, items):
quoted_items = []
for item in items:
- if classof(item) is self.__class__:
- quoted_items.append(str(item))
- elif isinstance(item, StringType):
+ if isinstance(item, htmltext):
+ quoted_items.append(stringify(item))
+ elif isinstance(item, basestring):
quoted_items.append(_escape_string(item))
else:
raise TypeError(
'join() requires string arguments (got %r)' % item)
- return self.__class__(self.s.join(quoted_items))
+ return htmltext(self.s.join(quoted_items))
def startswith(self, s):
if isinstance(s, htmltext):
@@ -137,31 +139,30 @@
new = new.s
else:
new = _escape_string(new)
- return self.__class__(self.s.replace(old, new))
+ return htmltext(self.s.replace(old, new))
def lower(self):
- return self.__class__(self.s.lower())
+ return htmltext(self.s.lower())
def upper(self):
- return self.__class__(self.s.upper())
+ return htmltext(self.s.upper())
def capitalize(self):
- return self.__class__(self.s.capitalize())
+ return htmltext(self.s.capitalize())
class _QuoteWrapper(object):
# helper for htmltext class __mod__
- __slots__ = ['value', 'escape']
+ __slots__ = ['value']
- def __init__(self, value, escape):
+ def __init__(self, value):
self.value = value
- self.escape = escape
def __str__(self):
- return self.escape(str(self.value))
+ return _escape_string(stringify(self.value))
def __repr__(self):
- return self.escape(`self.value`)
+ return _escape_string(`self.value`)
class _DictWrapper(object):
def __init__(self, value):
@@ -171,15 +172,15 @@
return _wraparg(self.value[key])
def _wraparg(arg):
- if (classof(arg) is htmltext or
- isinstance(arg, IntType) or
- isinstance(arg, LongType) or
- isinstance(arg, FloatType)):
+ if (isinstance(arg, htmltext) or
+ isinstance(arg, int) or
+ isinstance(arg, long) or
+ isinstance(arg, float)):
# ints, longs, floats, and htmltext are okay
return arg
else:
# everything is gets wrapped
- return _QuoteWrapper(arg, _escape_string)
+ return _QuoteWrapper(arg)
def htmlescape(s):
"""htmlescape(s) -> htmltext
@@ -188,12 +189,10 @@
already a 'htmltext' object then the HTML markup characters \", <, >,
and & are first escaped.
"""
- if classof(s) is htmltext:
+ if isinstance(s, htmltext):
return s
- elif isinstance(s, UnicodeType):
- s = s.encode('iso-8859-1')
else:
- s = str(s)
+ s = stringify(s)
# inline _escape_string for speed
s = s.replace("&", "&") # must be done first
s = s.replace("<", "<")
@@ -222,10 +221,10 @@
(self.__class__.__name__, id(self), len(self.data)))
def __str__(self):
- return str(self.getvalue())
+ return stringify(self.getvalue())
def getvalue(self):
if self.html:
return htmltext('').join(map(htmlescape, self.data))
else:
- return ''.join(map(str, self.data))
+ return ''.join(map(stringify, self.data))
Modified: trunk/quixote/errors.py
===================================================================
--- trunk/quixote/errors.py 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/errors.py 2004-10-14 17:17:51 UTC (rev 25339)
@@ -45,8 +45,6 @@
def format(self, request):
msg = htmlescape(self.title)
- if not isinstance(self.title, htmltext):
- msg = str(msg) # for backwards compatibility
if self.public_msg:
msg = msg + ": " + self.public_msg
if self.private_msg:
@@ -80,8 +78,6 @@
def format(self, request):
msg = htmlescape(self.title) + ": " + self.path
- if not isinstance(self.title, htmltext):
- msg = str(msg) # for backwards compatibility
if self.public_msg:
msg = msg + ": " + self.public_msg
if self.private_msg:
Modified: trunk/quixote/form2/widget.py
===================================================================
--- trunk/quixote/form2/widget.py 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/form2/widget.py 2004-10-14 17:17:51 UTC (rev 25339)
@@ -8,7 +8,7 @@
import struct
from types import FloatType, IntType, ListType, StringType, TupleType
from quixote import get_request
-from quixote.html import htmltext, htmlescape, htmltag, TemplateIO
+from quixote.html import htmltext, htmlescape, htmltag, TemplateIO, stringify
from quixote.upload import Upload
def subname(prefix, name):
@@ -40,9 +40,10 @@
self.msg = msg
def __str__(self):
- return str(self.msg)
+ return stringify(self.msg)
+
class Widget:
"""Abstract base class for web widgets.
@@ -125,7 +126,7 @@
try:
self._parse(request)
except WidgetValueError, exc:
- self.set_error(str(exc))
+ self.set_error(stringify(exc))
if (self.required and self.value is None and
not self.has_error()):
self.set_error('required')
@@ -134,7 +135,7 @@
def _parse(self, request):
# subclasses may override but this is not part of the public API
value = request.form.get(self.name)
- if type(value) is StringType and value.strip():
+ if isinstance(value, basestring) and value.strip():
self.value = value
else:
self.value = None
@@ -376,7 +377,7 @@
for value, description, key in options:
values.append(value)
descriptions.append(description)
- keys.append(str(key))
+ keys.append(stringify(key))
else:
raise ValueError, 'invalid options %r' % options
else:
@@ -393,7 +394,7 @@
if value is None:
return ('', option)
else:
- return (str(description).lower(), option)
+ return (stringify(description).lower(), option)
doptions = map(make_sort_key, options)
doptions.sort()
options = [item[1] for item in doptions]
Modified: trunk/quixote/html.py
===================================================================
--- trunk/quixote/html.py 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/html.py 2004-10-14 17:17:51 UTC (rev 25339)
@@ -44,10 +44,10 @@
try:
# faster C implementation
from quixote._c_htmltext import htmltext, htmlescape, _escape_string, \
- TemplateIO
+ stringify, TemplateIO
except ImportError:
from quixote._py_htmltext import htmltext, htmlescape, _escape_string, \
- TemplateIO
+ stringify, TemplateIO
ValuelessAttr = object() # magic singleton object
@@ -97,8 +97,4 @@
raise ValueError, "value is None and no fallback supplied"
else:
return fallback
- if isinstance(value, unicode):
- value = value.encode('iso-8859-1')
- else:
- value = str(value)
- return urllib.quote(value)
+ return urllib.quote(stringify(value))
Modified: trunk/quixote/http_response.py
===================================================================
--- trunk/quixote/http_response.py 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/http_response.py 2004-10-14 17:17:51 UTC (rev 25339)
@@ -87,6 +87,8 @@
after all).
Instance attributes:
+ charset : string
+ the default character encoding of the the response
status_code : int
HTTP response status code (integer between 100 and 599)
reason_phrase : string
@@ -126,10 +128,11 @@
else's problem.
"""
- def __init__(self, status=200, body=None):
+ def __init__(self, status=200, body=None, charset='iso-8859-1'):
"""
Creates a new HTTP response.
"""
+ self.charset = charset
self.set_status(status)
self.headers = {}
@@ -143,6 +146,9 @@
self.buffered = True
self.javascript_code = None
+ def set_charset(self, charset):
+ self.charset = charset.lower()
+
def set_status(self, status, reason=None):
"""set_status(status : int, reason : string = None)
@@ -208,10 +214,14 @@
if body.length is not None:
self.set_header('content-length', body.length)
else:
- self.body = str(body)
+ if self.charset == 'iso-8859-1':
+ self.body = str(body)
+ else:
+ self.body = unicode(body).encode(self.charset)
self.set_header('content-length', len(self.body))
if not self.headers.has_key('content-type'):
- self.set_header('content-type', 'text/html; charset=iso-8859-1')
+ self.set_header('content-type',
+ 'text/html; charset=%s' % self.charset)
def expire_cookie(self, name, **attrs):
"""
@@ -389,6 +399,8 @@
if self.body is not None:
if isinstance(self.body, Stream):
for chunk in self.body:
+ if isinstance(chunk, unicode):
+ chunk = chunk.encode(self.charset)
file.write(chunk)
if flush_output:
file.flush()
Modified: trunk/quixote/publish.py
===================================================================
--- trunk/quixote/publish.py 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/publish.py 2004-10-14 17:17:51 UTC (rev 25339)
@@ -504,10 +504,12 @@
"""Hook for post processing the output. Subclasses may wish to
override (e.g. check HTML syntax).
"""
- if (output and
- self.config.compress_pages and
- not isinstance(output, Stream)):
- output = self.compress_output(request, str(output))
+ if 0:
+ # XXX need to use charset from response
+ if (output and
+ self.config.compress_pages and
+ not isinstance(output, Stream)):
+ output = self.compress_output(request, str(output))
return output
def process_request(self, request, env):
@@ -892,7 +894,7 @@
if sys.hexversion >= 0x02020000: # Python 2.2 or greater
def isstring(x):
- return isinstance(x, (str, unicode, htmltext))
+ return isinstance(x, (basestring, htmltext))
else:
if hasattr(types, 'UnicodeType'):
_string_types = (types.StringType, types.UnicodeType)
Modified: trunk/quixote/src/_c_htmltext.c
===================================================================
--- trunk/quixote/src/_c_htmltext.c 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/src/_c_htmltext.c 2004-10-14 17:17:51 UTC (rev 25339)
@@ -5,7 +5,7 @@
typedef struct {
PyObject_HEAD
- PyStringObject *s;
+ PyObject *s;
} htmltextObject;
static PyTypeObject htmltext_Type;
@@ -36,10 +36,8 @@
typedef struct {
PyObject_HEAD
+ PyObject *data; /* PyList_Object */
int html;
- char *buf;
- size_t size;
- size_t pos;
} TemplateIO_Object;
static PyTypeObject TemplateIO_Type;
@@ -54,19 +52,58 @@
return NULL;
}
+static int
+string_check(PyObject *v)
+{
+ return PyUnicode_Check(v) || PyString_Check(v);
+}
+
static PyObject *
-escape_string(PyObject *s)
+stringify(PyObject *obj)
{
- PyObject *new_s;
- char *ss, *new_ss;
+ static PyObject *unicodestr = NULL;
+ PyObject *res, *func;
+ if (string_check(obj)) {
+ Py_INCREF(obj);
+ return obj;
+ }
+ if (unicodestr == NULL) {
+ unicodestr = PyString_InternFromString("__unicode__");
+ if (unicodestr == NULL)
+ return NULL;
+ }
+ func = PyObject_GetAttr(obj, unicodestr);
+ if (func != NULL) {
+ res = PyEval_CallObject(func, (PyObject *)NULL);
+ Py_DECREF(func);
+ }
+ else {
+ PyErr_Clear();
+ if (obj->ob_type->tp_str != NULL)
+ res = (*obj->ob_type->tp_str)(obj);
+ else
+ res = PyObject_Repr(obj);
+ }
+ if (res == NULL)
+ return NULL;
+ if (!string_check(res)) {
+ Py_DECREF(res);
+ return type_error("string object required");
+ }
+ return res;
+}
+
+static PyObject *
+escape_string(PyObject *obj)
+{
+ char *s;
+ PyObject *newobj;
size_t i, j, extra_space, size, new_size;
- if (!PyString_Check(s))
- return type_error("str object required");
- ss = PyString_AS_STRING(s);
- size = PyString_GET_SIZE(s);
+ assert (PyString_Check(obj));
+ size = PyString_GET_SIZE(obj);
extra_space = 0;
for (i=0; i < size; i++) {
- switch (ss[i]) {
+ switch (PyString_AS_STRING(obj)[i]) {
case '&':
extra_space += 4;
break;
@@ -80,53 +117,138 @@
}
}
if (extra_space == 0) {
- Py_INCREF(s);
- return (PyObject *)s;
+ Py_INCREF(obj);
+ return (PyObject *)obj;
}
new_size = size + extra_space;
- new_s = PyString_FromStringAndSize(NULL, new_size);
- if (new_s == NULL)
+ newobj = PyString_FromStringAndSize(NULL, new_size);
+ if (newobj == NULL)
return NULL;
- new_ss = PyString_AsString(new_s);
+ s = PyString_AS_STRING(newobj);
for (i=0, j=0; i < size; i++) {
- switch (ss[i]) {
+ switch (PyString_AS_STRING(obj)[i]) {
case '&':
- new_ss[j++] = '&';
- new_ss[j++] = 'a';
- new_ss[j++] = 'm';
- new_ss[j++] = 'p';
- new_ss[j++] = ';';
+ s[j++] = '&';
+ s[j++] = 'a';
+ s[j++] = 'm';
+ s[j++] = 'p';
+ s[j++] = ';';
break;
case '<':
- new_ss[j++] = '&';
- new_ss[j++] = 'l';
- new_ss[j++] = 't';
- new_ss[j++] = ';';
+ s[j++] = '&';
+ s[j++] = 'l';
+ s[j++] = 't';
+ s[j++] = ';';
break;
case '>':
- new_ss[j++] = '&';
- new_ss[j++] = 'g';
- new_ss[j++] = 't';
- new_ss[j++] = ';';
+ s[j++] = '&';
+ s[j++] = 'g';
+ s[j++] = 't';
+ s[j++] = ';';
break;
case '"':
- new_ss[j++] = '&';
- new_ss[j++] = 'q';
- new_ss[j++] = 'u';
- new_ss[j++] = 'o';
- new_ss[j++] = 't';
- new_ss[j++] = ';';
+ s[j++] = '&';
+ s[j++] = 'q';
+ s[j++] = 'u';
+ s[j++] = 'o';
+ s[j++] = 't';
+ s[j++] = ';';
break;
default:
- new_ss[j++] = ss[i];
+ s[j++] = PyString_AS_STRING(obj)[i];
break;
}
}
assert (j == new_size);
- return (PyObject *)new_s;
+ return (PyObject *)newobj;
}
static PyObject *
+escape_unicode(PyObject *obj)
+{
+ Py_UNICODE *u;
+ PyObject *newobj;
+ size_t i, j, extra_space, size, new_size;
+ assert (PyUnicode_Check(obj));
+ size = PyUnicode_GET_SIZE(obj);
+ extra_space = 0;
+ for (i=0; i < size; i++) {
+ switch (PyUnicode_AS_UNICODE(obj)[i]) {
+ case '&':
+ extra_space += 4;
+ break;
+ case '<':
+ case '>':
+ extra_space += 3;
+ break;
+ case '"':
+ extra_space += 5;
+ break;
+ }
+ }
+ if (extra_space == 0) {
+ Py_INCREF(obj);
+ return (PyObject *)obj;
+ }
+ new_size = size + extra_space;
+ newobj = PyUnicode_FromUnicode(NULL, new_size);
+ if (newobj == NULL) {
+ return NULL;
+ }
+ u = PyUnicode_AS_UNICODE(newobj);
+ for (i=0, j=0; i < size; i++) {
+ switch (PyUnicode_AS_UNICODE(obj)[i]) {
+ case '&':
+ u[j++] = '&';
+ u[j++] = 'a';
+ u[j++] = 'm';
+ u[j++] = 'p';
+ u[j++] = ';';
+ break;
+ case '<':
+ u[j++] = '&';
+ u[j++] = 'l';
+ u[j++] = 't';
+ u[j++] = ';';
+ break;
+ case '>':
+ u[j++] = '&';
+ u[j++] = 'g';
+ u[j++] = 't';
+ u[j++] = ';';
+ break;
+ case '"':
+ u[j++] = '&';
+ u[j++] = 'q';
+ u[j++] = 'u';
+ u[j++] = 'o';
+ u[j++] = 't';
+ u[j++] = ';';
+ break;
+ default:
+ u[j++] = PyUnicode_AS_UNICODE(obj)[i];
+ break;
+ }
+ }
+ assert (j == new_size);
+ return (PyObject *)newobj;
+}
+
+static PyObject *
+escape(PyObject *obj)
+{
+ if (PyString_Check(obj)) {
+ return escape_string(obj);
+ }
+ else if (PyUnicode_Check(obj)) {
+ return escape_unicode(obj);
+ }
+ else {
+ return type_error("string object required");
+ }
+}
+
+static PyObject *
quote_wrapper_new(PyObject *o)
{
QuoteWrapperObject *self;
@@ -160,7 +282,7 @@
PyObject *s = PyObject_Repr(self->obj);
if (s == NULL)
return NULL;
- qs = escape_string(s);
+ qs = escape(s);
Py_DECREF(s);
return qs;
}
@@ -169,10 +291,10 @@
quote_wrapper_str(QuoteWrapperObject *self)
{
PyObject *qs;
- PyObject *s = PyObject_Str(self->obj);
+ PyObject *s = stringify(self->obj);
if (s == NULL)
return NULL;
- qs = escape_string(s);
+ qs = escape(s);
Py_DECREF(s);
return qs;
}
@@ -212,16 +334,17 @@
static PyObject *
htmltext_from_string(PyObject *s)
{
- /* note, this takes a reference */
+ /* note, this steals a reference */
PyObject *self;
if (s == NULL)
return NULL;
- assert (PyString_Check(s));
+ assert (string_check(s));
self = PyType_GenericAlloc(&htmltext_Type, 0);
if (self == NULL) {
+ Py_DECREF(s);
return NULL;
}
- ((htmltextObject *)self)->s = (PyStringObject *)s;
+ ((htmltextObject *)self)->s = s;
return self;
}
@@ -234,7 +357,7 @@
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:htmltext", kwlist,
&s))
return NULL;
- s = PyObject_Str(s);
+ s = stringify(s);
if (s == NULL)
return NULL;
self = (htmltextObject *)type->tp_alloc(type, 0);
@@ -242,7 +365,7 @@
Py_DECREF(s);
return NULL;
}
- self->s = (PyStringObject *)s;
+ self->s = s;
return (PyObject *)self;
}
@@ -283,32 +406,19 @@
static PyObject *
htmltext_richcompare(PyObject *a, PyObject *b, int op)
{
- PyObject *sa, *sb;
- if (PyString_Check(a)) {
- sa = a;
- } else if (htmltextObject_Check(a)) {
- sa = htmltext_STR(a);
- } else {
- goto fail;
+ if (htmltextObject_Check(a)) {
+ a = htmltext_STR(a);
}
- if (PyString_Check(b)) {
- sb = b;
- } else if (htmltextObject_Check(b)) {
- sb = htmltext_STR(b);
- } else {
- goto fail;
+ if (htmltextObject_Check(b)) {
+ b = htmltext_STR(b);
}
- return sa->ob_type->tp_richcompare(sa, sb, op);
-
-fail:
- Py_INCREF(Py_NotImplemented);
- return Py_NotImplemented;
+ return PyObject_RichCompare(a, b, op);
}
static long
htmltext_length(htmltextObject *self)
{
- return ((PyStringObject *)self->s)->ob_size;
+ return PyObject_Size(htmltext_STR(self));
}
@@ -320,30 +430,42 @@
/* don't bother with wrapper object */
warg = arg;
Py_INCREF(arg);
- } else {
+ }
+ else {
warg = quote_wrapper_new(arg);
}
return warg;
}
+
static PyObject *
htmltext_format(htmltextObject *self, PyObject *args)
{
/* wrap the format arguments with QuoteWrapperObject */
- int do_dict = 0;
+ int do_dict = 0, is_unicode;
PyObject *rv, *wargs;
+ if (PyUnicode_Check(self->s)) {
+ is_unicode = 1;
+ }
+ else {
+ is_unicode = 0;
+ assert (PyString_Check(self->s));
+ }
if (args->ob_type->tp_as_mapping && !PyTuple_Check(args) &&
- !PyString_Check(args)) {
- char *fmt = PyString_AS_STRING(htmltext_STR(self));
- size_t i, n = PyString_GET_SIZE(htmltext_STR(self));
- char last = 0;
+ !string_check(args)) {
+ Py_UNICODE fmt_char, last_char = 0;
+ size_t i, n = PyObject_Size(self->s);
/* second check necessary since '%s' % {} => '{}' */
for (i=0; i < n; i++) {
- if (last == '%' && fmt[i] == '(') {
+ if (is_unicode)
+ fmt_char = PyUnicode_AS_UNICODE(self->s)[i];
+ else
+ fmt_char = PyString_AS_STRING(self->s)[i];
+ if (last_char == '%' && fmt_char == '(') {
do_dict = 1;
break;
}
- last = fmt[i];
+ last_char = fmt_char;
}
}
if (do_dict) {
@@ -369,7 +491,10 @@
return NULL;
}
}
- rv = PyString_Format((PyObject *)self->s, wargs);
+ if (is_unicode)
+ rv = PyUnicode_Format(self->s, wargs);
+ else
+ rv = PyString_Format(self->s, wargs);
Py_DECREF(wargs);
return htmltext_from_string(rv);
}
@@ -377,24 +502,24 @@
static PyObject *
htmltext_add(PyObject *v, PyObject *w)
{
- PyObject *qv, *qw;
+ PyObject *qv, *qw, *rv;
if (htmltextObject_Check(v) && htmltextObject_Check(w)) {
qv = htmltext_STR(v);
qw = htmltext_STR(w);
Py_INCREF(qv);
Py_INCREF(qw);
}
- else if (PyString_Check(w)) {
+ else if (string_check(w)) {
assert (htmltextObject_Check(v));
qv = htmltext_STR(v);
- qw = escape_string(w);
+ qw = escape(w);
if (qw == NULL)
return NULL;
Py_INCREF(qv);
}
- else if (PyString_Check(v)) {
+ else if (string_check(v)) {
assert (htmltextObject_Check(w));
- qv = escape_string(v);
+ qv = escape(v);
if (qv == NULL)
return NULL;
qw = htmltext_STR(w);
@@ -404,8 +529,10 @@
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
- PyString_ConcatAndDel(&qv, qw);
- return htmltext_from_string(qv);
+ rv = PyUnicode_Concat(qv, qw);
+ Py_DECREF(qv);
+ Py_DECREF(qw);
+ return htmltext_from_string(rv);
}
static PyObject *
@@ -420,44 +547,46 @@
static PyObject *
htmltext_join(PyObject *self, PyObject *args)
{
- long i;
- PyObject *qargs, *rv;
- if (!PySequence_Check(args)) {
- return type_error("argument must be a sequence");
- }
- qargs = PyList_New(PySequence_Size(args));
- if (qargs == NULL)
+ int i;
+ PyObject *quoted_args, *rv;
+
+ quoted_args = PySequence_List(args);
+ if (quoted_args == NULL)
return NULL;
- for (i=0; i < PySequence_Size(args); i++) {
+ for (i=0; i < PyList_Size(quoted_args); i++) {
PyObject *value, *qvalue;
- value = PySequence_GetItem(args, i);
+ value = PyList_GET_ITEM(args, i);
if (value == NULL) {
goto error;
}
if (htmltextObject_Check(value)) {
qvalue = htmltext_STR(value);
Py_INCREF(qvalue);
- Py_DECREF(value);
}
- else if (PyString_Check(value)) {
- qvalue = escape_string(value);
- Py_DECREF(value);
- }
else {
- Py_DECREF(value);
- type_error("join requires a list of strings");
- goto error;
+ if (!string_check(value)) {
+ type_error("join requires a list of strings");
+ goto error;
+ }
+ qvalue = escape(value);
+ if (qvalue == NULL)
+ goto error;
}
- if (PyList_SetItem(qargs, i, qvalue) < 0) {
+ if (PyList_SetItem(quoted_args, i, qvalue) < 0) {
goto error;
}
}
- rv = _PyString_Join(htmltext_STR(self), qargs);
- Py_DECREF(qargs);
+ if (PyUnicode_Check(htmltext_STR(self))) {
+ rv = PyUnicode_Join(htmltext_STR(self), quoted_args);
+ }
+ else {
+ rv = _PyString_Join(htmltext_STR(self), quoted_args);
+ }
+ Py_DECREF(quoted_args);
return htmltext_from_string(rv);
error:
- Py_DECREF(qargs);
+ Py_DECREF(quoted_args);
return NULL;
}
@@ -465,8 +594,8 @@
quote_arg(PyObject *s)
{
PyObject *ss;
- if (PyString_Check(s)) {
- ss = escape_string(s);
+ if (string_check(s)) {
+ ss = escape(s);
if (ss == NULL)
return NULL;
}
@@ -561,25 +690,32 @@
if (self == NULL) {
return NULL;
}
+ self->data = PyList_New(0);
+ if (self->data == NULL) {
+ Py_DECREF(self);
+ return NULL;
+ }
self->html = html != 0;
- self->buf = NULL;
- self->size = 0;
- self->pos = 0;
return (PyObject *)self;
}
static void
template_io_dealloc(TemplateIO_Object *self)
{
- if (self->size > 0)
- PyMem_Free(self->buf);
+ Py_DECREF(self->data);
self->ob_type->tp_free((PyObject *)self);
}
static PyObject *
template_io_str(TemplateIO_Object *self)
{
- return PyString_FromStringAndSize(self->buf, self->pos);
+ static PyObject *empty = NULL;
+ if (empty == NULL) {
+ empty = PyString_FromStringAndSize(NULL, 0);
+ if (empty == NULL)
+ return NULL;
+ }
+ return _PyString_Join(empty, self->data);
}
static PyObject *
@@ -594,51 +730,8 @@
}
static PyObject *
-template_io_repr(TemplateIO_Object *self)
-{
- PyObject *s, *sr, *rv;
- s = template_io_str(self);
- if (s == NULL)
- return NULL;
- sr = PyObject_Repr(s);
- Py_DECREF(s);
- if (sr == NULL)
- return NULL;
- rv = PyString_FromFormat("<TemplateIO %s>", PyString_AsString(sr));
- Py_DECREF(sr);
- return rv;
-}
-
-
-static PyObject *
-template_io_do_concat(TemplateIO_Object *self, char *s, size_t size)
-{
- /* note this adds a reference to self */
- if (self->pos + size > self->size) {
- size_t new_size;
- char *new_buf;
- if (self->size > size)
- new_size = self->size * 2;
- else
- new_size = size * 2;
- new_buf = PyMem_Realloc(self->buf, new_size);
- if (new_buf == NULL)
- return NULL;
- self->buf = new_buf;
- self->size = new_size;
- }
- assert (self->pos + size <= self->size);
- memcpy(self->buf + self->pos, s, size);
- self->pos += size;
- Py_INCREF(self);
- return (PyObject *)self;
-}
-
-
-static PyObject *
template_io_iadd(TemplateIO_Object *self, PyObject *other)
{
- PyObject *rv;
PyObject *s = NULL;
if (!TemplateIO_Check(self))
return type_error("TemplateIO object required");
@@ -646,43 +739,29 @@
Py_INCREF(self);
return (PyObject *)self;
}
- else if (TemplateIO_Check(other)) {
- TemplateIO_Object *o = (TemplateIO_Object *)other;
- if (self->html && !o->html) {
- PyObject *ss = PyString_FromStringAndSize(o->buf,
- o->pos);
- if (ss == NULL)
- return NULL;
- s = escape_string(ss);
- Py_DECREF(ss);
- goto concat_str;
- }
- rv = template_io_do_concat(self, o->buf, o->pos);
- }
else if (htmltextObject_Check(other)) {
- PyStringObject *s = ((htmltextObject *)other)->s;
- rv = template_io_do_concat(self,
- PyString_AS_STRING(s),
- PyString_GET_SIZE(s));
+ s = htmltext_STR(other);
+ Py_INCREF(s);
}
else {
if (self->html) {
- PyObject *ss = PyObject_Str(other);
+ PyObject *ss = stringify(other);
if (ss == NULL)
return NULL;
- s = escape_string(ss);
+ s = escape(ss);
Py_DECREF(ss);
- } else {
- s = PyObject_Str(other);
}
-concat_str:
+ else {
+ s = stringify(other);
+ }
if (s == NULL)
return NULL;
- rv = template_io_do_concat(self, PyString_AS_STRING(s),
- PyString_GET_SIZE(s));
- Py_XDECREF(s);
}
- return rv;
+ if (PyList_Append(self->data, s) != 0)
+ return NULL;
+ Py_DECREF(s);
+ Py_INCREF(self);
+ return (PyObject *)self;
}
static PyMethodDef htmltext_methods[] = {
@@ -828,9 +907,9 @@
};
static PyMappingMethods dict_wrapper_as_mapping = {
- 0, /*mp_length*/
- (binaryfunc)dict_wrapper_subscript, /*mp_subscript*/
- 0, /*mp_ass_subscript*/
+ 0, /*mp_length*/
+ (binaryfunc)dict_wrapper_subscript, /*mp_subscript*/
+ 0, /*mp_ass_subscript*/
};
static PyTypeObject DictWrapper_Type = {
@@ -895,7 +974,7 @@
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
- (unaryfunc)template_io_repr,/*tp_repr*/
+ 0, /*tp_repr*/
&template_io_as_number, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@@ -939,10 +1018,10 @@
}
else {
PyObject *rv;
- PyObject *s = PyObject_Str(o);
+ PyObject *s = stringify(o);
if (s == NULL)
return NULL;
- rv = escape_string(s);
+ rv = escape(s);
Py_DECREF(s);
return htmltext_from_string(rv);
}
@@ -951,18 +1030,21 @@
static PyObject *
py_escape_string(PyObject *self, PyObject *o)
{
- PyObject *rv;
- if (!PyString_Check(o))
- return type_error("string required");
- rv = escape_string(o);
- return rv;
+ return escape(o);
}
+static PyObject *
+py_stringify(PyObject *self, PyObject *o)
+{
+ return stringify(o);
+}
+
/* List of functions defined in the module */
static PyMethodDef htmltext_module_methods[] = {
{"htmlescape", (PyCFunction)html_escape, METH_O},
{"_escape_string", (PyCFunction)py_escape_string, METH_O},
+ {"stringify", (PyCFunction)py_stringify, METH_O},
{NULL, NULL}
};
Modified: trunk/quixote/src/setup.py
===================================================================
--- trunk/quixote/src/setup.py 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/src/setup.py 2004-10-14 17:17:51 UTC (rev 25339)
@@ -4,11 +4,13 @@
Import = Extension(name="cimport",
sources=["cimport.c"])
+Html = Extension(name="_c_htmltext",
+ sources=["_c_htmltext.c"])
setup(name = "cimport",
version = "0.1",
description = "Import tools for Python",
author = "Neil Schemenauer",
author_email = "nas-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]",
- ext_modules = [Import]
+ ext_modules = [Import, Html]
)
Modified: trunk/quixote/test/utest_html.py
===================================================================
--- trunk/quixote/test/utest_html.py 2004-10-14 17:10:35 UTC (rev 25338)
+++ trunk/quixote/test/utest_html.py 2004-10-14 17:17:51 UTC (rev 25339)
@@ -1,12 +1,6 @@
-#!/www/python/bin/python
-"""
-$URL$
-$Id$
-"""
from sancho.utest import UTest
from quixote import _py_htmltext
-escape = htmlescape = None # so that checker does not complain
class Wrapper:
def __init__(self, s):
@@ -18,43 +12,51 @@
def __str__(self):
return self.s
+class BrokenError(Exception):
+ pass
+
class Broken:
def __str__(self):
- raise RuntimeError, 'eieee'
+ raise BrokenError, 'eieee'
def __repr__(self):
- raise RuntimeError, 'eieee'
+ raise BrokenError, 'eieee'
markupchars = '<>&"'
quotedchars = '<>&"'
+unicodechars = u'\u1234'
class HTMLTest (UTest):
def _pre(self):
- global htmltext, escape, htmlescape
+ global htmltext, escape, htmlescape, TemplateIO
htmltext = _py_htmltext.htmltext
escape = _py_htmltext._escape_string
htmlescape = _py_htmltext.htmlescape
+ TemplateIO = _py_htmltext.TemplateIO
def _post(self):
pass
-
- def check_init(self):
+ def _check_init(self):
assert str(htmltext('foo')) == 'foo'
assert str(htmltext(markupchars)) == markupchars
+ assert unicode(htmltext(unicodechars)) == unicodechars
+ assert str(htmltext(unicode(markupchars))) == markupchars
assert str(htmltext(None)) == 'None'
assert str(htmltext(1)) == '1'
try:
htmltext(Broken())
assert 0
- except RuntimeError: pass
+ except BrokenError: pass
def check_escape(self):
assert htmlescape(markupchars) == quotedchars
assert isinstance(htmlescape(markupchars), htmltext)
assert escape(markupchars) == quotedchars
- assert isinstance(escape(markupchars), str)
+ assert escape(unicodechars) == unicodechars
+ assert escape(unicode(markupchars)) == quotedchars
+ assert isinstance(escape(markupchars), basestring)
assert htmlescape(htmlescape(markupchars)) == quotedchars
try:
escape(1)
@@ -67,6 +69,7 @@
assert s != 'bar'
assert s == htmltext('foo')
assert s != htmltext('bar')
+ assert htmltext(u'\u1234') == u'\u1234'
assert htmltext('1') != 1
assert 1 != s
@@ -89,6 +92,7 @@
assert isinstance(s + markupchars, htmltext)
assert markupchars + s == quotedchars + "foo"
assert isinstance(markupchars + s, htmltext)
+ assert markupchars + htmltext(u'') == quotedchars
try:
s + 1
assert 0
@@ -118,23 +122,29 @@
def check_format(self):
s_fmt = htmltext('%s')
+ u_fmt = htmltext(u'%s')
assert s_fmt % 'foo' == "foo"
+ assert u_fmt % 'foo' == u"foo"
assert isinstance(s_fmt % 'foo', htmltext)
+ assert isinstance(u_fmt % 'foo', htmltext)
assert s_fmt % markupchars == quotedchars
+ assert u_fmt % markupchars == quotedchars
assert s_fmt % None == "None"
+ assert u_fmt % None == "None"
+ assert u_fmt % unicodechars == unicodechars
assert htmltext('%r') % Wrapper(markupchars) == quotedchars
- assert htmltext('%s%s') % ('foo', htmltext(markupchars)) == (
- "foo" + markupchars)
+ assert htmltext('%s%s') % ('foo', htmltext(markupchars)) \
+ == ("foo" + markupchars)
assert htmltext('%d') % 10 == "10"
assert htmltext('%.1f') % 10 == "10.0"
try:
s_fmt % Broken()
assert 0
- except RuntimeError: pass
+ except BrokenError: pass
try:
htmltext('%r') % Broken()
assert 0
- except RuntimeError: pass
+ except BrokenError: pass
try:
s_fmt % (1, 2)
assert 0
@@ -142,8 +152,9 @@
assert htmltext('%d') % 12300000000000000000L == "12300000000000000000"
def check_dict_format(self):
- assert htmltext('%(a)s %(a)r %(b)s') % (
- {'a': 'foo&', 'b': htmltext('bar&')}) == "foo& 'foo&' bar&"
+ args = {'a': 'foo&', 'b': htmltext('bar&')}
+ result = "foo& 'foo&' bar&"
+ assert htmltext('%(a)s %(a)r %(b)s') % args == result
assert htmltext('%(a)s') % {'a': 'foo&'} == "foo&"
assert isinstance(htmltext('%(a)s') % {'a': 'a'}, htmltext)
assert htmltext('%s') % {'a': 'foo&'} == "{'a': 'foo&'}"
@@ -158,13 +169,15 @@
def check_join(self):
assert htmltext(' ').join(['foo', 'bar']) == "foo bar"
- assert htmltext(' ').join(['foo', markupchars]) == (
- "foo " + quotedchars)
- assert htmlescape(markupchars).join(['foo', 'bar']) == (
- "foo" + quotedchars + "bar")
- assert htmltext(' ').join([htmltext(markupchars), 'bar']) == (
- markupchars + " bar")
+ assert htmltext(' ').join(['foo', markupchars]) == \
+ "foo " + quotedchars
+ assert htmlescape(markupchars).join(['foo', 'bar']) == \
+ "foo" + quotedchars + "bar"
+ assert htmltext(' ').join([htmltext(markupchars), 'bar']) == \
+ markupchars + " bar"
assert isinstance(htmltext('').join([]), htmltext)
+ assert htmltext(u' ').join([unicodechars]) == unicodechars
+ assert htmltext(u' ').join(['']) == u''
try:
htmltext('').join(1)
assert 0
@@ -215,6 +228,53 @@
assert isinstance(htmltext('a').capitalize(), htmltext)
+class TemplateTest (UTest):
+
+ def _pre(self):
+ global TemplateIO
+ TemplateIO = _py_htmltext.TemplateIO
+
+ def _post(self):
+ pass
+
+ def check_init(self):
+ TemplateIO()
+ TemplateIO(html=True)
+ TemplateIO(html=False)
+
+ def check_text_iadd(self):
+ t = TemplateIO()
+ assert t.getvalue() == ''
+ t += "abcd"
+ assert t.getvalue() == 'abcd'
+ t += None
+ assert t.getvalue() == 'abcd'
+ t += 123
+ assert t.getvalue() == 'abcd123'
+ t += u'\u1234'
+ assert t.getvalue() == u'abcd123\u1234'
+ try:
+ t += Broken(); t.getvalue()
+ assert 0
+ except BrokenError: pass
+
+ def check_html_iadd(self):
+ t = TemplateIO(html=1)
+ assert t.getvalue() == ''
+ t += "abcd"
+ assert t.getvalue() == 'abcd'
+ t += None
+ assert t.getvalue() == 'abcd'
+ t += 123
+ assert t.getvalue() == 'abcd123'
+ try:
+ t += Broken(); t.getvalue()
+ assert 0
+ except BrokenError: pass
+ t = TemplateIO(html=1)
+ t += markupchars
+ assert t.getvalue() == quotedchars
+
try:
from quixote import _c_htmltext
except ImportError:
@@ -230,5 +290,15 @@
escape = _c_htmltext._escape_string
htmlescape = _c_htmltext.htmlescape
+ class CTemplateTest(TemplateTest):
+ def _pre(self):
+ global TemplateIO
+ TemplateIO = _c_htmltext.TemplateIO
+
+
if __name__ == "__main__":
HTMLTest()
+ TemplateTest()
+ if _c_htmltext:
+ CHTMLTest()
+ CTemplateTest()