/***********************************************************************
 * $Header: /var/local/cvsroot/4Suite/Ft/Xml/src/xmlstring.c,v 1.2 2005/03/24 14:13:10 uogbuji Exp $
 ***********************************************************************/

static char module_doc[] = "\
Miscellaneous XML-specifc string functions\n\
\n\
Copyright 2005 Fourthought, Inc. (USA).\n\
Detailed license and copyright information: http://4suite.org/COPYRIGHT\n\
Project home, documentation, distributions: http://4suite.org/\n\
";

#include "Python.h"

#define XmlString_BUILDING_MODULE
#include "xmlstring.h"

/* header generated from gencharset.py */
#include "charset.h"

/** Private Routines **************************************************/

#define MATCH_CHARSET(set, c) \
  (set[256 + (set[c >> 8] << 5) + ((c & 255) >> 3)] & (1 << (c & 7)))

#ifdef Py_UNICODE_WIDE
#define IS_NCNAMESTART(c) (c < 65536 && MATCH_CHARSET(charset_NCNameStart, c))
#define IS_NCNAMECHAR(c) (c < 65536 && MATCH_CHARSET(charset_NCNameChar, c))
#define IS_NAMESTART(c) (c < 65536 && MATCH_CHARSET(charset_NameStart, c))
#define IS_NAMECHAR(c) (c < 65536 && MATCH_CHARSET(charset_NameChar, c))
#else
#define IS_NCNAMESTART(c) MATCH_CHARSET(charset_NCNameStart, c)
#define IS_NCNAMECHAR(c) MATCH_CHARSET(charset_NCNameChar, c)
#define IS_NAMESTART(c) MATCH_CHARSET(charset_NameStart, c)
#define IS_NAMECHAR(c) MATCH_CHARSET(charset_NameChar, c)
#endif

#define IS_XMLSPACE(c) (((c) == 0x09) || \
                        ((c) == 0x0A) || \
                        ((c) == 0x0D) || \
                        ((c) == 0x20))

static PyObject *XmlStrip(PyUnicodeObject *str, int left, int right)
{
  Py_UNICODE *p = PyUnicode_AS_UNICODE(str);
  int start = 0;
  int end = PyUnicode_GET_SIZE(str);

  if (left)
    while (start < end && IS_XMLSPACE(p[start]))
      start++;

  if (right)
    while (end > start && IS_XMLSPACE(p[end-1]))
      end--;
  
  if (start == 0 && end == PyUnicode_GET_SIZE(str)) {
    /* couldn't strip anything off, return original string */
    Py_INCREF(str);
    return (PyObject *) str;
  }
  return PySequence_GetSlice((PyObject *)str, start, end);
}

static int IsSpace(PyObject *str)
{
  Py_UNICODE *p, *e;

  if (!PyUnicode_CheckExact(str))
    return -1;

  p = PyUnicode_AS_UNICODE(str);
  e = p + PyUnicode_GET_SIZE(str);
  while (p < e) {
    if (!IS_XMLSPACE(*p)) return 0;
    p++;
  }
  return 1;
}

static int SplitQName(PyObject *qualifiedName, PyObject **prefix,
                      PyObject **localName)
{
  int i;
  Py_UNICODE colon = ':';
  int len = PyUnicode_GET_SIZE(qualifiedName);
  const Py_UNICODE *p = PyUnicode_AS_UNICODE(qualifiedName);
  
  for (i = 0; i < len; i++) {
    if (p[i] == colon) {
      PyObject *u, *v;
      u = PyUnicode_FromUnicode(p, i);
      if (u == NULL) {
        return 0;
      }
      /* skip over the colon */
      i++;
      v = PyUnicode_FromUnicode((p + i), (len - i));
      if (v == NULL) {
        Py_DECREF(u);
        return 0;
      }
      *prefix = u;
      *localName = v;
      return 1;
    }
  }

  /* No prefix */
  *prefix = Py_None;
  Py_INCREF(Py_None);
  *localName = qualifiedName;
  Py_INCREF(qualifiedName);
  return 1;
}

