/*
$Id: quoted.c 31099 2008-09-15 19:36:31Z dbinger $
$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/qpy/quoted.c $
*/

#include "Python.h"
#include "structmember.h"

#if PY_VERSION_HEX < 0x02050000
    typedef int Py_ssize_t;
#endif



#if PY_VERSION_HEX >= 0x03000000
    #define PyObject_Unicode_UTF8 PyObject_Str
    #define PyString_Check PyBytes_Check
    #define PyString_Size PyBytes_Size
#else

PyObject *
PyObject_Unicode_UTF8 (PyObject *x)
{
	PyObject *result;

#if PY_VERSION_HEX < 0x03000000
	const char *encoding;
	encoding = PyUnicode_GetDefaultEncoding();	
	PyUnicode_SetDefaultEncoding("utf-8");
	result = PyObject_Unicode(x);
	PyUnicode_SetDefaultEncoding(encoding);
#else
	result = PyObject_Unicode(x);
#endif

	return result;
}

#endif

typedef PyUnicodeObject xml_object;

typedef struct {
    PyObject_HEAD
    PyObject *value;
} _quote_wrapper_object;

static PyTypeObject xml_type;

static PyTypeObject _quote_wrapper_type;

#define is_xml_object(v) ((v)->ob_type == &xml_type)

/* _get_empty_unicode() is here for use by join_str(). */
static PyObject *
_get_empty_unicode (void) 
{
    static PyObject *empty = NULL;
    if (empty == NULL) {
        empty = PyUnicode_DecodeUTF8("", 0, NULL);
    } else {
        Py_INCREF(empty);
    }
    return empty;
}

/* _get_empty_xml() is here for use by join_xml(). */
PyObject *
_get_empty_xml (void)
{
     static xml_object *xml_empty;
     PyUnicodeObject *x;
     if (xml_empty == NULL) {
         /* Make sure we get a *new* empty PyUnicode instance. */
         x = PyObject_New(PyUnicodeObject, &xml_type);
         if (x == NULL)
             return NULL;
         x->str = PyMem_NEW(Py_UNICODE, 1);
         if (x->str == NULL) {
             Py_DECREF(x);
             return NULL;
         }
         x->str[0] = 0;
         x->length = 0;
         x->hash = -1;
         x->defenc = NULL;
         xml_empty = (xml_object *)x;
     } else {
         Py_INCREF(xml_empty);
     }
     return (PyObject *)xml_empty;
}

/* _quote_str() is here to be used by _str_list() */
static PyObject *
_quote_str(PyObject *x)
{
    PyObject *result;
    if (x == Py_None) {
        result = _get_empty_unicode();
    } else {
        result = PyObject_Unicode_UTF8(x);
    }
    return result;
}

/* returns the list of _quote_str()-ed values in args */
static PyObject *
_str_list(PyObject* args)
{
    PyObject *value = NULL;
    PyObject *qvalue = NULL;
    Py_ssize_t i = 0;
    Py_ssize_t n = 0;
    PyObject *quoted_args = PySequence_List(args);
    if (quoted_args == NULL)
        return NULL;
    n = PyList_Size(quoted_args);
    for (i=0; i < n; i++) {
        value = PyList_GET_ITEM(quoted_args, i);
        if (value == NULL)
            goto error;
        qvalue = _quote_str(value);
        if (qvalue == NULL)
            goto error;
        if (PyList_SetItem(quoted_args, i, qvalue) < 0)
            goto error;
    }
    return quoted_args;

    error:
        Py_DECREF(quoted_args);
        return NULL;
}

static PyObject *
_xml_new_from_unicode(PyObject *x)
{
    PyObject *args = NULL;
    PyObject *result = NULL;
    if (!PyUnicode_Check(x)) 
        return NULL;
    if (PyUnicode_GetSize(x) == 0)
        return (PyObject *)_get_empty_xml();
    else {
        args = PyTuple_New(1);
        if (args == NULL)
            return NULL;
        Py_INCREF(x);
        PyTuple_SET_ITEM(args, 0, x);
        result = PyUnicode_Type.tp_new(&xml_type, args, NULL);
        Py_DECREF(args);
        return result;
    }
}

static PyObject *
_escape_unicode(PyObject *obj)
{
    Py_UNICODE *u;
    PyObject *newobj;
    Py_ssize_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;
        case 39: // apostrophe
            extra_space += 4;
            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;
        case 39: // apostrophe
            u[j++] = '&';
            u[j++] = '#';
            u[j++] = '3';
            u[j++] = '9';
            u[j++] = ';';
            break;            
        default:
            u[j++] = PyUnicode_AS_UNICODE(obj)[i];
            break;
        }
    }
    assert (j == new_size);
    return (PyObject *)newobj;
}