/** Public Methods ****************************************************/

static char lstrip_doc[] =
"XmlStrLStrip(S) -> unicode\n\
\n\
Return a copy of the string S with leading whitespace removed.";

static PyObject *string_lstrip(PyObject *self, PyObject *args)
{
  PyObject *_argc0, *result;  /*Unicode objects*/
  PyUnicodeObject *unistr;

  if (!PyArg_ParseTuple(args,"O:XmlStrLStrip",&_argc0))
    return NULL;

  unistr = (PyUnicodeObject *)PyUnicode_FromObject(_argc0);
  if (!unistr) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
      PyErr_Format(PyExc_TypeError, 
                   "argument must be unicode or string, %.80s found.",
                   _argc0->ob_type->tp_name);
    return NULL;
  }
  result = XmlStrip(unistr , 1, 0);

  /*PyUnicode_FromObject always adds one REF so do a DECREF on it*/
  Py_DECREF(unistr);

  /*Has a REF of one now so we don't need to INCREF it*/
  return result;
}


static char rstrip_doc[] =
"XmlStrRStrip(S) -> unicode\n\
\n\
Return a copy of the string S with trailing whitespace removed.";

static PyObject *string_rstrip(PyObject * self, PyObject *args)
{
  PyObject *_argc0, *result;  /*Unicode objects*/
  PyUnicodeObject *unistr;

  if (!PyArg_ParseTuple(args,"O:XmlStrRStrip",&_argc0))
    return NULL;

  unistr = (PyUnicodeObject *)PyUnicode_FromObject(_argc0);
  if (!unistr) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
      PyErr_Format(PyExc_TypeError, 
                   "argument must be unicode or string, %.80s found.",
                   _argc0->ob_type->tp_name);
    return NULL;
  }
  result = XmlStrip(unistr, 0, 1);

  /*PyUnicode_FromObject always adds one REF so do a DECREF on it*/
  Py_DECREF(unistr);

  /*Has a REF of one now so we don't need to INCREF it*/
  return result;
}


static char strip_doc[] =
"XmlStrStrip(S) -> unicode\n\
\n\
Return a copy of the string S with leading and trailing whitespace removed.";

static PyObject *string_strip(PyObject * self, PyObject *args)
{
  PyObject *_argc0, *result;  /*Unicode objects*/
  PyUnicodeObject *unistr;

  if (!PyArg_ParseTuple(args,"O:XmlStrStrip",&_argc0))
    return NULL;

  unistr = (PyUnicodeObject *)PyUnicode_FromObject(_argc0);
  if (!unistr) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
      PyErr_Format(PyExc_TypeError, 
                   "argument must be unicode or string, %.80s found.",
                   _argc0->ob_type->tp_name);
    return NULL;
  }

  result = XmlStrip(unistr, 1, 1);

  /*PyUnicode_FromObject always adds one REF so do a DECREF on it*/
  Py_DECREF(unistr);

  /*Has a REF of one now so we don't need to INCREF it*/
  return result;
}

static char isspace_doc[] =
"IsXmlSpace(S) -> bool\n\
\n\
Return True if there are only whitespace characters in S, False otherwise.";

static PyObject *string_isspace(PyObject *self, PyObject *args)
{
  PyObject *arg, *str;

  if (!PyArg_ParseTuple(args,"O:IsXmlSpace", &arg))
    return NULL;

  str = PyUnicode_FromObject(arg);
  if (!str) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
      PyErr_Format(PyExc_TypeError, 
                   "argument must be unicode or string, %.80s found.",
                   arg->ob_type->tp_name);
    return NULL;
  }

  arg = IsSpace(str) ? Py_True : Py_False;
  Py_DECREF(str);

  Py_INCREF(arg);
  return arg;
}

static char isname_doc[] = "\
IsName(s) -> bool\n\
\n\
Returns True if the given string matches the Name production as defined\n\
by the W3C's XML 1.0 Recommendation.";