static PyObject *
quote_object(PyObject *obj)
{
    PyObject *unicode_obj;
    PyObject *unicode_result;
    PyObject *result;
    if (is_xml_object(obj)) {
         Py_INCREF(obj);
         return obj;
     }
     if (obj == Py_None)
         return _get_empty_xml();
     unicode_obj = PyObject_Unicode_UTF8(obj);
     if (unicode_obj == NULL)
         return NULL;
     unicode_result = _escape_unicode(unicode_obj);
     Py_DECREF(unicode_obj);
     if (unicode_result == NULL)
         return NULL;
     result = _xml_new_from_unicode(unicode_result);    
     Py_DECREF(unicode_result);
     return result;
}

PyDoc_STRVAR(quote_doc,
     "(args) -> xml\n"
    "Converts as needed to xml-quoted xml instances.");

static PyObject *
quote(PyObject *self, PyObject *obj) {
    return quote_object(obj);
}

static PyObject *
_quote_list(PyObject* args)
{
    PyObject *value = NULL;
    PyObject *qvalue = NULL;
    Py_ssize_t i = 0;
    Py_ssize_t n = 0;
    PyObject *quoted_args = PySequence_List(args);
    if (quoted_args == NULL)
        return NULL;
    n = PyList_Size(quoted_args);
    for (i=0; i < n; i++) {
        value = PyList_GET_ITEM(quoted_args, i);
        if (value == NULL)
            goto error;
        qvalue = quote_object(value);
        if (qvalue == NULL)
            goto error;
        if (PyList_SetItem(quoted_args, i, qvalue) < 0)
            goto error;
    }
    return quoted_args;

    error:
        Py_DECREF(quoted_args);
        return NULL;
}

PyDoc_STRVAR(join_str_doc,
    "(args) -> str\n"
    "Joins the str()s of the non-None items in args");

static PyObject *
join_str(PyObject *self, PyObject *args)
{
    PyObject *quoted_args = NULL;
    PyObject *result = NULL;
    PyObject *empty = NULL;
    quoted_args = _str_list(args);
    if (quoted_args == NULL)
         return NULL;
    empty = _get_empty_unicode();
    if (empty == NULL) {
        Py_DECREF(quoted_args);
        return NULL;    
    }    
    result = PyUnicode_Join(empty, quoted_args);
    Py_DECREF(empty);    
    Py_DECREF(quoted_args);
    return result;
}

static PyObject *
xml_join(PyObject *self, PyObject *args)
{
    PyObject *quoted_args = NULL;
    PyObject *unicode_result = NULL;
    PyObject *result = NULL;    
    PyObject *obj;
    if (!PyArg_ParseTuple(args, "O", &obj)) {
        return NULL;
    }
    quoted_args = _quote_list(obj);
    if (quoted_args == NULL)
         return NULL;
    unicode_result = PyUnicode_Join(self, quoted_args);
    Py_DECREF(quoted_args);
    if (unicode_result == NULL)
         return NULL;    
    result = _xml_new_from_unicode(unicode_result);
    Py_DECREF(unicode_result);    
    return result;
}

PyDoc_STRVAR(join_xml_doc,
     "(args) -> str\n"
    "Joins the quote()s of the items in args");

static PyObject *
join_xml(PyObject *self, PyObject *args)
{
    PyObject *empty;
    PyObject *result;
    empty = _get_empty_xml();
    if (empty == NULL)
        return NULL;
    result = xml_join(empty, args);
    Py_DECREF(empty);
    return result;
}

static PyObject *
xml_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
    PyObject *x = NULL;
    static char *kwlist[] = {"string", "encoding", "errors", 0};
    char *encoding = NULL;
    char *errors = NULL;
    PyObject *result;
    if (!PyArg_ParseTupleAndKeywords(
        args, kw, "|Oss:u8", kwlist, &x, &encoding, &errors))
        return NULL;
    if ((x == NULL) ||
        (x == Py_None) ||
        (PyString_Check(x) && (PyString_Size(x) == 0))) {
        result = _get_empty_xml();            
    } else if (PyUnicode_Check(x)) {
        result = _xml_new_from_unicode(x);
    } else {
        result = PyUnicode_Type.tp_new(&xml_type, args, kw);        
    }
    return result;
}

static PyObject *
_quote_wrap(PyObject *value)
{
    _quote_wrapper_object *self;
#if PY_VERSION_HEX >= 0x03000000
    if (PyLong_Check(value) || PyFloat_Check(value)) {
#else
    if (PyInt_Check(value) ||
        PyFloat_Check(value) ||
        PyLong_Check(value)) {
#endif    
        /* no need for wrapper */
        Py_INCREF(value);
        return value;
    }
    self = PyObject_New(_quote_wrapper_object, &_quote_wrapper_type);
    if (self == NULL)
        return NULL;
    Py_INCREF(value);
    self->value = value;
    return (PyObject *)self;
}

static void
_quote_wrapper_dealloc(_quote_wrapper_object *self)
{
    Py_DECREF(self->value);
    PyObject_Del(self);
}

static PyObject *
_quote_wrapper_repr(_quote_wrapper_object *self)
{
    PyObject *s = PyObject_Repr(self->value);
    PyObject *qs = NULL;
    if (s == NULL)
        return NULL;
    qs = quote_object(s);
    Py_DECREF(s);
    return qs;
}

static PyObject *
_quote_wrapper_str(_quote_wrapper_object *self)
{
    return quote_object(self->value);
}

static PyObject *
_quote_wrapper_subscript(_quote_wrapper_object *self, PyObject *key)
{
    PyObject *w = NULL;
    PyObject *v = PyObject_GetItem(self->value, key);
    if (v == NULL)
        return NULL;
    w = _quote_wrap(v);
    Py_DECREF(v);
    return w;
}

static PyObject *
_format_arg_wrap(PyObject *arg)
{
    PyObject *wrapped;
    if (PyTuple_Check(arg)) {
        Py_ssize_t i;
        Py_ssize_t n = PyTuple_GET_SIZE(arg);
        wrapped = PyTuple_New(n);
        for (i=0; i < n; i++) {
            PyObject *v = PyTuple_GET_ITEM(arg, i);
            v = _quote_wrap(v);
            if (v == NULL) {
                Py_DECREF(wrapped);
                return NULL;
            }
            PyTuple_SetItem(wrapped, i, v);
        }
    }
    else {
        wrapped = _quote_wrap(arg);
        if (wrapped == NULL)
            return NULL;
    }
    return wrapped;
}

static PyObject *
xml_add(PyObject *left, PyObject *right) 
{
    PyObject *quoted_left;
    PyObject *quoted_right;
    PyObject *unicode_result;
    PyObject *result;
    if (!(PyUnicode_Check(left) || PyString_Check(left))) {
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    } else {
        quoted_left = quote_object(left);
        if (quoted_left == NULL)
            return NULL;
        quoted_right = quote_object(right);
        if (quoted_right == NULL) {
            Py_DECREF(quoted_left);
            return NULL;
        }
        unicode_result = PyUnicode_Concat(quoted_left, quoted_right);
        Py_DECREF(quoted_left);
        Py_DECREF(quoted_right);        
        if (unicode_result == NULL) {
            return NULL;
        }
        result = _xml_new_from_unicode(unicode_result);
        Py_DECREF(unicode_result);
        return result;
    } 
}

static PyObject *
xml_repeat(PyObject *self, Py_ssize_t n)
{
    PyObject *unicode_result;
    PyObject *result;
    unicode_result = PyUnicode_Type.tp_as_sequence->sq_repeat(self, n);
    if (unicode_result == NULL)
        return NULL;
    result = _xml_new_from_unicode(unicode_result);
    Py_DECREF(unicode_result);
    return result;
}

static PyObject *
xml_mod(PyObject *v, PyObject *w)
{
    PyObject *wrapped;
    PyObject *unicode_result;
    PyObject *result;
    if (!is_xml_object(v)) {
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }        
    wrapped = _format_arg_wrap(w);        
    if (wrapped == NULL)
        return NULL;
    unicode_result = PyUnicode_Format(v, wrapped);
    Py_DECREF(wrapped);
    if (unicode_result == NULL)
        return NULL;
    result = _xml_new_from_unicode(unicode_result);
    Py_DECREF(unicode_result);
    return result;
}

static PyNumberMethods xml_as_number = {
    xml_add, /* nb_add       */
    0,  /* nb_subtract  */
    0,  /* nb_multiply  */
#if PY_VERSION_HEX < 0x03000000
    0, /* nb_divide */
#endif
    xml_mod, /* nb_remainder */
};

static PySequenceMethods xml_as_sequence = {
    0, /* sq_length */
    0, /* sq_concat */
    xml_repeat, /* sq_repeat */
};

PyDoc_STRVAR(xml_join_doc, "(s:sequence) -> xml");

PyDoc_STRVAR(xml_quote_doc,
     "(s:anything) -> xml\n"
     "Return an xml instance, based on s.  Escape xml characters as needed."); 

static PyMethodDef xml_methods[] = {
     {"join", xml_join, METH_VARARGS, xml_join_doc},
     {"quote", quote, METH_STATIC | METH_O, xml_quote_doc},    
     {NULL,  NULL},
};

PyDoc_STRVAR(xml_doc,
    "This subclass of str is designated as needing no more xml quoting.\n"
    "Some operations that combine this with non-xml instances quote the\n"
    "non-xml instances and produce xml instances.\n\n");

static PyTypeObject xml_type = {
#if PY_VERSION_HEX < 0x03000000
        PyObject_HEAD_INIT(0)
        0,
#else
    PyVarObject_HEAD_INIT(0, 0)
#endif
    "qpy.xml",
    sizeof(xml_object),
    0,
    0,  /* tp_dealloc */
    0,  /* tp_print */
    0,  /* tp_getattr */
    0,  /* tp_setattr */
    0,  /* tp_compare */
    0,  /* tp_repr */
    &xml_as_number,      /* tp_as_number */
    &xml_as_sequence,    /* tp_as_sequence */
    0,  /* tp_as_mapping */
    0,  /* tp_hash */
    0,  /* tp_call */
    0,  /* tp_str */
    0,  /* tp_getattro */
    0,  /* tp_setattro */
    0,  /* tp_as_buffer */
#if PY_VERSION_HEX < 0x03000000    
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,
#else
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_UNICODE_SUBCLASS, /* tp_flags */    
#endif
    xml_doc, /* tp_doc */
    0,  /* tp_traverse */
    0,  /* tp_clear */
    0,  /* tp_richcompare */
    0,  /* tp_weaklistoffset */
    0,  /* tp_iter */
    0,  /* tp_iternext */
    xml_methods, /* tp_methods */
    0,  /* tp_members */
    0,  /* tp_getset */
    0,  /* tp_base */
    0,  /* tp_dict */
    0,  /* tp_descr_get */
    0,  /* tp_descr_set */
    0,  /* tp_dictoffset */
    0,  /* tp_init */
    0,  /* tp_alloc */
    (newfunc)xml_new,    /* tp_new */
};

static PyMappingMethods _quote_wrapper_as_mapping = {
    0, /* mp_length */
    (binaryfunc)_quote_wrapper_subscript, /* mp_subscript */
};

static PyTypeObject _quote_wrapper_type = {
#if PY_VERSION_HEX < 0x03000000
        PyObject_HEAD_INIT(0)
        0,
#else
    PyVarObject_HEAD_INIT(0, 0)
#endif
    "_xml_quote_wrapper", /*tp_name*/
    sizeof(_quote_wrapper_object),  /*tp_basicsize*/
    0,  /*tp_itemsize*/
    (destructor)_quote_wrapper_dealloc, /*tp_dealloc*/
    0,  /*tp_print*/
    0,  /*tp_getattr*/
    0,  /*tp_setattr*/
    0,  /*tp_compare*/
    (unaryfunc)_quote_wrapper_repr, /*tp_repr*/
    0,  /*tp_as_number*/
    0,  /*tp_as_sequence*/
    &_quote_wrapper_as_mapping, /*tp_as_mapping*/
    0,  /*tp_hash*/
    0,  /*tp_call*/
    (unaryfunc)_quote_wrapper_str,  /*tp_str*/
};

static PyMethodDef quoted_functions[] = {
  {"join_str", join_str, METH_O, join_str_doc},
  {"join_xml", join_xml, METH_VARARGS, join_xml_doc},  
  {"xml_quote", quote, METH_O, quote_doc},    
  {NULL, NULL}
};

PyDoc_STRVAR(quoted_doc, 
    "The quoted module defines the xml class.");

#if PY_VERSION_HEX >= 0x03000000
    static struct PyModuleDef quoted_module = {
        PyModuleDef_HEAD_INIT,
        "quoted",
        quoted_doc,
        -1,
        quoted_functions,
        NULL,
        NULL,
        NULL,
        NULL
    };
#endif

PyObject *
init_quoted_module(void)
{
    PyObject *m;
    xml_type.tp_base = &PyUnicode_Type;
    if (PyType_Ready(&xml_type) < 0)
        return NULL;
    Py_INCREF(&xml_type);
    if (PyType_Ready(&_quote_wrapper_type) < 0)
        return NULL;
    Py_INCREF(&_quote_wrapper_type);
#if PY_VERSION_HEX >= 0x03000000
    m = PyModule_Create(&quoted_module);
#else
    m = Py_InitModule3("quoted", quoted_functions, quoted_doc);
#endif
    if (!m)
        return NULL;
    if (PyModule_AddObject(m, 
        "xml", (PyObject *)&xml_type) < 0)
        return NULL;
    _get_empty_unicode();
    _get_empty_xml();   
    return m;
}


#if PY_VERSION_HEX >= 0x03000000
    PyMODINIT_FUNC
    PyInit_quoted(void)
    {
        return init_quoted_module();
    }
#else
    PyMODINIT_FUNC
    initquoted(void) {
        init_quoted_module();
    }
#endif