static PyObject *string_isname(PyObject *module, PyObject *args)
{
  PyObject *arg, *str;
  Py_UNICODE *p;

  if (!PyArg_ParseTuple(args,"O:IsName", &arg))
    return NULL;

  str = PyUnicode_FromObject(arg);
  if (str == NULL) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
      PyErr_Format(PyExc_TypeError, 
                   "argument must be unicode or string, %.80s found.",
                   arg->ob_type->tp_name);
    return NULL;
  }

  p = PyUnicode_AS_UNICODE(str);
  if (*p == 0) {
    Py_INCREF(Py_False);
    return Py_False;
  }

  /* the first character must match NameStart */
  if (!IS_NAMESTART(*p)) {
    Py_INCREF(Py_False);
    return Py_False;
  }

  /* the remaining characters must match NameChar */
  for (p++; *p; p++) {
    if (!IS_NAMECHAR(*p)) {
      Py_INCREF(Py_False);
      return Py_False;
    }
  }

  Py_INCREF(Py_True);
  return Py_True;
}


static char isnmtoken_doc[] = "\
IsNmtoken(s) -> bool\n\
\n\
Returns True if the given string matches the Nmtoken production as defined\n\
by the W3C's XML 1.0 Recommendation.";

static PyObject *string_isnmtoken(PyObject *module, PyObject *args)
{
  PyObject *arg, *str;
  Py_UNICODE *p;

  if (!PyArg_ParseTuple(args,"O:IsNmtoken", &arg))
    return NULL;

  str = PyUnicode_FromObject(arg);
  if (str == NULL) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
      PyErr_Format(PyExc_TypeError, 
                   "argument must be unicode or string, %.80s found.",
                   arg->ob_type->tp_name);
    return NULL;
  }

  p = PyUnicode_AS_UNICODE(str);
  if (*p == 0) {
    Py_INCREF(Py_False);
    return Py_False;
  }

  /* all characters must match NameChar */
  for (p++; *p; p++) {
    if (!IS_NAMECHAR(*p)) {
      Py_INCREF(Py_False);
      return Py_False;
    }
  }

  Py_INCREF(Py_True);
  return Py_True;
}


static char isqname_doc[] = "\
IsQName(s) -> bool\n\
\n\
Returns True if the given string matches the QName production as defined\n\
by the W3C's Namespaces in XML Recommendation.";

static PyObject *string_isqname(PyObject *module, PyObject *args)
{
  PyObject *arg, *str;
  Py_UNICODE *p;

  if (!PyArg_ParseTuple(args,"O:IsQName", &arg))
    return NULL;

  str = PyUnicode_FromObject(arg);
  if (str == NULL) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
      PyErr_Format(PyExc_TypeError, 
                   "argument must be unicode or string, %.80s found.",
                   arg->ob_type->tp_name);
    return NULL;
  }

  p = PyUnicode_AS_UNICODE(str);
  if (*p == 0) {
    Py_INCREF(Py_False);
    return Py_False;
  }

  /* the first character must match NCNameStart */
  if (!IS_NCNAMESTART(*p)) {
    Py_INCREF(Py_False);
    return Py_False;
  }

  /* the remaining characters must match NCNameChar (up to ':' or EOS) */
  for (p++; *p; p++) {
    if (!IS_NCNAMECHAR(*p)) {
      if (*p == ':') {
        /* skip over the ':' */
        p++;

        /* the next character must match NCNameStart */
        if (!IS_NCNAMESTART(*p)) {
          Py_INCREF(Py_False);
          return Py_False;
        }

        /* the remaining characters must match NCName */
        for (p++; *p; p++) {
          if (!IS_NCNAMECHAR(*p)) {
            Py_INCREF(Py_False);
            return Py_False;
          }
        }
        break;
      } else {
        Py_INCREF(Py_False);
        return Py_False;
      }
    }
  }

  Py_INCREF(Py_True);
  return Py_True;
}


static char isncname_doc[] = "\
IsNCName(s) -> bool\n\
\n\
Returns True if the given string matches the NCName production as defined\n\
by the W3C's Namespaces in XML Recommendation.";

static PyObject *string_isncname(PyObject *module, PyObject *args)
{
  PyObject *arg, *str;
  Py_UNICODE *p;

  if (!PyArg_ParseTuple(args,"O:IsNCName", &arg))
    return NULL;

  str = PyUnicode_FromObject(arg);
  if (str == NULL) {
    if (PyErr_ExceptionMatches(PyExc_TypeError))
      PyErr_Format(PyExc_TypeError, 
                   "argument must be unicode or string, %.80s found.",
                   arg->ob_type->tp_name);
    return NULL;
  }

  p = PyUnicode_AS_UNICODE(str);
  if (*p == 0) {
    Py_INCREF(Py_False);
    return Py_False;
  }

  /* the first character must match NCNameStart */
  if (!IS_NCNAMESTART(*p)) {
    Py_INCREF(Py_False);
    return Py_False;
  }

  /* the remaining characters must match NCNameChar */
  for (p++; *p; p++) {
    if (!IS_NCNAMECHAR(*p)) {
      Py_INCREF(Py_False);
      return Py_False;
    }
  }

  Py_INCREF(Py_True);
  return Py_True;
}


static char splitqname_doc[] = "\
SplitQName(qualifiedName) -> (prefix, localName)\n\
\n\
where 'qualifiedName' is a QName according to XML Namespaces 1.0\n\
<http://www.w3.org/TR/REC-xml-names>.\n\
returns the name parts according to the spec.";

static PyObject *string_splitqname(PyObject *self, PyObject *args)
{
  PyObject *qualifiedName, *localName, *prefix, *result;

  if (!PyArg_ParseTuple(args, "O:SplitQName", &qualifiedName))
    return NULL;

  qualifiedName = PyUnicode_FromObject(qualifiedName);
  if (!qualifiedName) return NULL;

  if (SplitQName(qualifiedName, &prefix, &localName) == 0) {
    Py_DECREF(qualifiedName);
    return NULL;
  }

  result = PyTuple_New(2);
  if (result == NULL) {
    Py_DECREF(prefix);
    Py_DECREF(localName);
    return NULL;
  }

  /* steals references */
  PyTuple_SET_ITEM(result, 0, prefix);
  PyTuple_SET_ITEM(result, 1, localName);
  return result;
}

/** Module Initialization *********************************************/

static PyMethodDef module_methods[] = {
  { "XmlStrLStrip", string_lstrip,     METH_VARARGS, lstrip_doc },
  { "XmlStrRStrip", string_rstrip,     METH_VARARGS, rstrip_doc },
  { "XmlStrStrip",  string_strip,      METH_VARARGS, strip_doc },
  { "IsXmlSpace",   string_isspace,    METH_VARARGS, isspace_doc },
  { "IsName",       string_isname,     METH_VARARGS, isname_doc },
  { "IsNmtoken",    string_isnmtoken,  METH_VARARGS, isnmtoken_doc },
  { "IsQName",      string_isqname,    METH_VARARGS, isqname_doc },
  { "IsNCName",     string_isncname,   METH_VARARGS, isncname_doc },
  { "SplitQName",   string_splitqname, METH_VARARGS, splitqname_doc },
  { NULL, NULL }
};

static XmlString_APIObject XmlString_API = {
  IsSpace,
  SplitQName,
};

DL_EXPORT(void) initXmlString(void)
{
  PyObject *module;
  PyObject *capi;

  module = Py_InitModule3("XmlString", module_methods, module_doc);
  if (module == NULL) return;

  /* Export C API */
  capi = PyCObject_FromVoidPtr((void *)&XmlString_API, NULL);
  if (capi) PyModule_AddObject(module, "CAPI", capi);

  return;
}
