#include "Python.h"
#include "structmember.h"
#include "compile.h"
#include "frameobject.h"

#include "expat_module.h"
#include "domlette.h"

static PyObject *uri_resolver;
static PyObject *feature_external_ges;
static PyObject *feature_namespaces;
static PyObject *feature_namespace_prefixes;
static PyObject *feature_process_xincludes;
static PyObject *property_dom_node;
static PyObject *property_whitespace_rules;
static PyObject *sax_input_source;

enum HandlerTypes {
  Handler_SetLocator,
  Handler_StartDocument,
  Handler_EndDocument,
  Handler_StartNamespace,
  Handler_EndNamespace,
  Handler_StartElement,
  Handler_EndElement,
  Handler_Characters,

  TotalHandlers
};

typedef struct {
  PyObject_HEAD
  ExpatParser parser;

  /* SAX features */
  int feature_external_entities;
  int feature_process_xincludes;

  /* SAX properties */
  PyObject *whitespace_rules;
  PyNodeObject *dom_node;

  /* Python callbacks */
  PyObject *handlers[TotalHandlers];
} XMLParserObject;

typedef struct {
  PyObject_HEAD
  PyObject *values;
  PyObject *qnames;
  int length;
} AttributesObject;

/* Cached PyCodeObjects for frames */
static PyCodeObject *tb_codes[TotalHandlers];

/* Empty attributes reuse scheme to save calls to malloc and free */
#define MAX_FREE_ATTRS 80
static AttributesObject *free_attrs[MAX_FREE_ATTRS];
static int num_free_attrs = 0;

/** InputSource Helpers ***********************************************/

typedef struct {
  PyObject_HEAD
  PyObject *uri;
  PyObject *stream;
  PyObject *encoding;
} InputSourceObject;

static PyObject *InputSource_New(PyObject *uri, PyObject *stream, 
                                 PyObject *encoding);

static void InputSource_Del(InputSourceObject *self)
{
  Py_DECREF(self->uri);
  Py_DECREF(self->stream);
  Py_DECREF(self->encoding);
  PyObject_Del(self);
}

static PyObject *InputSource_Resolve(InputSourceObject *self, PyObject *args)
{
  PyObject *systemId, *publicId=Py_None, *hint=Py_None;
  PyObject *stream;

  if (!PyArg_ParseTuple(args, "O|OO", &systemId, &publicId, &hint))
    return NULL;

  systemId = PyObject_CallMethod(uri_resolver, "normalize", "OO",
                                 systemId, self->uri);
  if (systemId == NULL)
    return NULL;

  stream = PyObject_CallMethod(uri_resolver, "resolve", "O", systemId);
  if (stream == NULL) {
    Py_DECREF(systemId);
    return NULL;
  }

  Py_INCREF(Py_None);
  return InputSource_New(systemId, stream, Py_None);
}

static PyMethodDef input_source_methods[] = {
  { "resolve", (PyCFunction) InputSource_Resolve, METH_VARARGS, NULL },
  { NULL }
};

static PyMemberDef input_source_members[] = {
  { "uri",      T_OBJECT, offsetof(InputSourceObject, uri),      RO },
  { "stream",   T_OBJECT, offsetof(InputSourceObject, stream),   RO },
  { "encoding", T_OBJECT, offsetof(InputSourceObject, encoding), RO },
  { NULL }
};

static PyTypeObject InputSource_Type = {
  /* PyObject_HEAD     */ PyObject_HEAD_INIT(NULL)
  /* ob_size           */ 0,
  /* tp_name           */ "InputSource",
  /* tp_basicsize      */ sizeof(InputSourceObject),
  /* tp_itemsize       */ 0,
  /* tp_dealloc        */ (destructor) InputSource_Del,
  /* tp_print          */ (printfunc) 0,
  /* tp_getattr        */ (getattrfunc) 0,
  /* tp_setattr        */ (setattrfunc) 0,
  /* tp_compare        */ (cmpfunc) 0,
  /* tp_repr           */ (reprfunc) 0,
  /* tp_as_number      */ (PyNumberMethods *) 0,
  /* tp_as_sequence    */ (PySequenceMethods *) 0,
  /* tp_as_mapping     */ (PyMappingMethods *) 0,
  /* tp_hash           */ (hashfunc) 0,
  /* tp_call           */ (ternaryfunc) 0,
  /* tp_str            */ (reprfunc) 0,
  /* tp_getattro       */ (getattrofunc) 0,
  /* tp_setattro       */ (setattrofunc) 0,
  /* tp_as_buffer      */ (PyBufferProcs *) 0,
  /* tp_flags          */ Py_TPFLAGS_DEFAULT,
  /* tp_doc            */ (char *) 0,
  /* tp_traverse       */ (traverseproc) 0,
  /* tp_clear          */ (inquiry) 0,
  /* tp_richcompare    */ (richcmpfunc) 0,
  /* tp_weaklistoffset */ 0,
  /* tp_iter           */ (getiterfunc) 0,
  /* tp_iternext       */ (iternextfunc) 0,
  /* tp_methods        */ (PyMethodDef *) input_source_methods,
  /* tp_members        */ (PyMemberDef *) input_source_members,
  /* tp_getset         */ (PyGetSetDef *) 0,
  /* tp_base           */ (PyTypeObject *) 0,
  /* tp_dict           */ (PyObject *) 0,
  /* tp_descr_get      */ (descrgetfunc) 0,
  /* tp_descr_set      */ (descrsetfunc) 0,
  /* tp_dictoffset     */ 0,
  /* tp_init           */ (initproc) 0,
  /* tp_alloc          */ (allocfunc) 0,
  /* tp_new            */ (newfunc) 0,
  /* tp_free           */ 0,
};

static PyObject *InputSource_New(PyObject *uri, PyObject *stream,
                                 PyObject *encoding)
{
  InputSourceObject *self;

  self = PyObject_New(InputSourceObject, &InputSource_Type);
  if (self) {
    self->uri = uri;
    self->stream = stream;
    self->encoding = encoding;
  } else {
    Py_DECREF(uri);
    Py_DECREF(stream);
    Py_DECREF(encoding);
  }
  return (PyObject *) self;
}


/** SAXExceptions *****************************************************/

static PyObject *SAXNotRecognizedException;
static PyObject *SAXNotSupportedException;

static PyObject *SAXException(PyObject *exception, char *msg)
{
  PyObject *obj;

  obj = PyObject_CallFunction(exception, "s", msg);
  if (obj) {
    PyErr_SetObject(exception, obj);
  }
  return NULL;
}

/** Constructors ******************************************************/

static AttributesObject *Attributes_New(void);

/** Handlers **********************************************************/

#define getcode(slot) _getcode(Handler_##slot, #slot, __LINE__)

static PyCodeObject *_getcode(enum HandlerTypes slot, char *slot_name,
                             int lineno)
{
  PyObject *code, *name, *nulltuple, *filename;

  if (tb_codes[slot] == NULL) {
    code = PyString_FromString("");
    if (code == NULL) {
      return NULL;
    }

    name = PyString_FromString(slot_name);
    if (name == NULL) {
      Py_DECREF(code);
      return NULL;
    }

    nulltuple = PyTuple_New(0);
    if (nulltuple == NULL) {
      Py_DECREF(code);
      Py_DECREF(name);
      return NULL;
    }

    filename = PyString_FromString(__FILE__);
    if (filename == NULL) {
      Py_DECREF(code);
      Py_DECREF(name);
      Py_DECREF(nulltuple);
      return NULL;
    }

    tb_codes[slot] = PyCode_New(0,		/* argcount */
                                0,		/* nlocals */
                                0,		/* stacksize */
                                0,		/* flags */
                                code,		/* code */
                                nulltuple,	/* consts */
                                nulltuple,	/* names */
                                nulltuple,	/* varnames */
#if PYTHON_API_VERSION >= 1010
                                nulltuple,	/* freevars */
                                nulltuple,	/* cellvars */
#endif
                                filename,	/* filename */
                                name,		/* name */
                                lineno,		/* firstlineno */
                                code		/* lnotab */
                                );
    Py_DECREF(code);
    Py_DECREF(name);
    Py_DECREF(nulltuple);
    Py_DECREF(filename);
  }
  return tb_codes[slot];
}

static int
trace_frame(PyThreadState *tstate, PyFrameObject *f, int code, PyObject *val)
{
    int result = 0;
    if (!tstate->use_tracing || tstate->tracing)
	return 0;
    if (tstate->c_profilefunc != NULL) {
	tstate->tracing++;
	result = tstate->c_profilefunc(tstate->c_profileobj,
				       f, code , val);
	tstate->use_tracing = ((tstate->c_tracefunc != NULL)
			       || (tstate->c_profilefunc != NULL));
	tstate->tracing--;
	if (result)
	    return result;
    }
    if (tstate->c_tracefunc != NULL) {
	tstate->tracing++;
	result = tstate->c_tracefunc(tstate->c_traceobj,
				     f, code , val);
	tstate->use_tracing = ((tstate->c_tracefunc != NULL)
			       || (tstate->c_profilefunc != NULL));
	tstate->tracing--;
    }	
    return result;
}

static int
trace_frame_exc(PyThreadState *tstate, PyFrameObject *f)
{
    PyObject *type, *value, *traceback, *arg;
    int err;

    if (tstate->c_tracefunc == NULL)
	return 0;

    PyErr_Fetch(&type, &value, &traceback);
    if (value == NULL) {
	value = Py_None;
	Py_INCREF(value);
    }
#if PY_VERSION_HEX < 0x02040000
    arg = Py_BuildValue("(OOO)", type, value, traceback);
#else
    arg = PyTuple_Pack(3, type, value, traceback);
#endif
    if (arg == NULL) {
	PyErr_Restore(type, value, traceback);
	return 0;
    }
    err = trace_frame(tstate, f, PyTrace_EXCEPTION, arg);
    Py_DECREF(arg);
    if (err == 0)
	PyErr_Restore(type, value, traceback);
    else {
	Py_XDECREF(type);
	Py_XDECREF(value);
	Py_XDECREF(traceback);
    }
    return err;
}

static PyObject *call_with_frame(PyCodeObject *code, PyObject *func,
                                 PyObject *args)
{
  PyThreadState *tstate = PyThreadState_GET();
  PyFrameObject *f;
  PyObject *res;

  if (code == NULL || args == NULL) {
    return NULL;
  }
    
  f = PyFrame_New(tstate, code, PyEval_GetGlobals(), NULL);
  if (f == NULL) {
    return NULL;
  }
  tstate->frame = f;
  if (trace_frame(tstate, f, PyTrace_CALL, Py_None) < 0) {
    return NULL;
  }
  res = PyObject_Call(func, args, NULL);
  if (res == NULL) {
    if (tstate->curexc_traceback == NULL) {
      PyTraceBack_Here(f);
    }
    if (trace_frame_exc(tstate, f) < 0) {
      return NULL;
    }
  }
  else {
    if (trace_frame(tstate, f, PyTrace_RETURN, res) < 0) {
      Py_XDECREF(res);
      res = NULL;
    }
  }
  tstate->frame = f->f_back;
  Py_DECREF(f);
  return res;
}

static void parser_StartDocument(void *userData)
{
  XMLParserObject *self = (XMLParserObject *) userData;
  PyObject *handler, *args, *result;

  if ((handler = self->handlers[Handler_SetLocator]) != NULL) {
    /* handler.setDocumentLocator(locator) */
    if ((args = PyTuple_New(1)) == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    PyTuple_SET_ITEM(args, 0, (PyObject *)self);
    Py_INCREF(self); /* SET_ITEM steals reference */

    result = call_with_frame(getcode(SetLocator), handler, args);
    Py_DECREF(args);
    if (result == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    Py_DECREF(result);
  }

  if ((handler = self->handlers[Handler_StartDocument]) != NULL) {
    /* handler.startDocument() */
    if ((args = PyTuple_New(0)) == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }

    result = call_with_frame(getcode(StartDocument), handler, args);
    Py_DECREF(args);
    if (result == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    Py_DECREF(result);
  }
}

static void parser_EndDocument(void *userData)
{
  XMLParserObject *self = (XMLParserObject *) userData;
  PyObject *handler = self->handlers[Handler_EndDocument];
  PyObject *args, *result;

  if (handler != NULL) {
    /* handler.endDocument() */
    if ((args = PyTuple_New(0)) == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }

    result = call_with_frame(getcode(EndDocument), handler, args);
    Py_DECREF(args);
    if (result == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    Py_DECREF(result);
  }
}

static PyObject *createAttributes(XMLParserObject *parser, PyObject **atts)
{
  AttributesObject *self;
  PyObject **ppattr;

  self = Attributes_New();
  if (self == NULL)
    return NULL;

  for (ppattr = atts; *ppattr; ppattr += 2) {
    PyObject *expandedName, *namespaceURI, *localName, *qualifiedName;

    self->length++;

    if (Expat_SplitName(parser->parser, ppattr[0], &namespaceURI, &localName,
                        &qualifiedName, NULL) == 0) {
      Py_DECREF(self);
      return NULL;
    }

    /* create the expanded-name 'key' of (namespaceURI, localName) */
    if ((expandedName = PyTuple_New(2)) == NULL) {
      Py_DECREF(namespaceURI);
      Py_DECREF(localName);
      Py_DECREF(qualifiedName);
      Py_DECREF(self);
      return NULL;
    } 
    PyTuple_SET_ITEM(expandedName, 0, namespaceURI);
    PyTuple_SET_ITEM(expandedName, 1, localName);

    if (PyDict_SetItem(self->values, expandedName, ppattr[1])) {
      Py_DECREF(expandedName);
      Py_DECREF(qualifiedName);
      Py_DECREF(self);
      return NULL;
    }

    if (PyDict_SetItem(self->qnames, expandedName, qualifiedName)) {
      Py_DECREF(expandedName);
      Py_DECREF(qualifiedName);
      Py_DECREF(self);
      return NULL;
    }

    Py_DECREF(expandedName);
    Py_DECREF(qualifiedName);
  }

  return (PyObject *) self;
}

static void parser_StartElement(void *userData, PyObject *name,
                                PyObject **atts)
{
  XMLParserObject *self = (XMLParserObject *) userData;
  PyObject *handler = self->handlers[Handler_StartElement];
  PyObject *args, *result;
  PyObject *expandedName, *namespaceURI, *localName, *tagName, *attributes;

  if (handler != NULL) {
    if (Expat_SplitName(self->parser, name, &namespaceURI, &localName,
                        &tagName, NULL) == 0) {
      Expat_ParserStop(self->parser);
      return;
    }

    if ((expandedName = PyTuple_New(2)) == NULL) {
      Py_DECREF(namespaceURI);
      Py_DECREF(localName);
      Py_DECREF(tagName);
      Expat_ParserStop(self->parser);
      return;
    }
    PyTuple_SET_ITEM(expandedName, 0, namespaceURI);
    PyTuple_SET_ITEM(expandedName, 1, localName);

    attributes = createAttributes(self, atts);
    if (attributes == NULL) {
      Py_DECREF(expandedName);
      Py_DECREF(tagName);
      Expat_ParserStop(self->parser);
      return;
    }

    /* handler.startElement((namespaceURI, localName), tagName, attributes) */
    if ((args = PyTuple_New(3)) == NULL) {
      Py_DECREF(expandedName);
      Py_DECREF(tagName);
      Py_DECREF(attributes);
      Expat_ParserStop(self->parser);
      return;
    }
    PyTuple_SET_ITEM(args, 0, expandedName);
    PyTuple_SET_ITEM(args, 1, tagName);
    PyTuple_SET_ITEM(args, 2, attributes);

    result = call_with_frame(getcode(StartElement), handler, args);
    Py_DECREF(args);
    if (result == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    Py_DECREF(result);
  }
}

static void parser_EndElement(void *userData, PyObject *name)
{
  XMLParserObject *self = (XMLParserObject *) userData;
  PyObject *handler = self->handlers[Handler_EndElement];
  PyObject *args, *result;
  PyObject *expandedName, *namespaceURI, *localName, *tagName;

  if (handler != NULL) {
    if (Expat_SplitName(self->parser, name, &namespaceURI, &localName,
                        &tagName, NULL) == 0) {
      Expat_ParserStop(self->parser);
      return;
    }

    if ((expandedName = PyTuple_New(2)) == NULL) {
      Py_DECREF(namespaceURI);
      Py_DECREF(localName);
      Py_DECREF(tagName);
      Expat_ParserStop(self->parser);
      return;
    }
    PyTuple_SET_ITEM(expandedName, 0, namespaceURI);
    PyTuple_SET_ITEM(expandedName, 1, localName);

    /* handler.endElement((namespaceURI, localName), tagName) */
    if ((args = PyTuple_New(2)) == NULL) {
      Py_DECREF(expandedName);
      Py_DECREF(tagName);
      Expat_ParserStop(self->parser);
      return;
    }
    PyTuple_SET_ITEM(args, 0, expandedName);
    PyTuple_SET_ITEM(args, 1, tagName);

    result = call_with_frame(getcode(EndElement), handler, args);
    Py_DECREF(args);
    if (result == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    Py_DECREF(result);
  }
}

static void parser_CharacterData(void *userData, PyObject *data)
{
  XMLParserObject *self = (XMLParserObject *) userData;
  PyObject *handler = self->handlers[Handler_Characters];
  PyObject *args, *result;

  if (handler != NULL) {
    /* handler.characters(content) */
    if ((args = PyTuple_New(1)) == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    PyTuple_SET_ITEM(args, 0, data);
    Py_INCREF(data); /* SET_ITEM steals reference */

    result = call_with_frame(getcode(Characters), handler, args);
    Py_DECREF(args);
    if (result == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    Py_DECREF(result);
  }
}

static void parser_StartNamespaceDecl(void *userData, PyObject *prefix,
                                      PyObject *uri)
{
  XMLParserObject *self = (XMLParserObject *) userData;
  PyObject *handler = self->handlers[Handler_StartNamespace];
  PyObject *args, *result;

  if (handler != NULL) {
    /* handler.startNamespace(prefix, uri) */
    if ((args = PyTuple_New(2)) == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    PyTuple_SET_ITEM(args, 0, prefix);
    Py_INCREF(prefix); /* SET_ITEM steals reference */
    PyTuple_SET_ITEM(args, 1, uri);
    Py_INCREF(uri); /* SET_ITEM steals reference */

    result = call_with_frame(getcode(StartNamespace), handler, args);
    Py_DECREF(args);
    if (result == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    Py_DECREF(result);
  }
  return;
}

static void parser_EndNamespaceDecl(void *userData, PyObject *prefix)
{
  XMLParserObject *self = (XMLParserObject *) userData;
  PyObject *handler = self->handlers[Handler_EndNamespace];
  PyObject *args, *result;

  if (handler != NULL) {
    /* handler.endNamespace(prefix) */
    if ((args = PyTuple_New(1)) == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    PyTuple_SET_ITEM(args, 0, prefix);
    Py_INCREF(prefix); /* SET_ITEM steals reference */

    result = call_with_frame(getcode(EndNamespace), handler, args);
    Py_DECREF(args);
    if (result == NULL) {
      Expat_ParserStop(self->parser);
      return;
    }
    Py_DECREF(result);
  }
}

/** DOMWalker *********************************************************/

static int domwalker_visit(XMLParserObject *parser, PyNodeObject *node,
                           PyObject *current_namespaces)
{
  if (PyElement_Check(node)) {
    AttributesObject *attrs;
    PyObject *new_namespaces, *prefixes, *namespaceURI, *prefix;
    PyObject *key, *value;
    PyObject *handler, *args, *result;
    int i;

    attrs = Attributes_New();
    if (attrs == NULL) {
      return 0;
    }

    new_namespaces = PyDict_New();
    if (new_namespaces == NULL) {
      Py_DECREF(attrs);
      return 0;
    }

    /* Create expat-style attribute names and trim out namespaces */
    i = 0;
    while (PyDict_Next(PyElement_ATTRIBUTES(node), &i, &key, &value)) {
      PyObject *name, *nodeValue, *expandedName;
      namespaceURI = PyAttr_NAMESPACE_URI(value);
      prefix = PyAttr_PREFIX(value);
      name = PyAttr_LOCAL_NAME(value);
      nodeValue = PyAttr_NODE_VALUE(value);
      if (PyObject_RichCompareBool(g_xmlnsNamespace, namespaceURI, Py_EQ)) {
        /* namespace attribute */
        if (prefix != Py_None) {
          /* xmlns:prefix */
          prefix = name;
        }
        if (PyDict_SetItem(new_namespaces, prefix, nodeValue)) {
          Py_DECREF(new_namespaces);
          Py_DECREF(attrs);
          return 0;
        }
      } else {
        /* DOM doesn't need separate namespace declarations */
        if (namespaceURI != Py_None) {
          if (PyDict_SetItem(new_namespaces, prefix, namespaceURI)) {
            Py_DECREF(new_namespaces);
            Py_DECREF(attrs);
            return 0;
          }
        }
        expandedName = PyTuple_New(2);
        if (expandedName == NULL) {
          Py_DECREF(new_namespaces);
          Py_DECREF(attrs);
          return 0;
        }
        Py_INCREF(namespaceURI);
        PyTuple_SET_ITEM(expandedName, 0, namespaceURI);
        Py_INCREF(name);
        PyTuple_SET_ITEM(expandedName, 1, name);

        if (PyDict_SetItem(attrs->values, expandedName, nodeValue)) {
          Py_DECREF(expandedName);
          Py_DECREF(new_namespaces);
          Py_DECREF(attrs);
          return 0;
        }
        name = PyAttr_NODE_NAME(value);
        if (PyDict_SetItem(attrs->qnames, expandedName, name)) {
          Py_DECREF(expandedName);
          Py_DECREF(new_namespaces);
          Py_DECREF(attrs);
          return 0;
        }
        Py_DECREF(expandedName);
        attrs->length++;
      }
    }
    
    /* DOM doesn't need separate namespace declarations */
    namespaceURI = PyElement_NAMESPACE_URI(node);
    prefix = PyElement_PREFIX(node);
    if (namespaceURI != Py_None) {
      if (PyDict_SetItem(new_namespaces, prefix, namespaceURI)) {
        Py_DECREF(new_namespaces);
        Py_DECREF(attrs);
        return 0;
      }
    }

    /* notify start of namespace declaration(s) */
    current_namespaces = PyDict_Copy(current_namespaces);
    if (current_namespaces == NULL) {
      Py_DECREF(new_namespaces);
      Py_DECREF(attrs);
      return 0;
    }
    prefixes = PyList_New(0);
    if (prefixes == NULL) {
      Py_DECREF(current_namespaces);
      Py_DECREF(new_namespaces);
      Py_DECREF(attrs);
      return 0;
    }
    
    i = 0;
    while (PyDict_Next(new_namespaces, &i, &key, &value)) {
      namespaceURI = PyDict_GetItem(current_namespaces, key);
      if (namespaceURI == NULL ||
          PyObject_RichCompareBool(namespaceURI, value, Py_NE)) {
        if (PyDict_SetItem(current_namespaces, key, value) ||
            PyList_Append(prefixes, key)) {
          Py_DECREF(prefixes);
          Py_DECREF(current_namespaces);
          Py_DECREF(new_namespaces);
          Py_DECREF(attrs);
          return 0;
        }

        parser_StartNamespaceDecl(parser, key, value);
        if (PyErr_Occurred()) {
          Py_DECREF(prefixes);
          Py_DECREF(current_namespaces);
          Py_DECREF(new_namespaces);
          Py_DECREF(attrs);
          return 0;
        }
      }
    }
    Py_DECREF(new_namespaces);

    /* report element start */
    handler = parser->handlers[Handler_StartElement];
    if (handler != NULL) {
      args = Py_BuildValue("(OO)OO", 
                           PyElement_NAMESPACE_URI(node),
                           PyElement_LOCAL_NAME(node),
                           PyElement_NODE_NAME(node),
                           attrs);
      if (args == NULL) {
        Py_DECREF(current_namespaces);
        Py_DECREF(prefixes);
        Py_DECREF(attrs);
        return 0;
      }
      result = call_with_frame(getcode(StartElement), handler, args);
      Py_DECREF(args);
      if (result == NULL) {
        Py_DECREF(current_namespaces);
        Py_DECREF(prefixes);
        Py_DECREF(attrs);
        return 0;
      }
      Py_DECREF(result);
    }
    Py_DECREF(attrs);

    /* process the children */
    for (i = 0; i < ContainerNode_GET_COUNT(node); i++) {
      PyNodeObject *child = ContainerNode_GET_CHILD(node, i);
      if (domwalker_visit(parser, child, current_namespaces) == 0) {
        Py_DECREF(current_namespaces);
        Py_DECREF(prefixes);
        return 0;
      }
    }

    /* report element end */
    handler = parser->handlers[Handler_EndElement];
    if (handler != NULL) {
      args = Py_BuildValue("(OO)O",
                           PyElement_NAMESPACE_URI(node),
                           PyElement_LOCAL_NAME(node),
                           PyElement_NODE_NAME(node));
      if (args == NULL) {
        Py_DECREF(current_namespaces);
        Py_DECREF(prefixes);
        return 0;
      }
      result = call_with_frame(getcode(StartElement), handler, args);
      Py_DECREF(args);
      if (result == NULL) {
        Py_DECREF(current_namespaces);
        Py_DECREF(prefixes);
        return 0;
      }
      Py_DECREF(result);
    }
    Py_DECREF(current_namespaces);

    /* report end of namespace declaration(s) */
    for (i = 0; i < PyList_GET_SIZE(prefixes); i++) {
      parser_EndNamespaceDecl(parser, PyList_GET_ITEM(prefixes, i));
      if (PyErr_Occurred()) {
        Py_DECREF(prefixes);
        return 0;
      }
    }
    Py_DECREF(prefixes);
  }
  else if (PyText_Check(node)) {
    parser_CharacterData(parser, Text_GET_DATA(node));
    if (PyErr_Occurred()) return 0;
  }

  return 1;
}

static int ParseDOM(XMLParserObject *parser)
{
  PyObject *namespaces;
  int i;

  parser_StartDocument(parser);
  if (PyErr_Occurred()) return 0;

  namespaces = PyDict_New();
  if (namespaces == NULL) return 0;
    
  for (i = 0; i < ContainerNode_GET_COUNT(parser->dom_node); i++) {
    PyNodeObject *node = ContainerNode_GET_CHILD(parser->dom_node, i);
    if (domwalker_visit(parser, node, namespaces) == 0) {
      Py_DECREF(namespaces);
      return 0;
    }
  }
  Py_DECREF(namespaces);

  parser_EndDocument(parser);
  if (PyErr_Occurred()) return 0;
  
  return 1;
}

/**********************************************************************/
/** Python Objects ****************************************************/
/**********************************************************************/

#define METHOD_DOC(PREFIX, NAME) \
static char PREFIX##_##NAME##_doc[]

#define METHOD_DEF(PREFIX, NAME, OBJECT) \
static PyObject * PREFIX##_##NAME(OBJECT *self, PyObject *args)

#define Py_METHOD(PREFIX, NAME) \
  { #NAME, (PyCFunction) PREFIX##_##NAME, METH_VARARGS, PREFIX##_##NAME##_doc }
  
#define Py_MEMBER(NAME, TYPE, OBJECT) \
  { #NAME, TYPE, offsetof(OBJECT, NAME), 0 }

/********** XMLPARSER **********/

#define XMLPARSER_METHOD_DOC(NAME) METHOD_DOC(parser, NAME)
#define XMLPARSER_METHOD_DEF(NAME) METHOD_DEF(parser, NAME, XMLParserObject)
#define Py_XMLPARSER_METHOD(NAME) Py_METHOD(parser, NAME)
#define Py_XMLPARSER_MEMBER(NAME, TYPE) Py_MEMBER(NAME, TYPE, XMLParserObject)

XMLPARSER_METHOD_DOC(setContentHandler) = \
"setContentHandler(handler)\n\
\n\
Registers a handler to receive document content events.";

XMLPARSER_METHOD_DEF(setContentHandler)
{
  PyObject *handler;

  if (!PyArg_ParseTuple(args, "O:setContentHandler", &handler))
    return NULL;

#define GET_CALLBACK(TYPE, NAME)                                        \
  Py_XDECREF(self->handlers[Handler_##TYPE]);                           \
  self->handlers[Handler_##TYPE] = PyObject_GetAttrString(handler, NAME);

  GET_CALLBACK(SetLocator, "setDocumentLocator");
  GET_CALLBACK(StartDocument, "startDocument");
  GET_CALLBACK(EndDocument, "endDocument");
  GET_CALLBACK(StartNamespace, "startPrefixMapping");
  GET_CALLBACK(EndNamespace, "endPrefixMapping");
  GET_CALLBACK(StartElement, "startElementNS");
  GET_CALLBACK(EndElement, "endElementNS");
  GET_CALLBACK(Characters, "characters");

  /* ignore any undefined event handler errors */
  PyErr_Clear();

  Py_INCREF(Py_None);
  return Py_None;
}


XMLPARSER_METHOD_DOC(parse) = \
"parse(source)\n\
\n\
Parse an XML document from an Ft.Xml.InputSource.";

XMLPARSER_METHOD_DEF(parse)
{
  PyObject *source, *systemId, *byteStream, *encoding;
  int rc;

  if (!PyArg_ParseTuple(args, "O:parse", &source))
    return NULL;

  if (Expat_SetWhitespaceRules(self->parser, self->whitespace_rules) == 0)
    return NULL;

  if (self->dom_node) {
    /* walk over a DOM, ignoring the source argument */
    rc = ParseDOM(self);
  } else {
    rc = PyObject_IsInstance(source, sax_input_source);
    if (rc == -1)
      return NULL;
    else if (rc) {
      systemId = PyObject_CallMethod(source, "getSystemId", NULL);
      byteStream = PyObject_CallMethod(source, "getByteStream", NULL);
      encoding = PyObject_CallMethod(source, "getEncoding", NULL);
      if (byteStream == NULL || systemId == NULL || encoding == NULL) {
        Py_XDECREF(byteStream);
        Py_XDECREF(systemId);
        Py_XDECREF(encoding);
        return NULL;
      }
      source = InputSource_New(systemId, byteStream, encoding);
      if (source == NULL)
        return NULL;
    }
    /* check for URL */
    else if (PyString_Check(source) || PyUnicode_Check(source)) {
      byteStream = PyObject_CallMethod(uri_resolver, "resolve", "O", source);
      if (byteStream == NULL) {
        return NULL;
      }
      Py_INCREF(source);
      Py_INCREF(Py_None);
      source = InputSource_New(source, byteStream, Py_None);
      if (source == NULL)
        return NULL;
    }
    /* assume a 4Suite InputSource */
    else {
      Py_INCREF(source);
    }

    /* parse the document indicated by the InputSource */
    rc = Expat_ParseDocument(self->parser, source,
                             self->feature_external_entities);
    Py_DECREF(source);
  }

  if (rc == 0) {
    return NULL;
  }

  Py_INCREF(Py_None);
  return Py_None;
}

XMLPARSER_METHOD_DOC(getFeature) = \
"getFeature(featurename)\n\
\n\
Return the current setting for feature featurename. If the feature\n\
is not recognized, SAXNotRecognizedException is raised. The well-known\n\
featurenames are listed in the module xml.sax.handler.";

XMLPARSER_METHOD_DEF(getFeature)
{
  PyObject *featurename;
  PyObject *state;

  if (!PyArg_ParseTuple(args, "O:getFeature", &featurename))
    return NULL;

  if (PyObject_RichCompareBool(featurename, feature_external_ges, Py_EQ)) {
    state = self->feature_external_entities ? Py_True : Py_False;
  }
  else if (PyObject_RichCompareBool(featurename, feature_namespaces, Py_EQ)) {
    state = Py_True;
  }
  else if (PyObject_RichCompareBool(featurename, feature_namespace_prefixes,
                                    Py_EQ)) {
    state = Py_False;
  }
  else if (PyObject_RichCompareBool(featurename, feature_process_xincludes,
                                    Py_EQ)) {
    state = Expat_GetXIncludeProcessing(self->parser) ? Py_True : Py_False;
  }
  else {
    PyObject *repr = PyObject_Repr(featurename);
    if (repr) {
      SAXException(SAXNotRecognizedException, PyString_AsString(repr));
      Py_DECREF(repr);
    }
    return NULL;
  }

  Py_INCREF(state);
  return state;
}

XMLPARSER_METHOD_DOC(setFeature) = \
"setFeature(featurename, value)\n\
\n\
Set the featurename to value. If the feature is not recognized,\n\
SAXNotRecognizedException is raised. If the feature or its setting\n\
is not supported by the parser, SAXNotSupportedException is raised.";

XMLPARSER_METHOD_DEF(setFeature)
{
  PyObject *featurename, *value;
  int state;

  if (!PyArg_ParseTuple(args, "OO:setFeature", &featurename, &value))
    return NULL;

  if ((state = PyObject_IsTrue(value)) == -1) return NULL;

  if (Expat_GetParsingStatus(self->parser)) {
    return SAXException(SAXNotSupportedException,
                        "cannot set features while parsing");
  }
  else if (PyObject_RichCompareBool(featurename, feature_external_ges,
                                    Py_EQ)) {
    self->feature_external_entities = state;
  }
  else if (PyObject_RichCompareBool(featurename, feature_namespaces, Py_EQ)) {
    if (state == 0)
      return SAXException(SAXNotSupportedException,
                          "namespace processing always enabled");
  }
  else if (PyObject_RichCompareBool(featurename, feature_namespace_prefixes,
                                    Py_EQ)) {
    if (state == 1)
      return SAXException(SAXNotSupportedException,
                          "namespace prefixes never reported");
  }
  else if (PyObject_RichCompareBool(featurename, feature_process_xincludes,
                                    Py_EQ)) {
    Expat_SetXIncludeProcessing(self->parser, state);
  }
  else {
    PyObject *repr = PyObject_Repr(featurename);
    if (repr) {
      SAXException(SAXNotRecognizedException, PyString_AsString(repr));
      Py_DECREF(repr);
    }
    return NULL;
  }

  Py_INCREF(Py_None);
  return Py_None;
}

XMLPARSER_METHOD_DOC(getProperty) = \
"getProperty(propertyname)\n\
\n\
Return the current setting for property propertyname. If the property is\n\
not recognized, a SAXNotRecognizedException is raised. The well-known\n\
propertynames are listed in the module xml.sax.handler.";

XMLPARSER_METHOD_DEF(getProperty)
{
  PyObject *propertyname, *value;

  if (!PyArg_ParseTuple(args, "O:getProperty", &propertyname))
    return NULL;

  if (PyObject_RichCompareBool(propertyname, property_whitespace_rules,
                                    Py_EQ)) {
    /* XSLT-style whitespace stripping rules */
    if (self->whitespace_rules == NULL) {
      value = PyList_New(0);
    } else {
      Py_INCREF(self->whitespace_rules);
      value = self->whitespace_rules;
    }
  }
  else if (PyObject_RichCompareBool(propertyname, property_dom_node,
                                    Py_EQ)) {
    if (self->dom_node == NULL)
      value = Py_None;
    else
      value = (PyObject*) self->dom_node;
    Py_INCREF(value);
  }
  else {
    PyObject *repr = PyObject_Repr(propertyname);
    if (repr) {
      SAXException(SAXNotRecognizedException, PyString_AsString(repr));
      Py_DECREF(repr);
    }
    value = NULL;
  }

  return value;
}

XMLPARSER_METHOD_DOC(setProperty) = \
"setProperty(propertyname, value)\n\
\n\
Set the propertyname to value. If the property is not recognized,\n\
SAXNotRecognizedException is raised. If the property or its setting\n\
is not supported by the parser, SAXNotSupportedException is raised.";

XMLPARSER_METHOD_DEF(setProperty)
{
  PyObject *propertyname, *value;

  if (!PyArg_ParseTuple(args, "OO:setProperty", &propertyname, &value))
    return NULL;

  if (Expat_GetParsingStatus(self->parser)) {
    return SAXException(SAXNotSupportedException,
                        "cannot set properties while parsing");
  }
  else if (PyObject_RichCompareBool(propertyname, property_whitespace_rules,
                                    Py_EQ)) {
    /* XSLT-style whitespace stripping rules */
    if (value == Py_None) {
      Py_XDECREF(self->whitespace_rules);
      self->whitespace_rules = NULL;
    }
    else if (PyList_Check(value)) {
      Py_XDECREF(self->whitespace_rules);
      if (PyList_GET_SIZE(value) == 0) {
        self->whitespace_rules = NULL;
      } else {
        Py_INCREF(value);
        self->whitespace_rules = value;
      }        
    }
    else {
      return SAXException(SAXNotSupportedException,
                          "whitespace-rules must be a list");
    }
  }
  else if (PyObject_RichCompareBool(propertyname, property_dom_node,
                                    Py_EQ)) {
    /* create a "DOM Walker"-style parser */
    if (PyDocument_Check(value)) {
      Py_XDECREF(self->dom_node);
      Py_INCREF(value);
      self->dom_node = (PyNodeObject *) value;
    } else {
      return SAXException(SAXNotSupportedException,
                          "dom-node must be a Document node");
    }
  }
  else {
    PyObject *repr = PyObject_Repr(propertyname);
    if (repr) {
      SAXException(SAXNotRecognizedException, PyString_AsString(repr));
      Py_DECREF(repr);
    }
    return NULL;
  }

  Py_INCREF(Py_None);
  return Py_None;
}

XMLPARSER_METHOD_DOC(getLineNumber) = \
"getLineNumber() -> int\n\
\n\
Return the line number where the current event ends.";

XMLPARSER_METHOD_DEF(getLineNumber)
{
  int lineNumber;

  if (!PyArg_ParseTuple(args, ":getLineNumber"))
    return NULL;

  if (self->dom_node)
    lineNumber = -1;
  else
    lineNumber = Expat_GetLineNumber(self->parser);

  return PyInt_FromLong(lineNumber);
}

XMLPARSER_METHOD_DOC(getColumnNumber) = \
"getColumnNumber() -> int\n\
\n\
Return the column number where the current event ends.";

XMLPARSER_METHOD_DEF(getColumnNumber)
{
  int columnNumber;

  if (!PyArg_ParseTuple(args, ":getColumnNumber"))
    return NULL;

  if (self->dom_node)
    columnNumber = -1;
  else
    columnNumber = Expat_GetColumnNumber(self->parser);

  return PyInt_FromLong(columnNumber);
}

XMLPARSER_METHOD_DOC(getSystemId) = \
"getSystemId() -> string\n\
\n\
Return the system identifier for the current event.";

XMLPARSER_METHOD_DEF(getSystemId)
{
  PyObject *systemId;

  if (!PyArg_ParseTuple(args, ":getSystemId"))
    return NULL;

  if (self->dom_node) {
    systemId = PyDocument_BASE_URI(self->dom_node);
    Py_INCREF(systemId);
  } else {
    systemId = Expat_GetBase(self->parser);
  }
  return systemId;
}

static PyMethodDef parser_methods[] = {
  Py_XMLPARSER_METHOD(setContentHandler),
  Py_XMLPARSER_METHOD(parse),
  Py_XMLPARSER_METHOD(getFeature),
  Py_XMLPARSER_METHOD(setFeature),
  Py_XMLPARSER_METHOD(getProperty),
  Py_XMLPARSER_METHOD(setProperty),

  /* Locator Methods */
  Py_XMLPARSER_METHOD(getLineNumber),
  Py_XMLPARSER_METHOD(getColumnNumber),
  Py_XMLPARSER_METHOD(getSystemId),

  { NULL }
};

static struct memberlist parser_members[] = {
  Py_XMLPARSER_MEMBER(feature_external_entities, T_INT),

  { NULL }
};

static void parser_dealloc(XMLParserObject *self)
{
  int i;

  PyObject_GC_UnTrack(self);

  Py_XDECREF(self->dom_node);
  Py_XDECREF(self->whitespace_rules);

  for (i = 0; i < TotalHandlers; i++) {
    Py_XDECREF(self->handlers[i]);
  }

  Expat_ParserFree(self->parser);
  self->parser = NULL;

  PyObject_GC_Del(self);
}

static int parser_traverse(XMLParserObject *self, visitproc visit, void *arg)
{
  int err, i;

  for (i = 0; i < TotalHandlers; i++) {
    if (self->handlers[i]) {
      err = visit(self->handlers[i], arg);
      if (err) return err;
    }
  }
  
  return 0;
}

static int parser_clear(XMLParserObject *self)
{
  int i;

  for (i = 0; i < TotalHandlers; i++) {
    PyObject *handler = self->handlers[i];
    if (handler != NULL) {
      self->handlers[i] = NULL;
      Py_DECREF(handler);
    }
  }
  return 0;
}

static char parser_doc[] = 
"Interface for reading an XML document using callbacks.\n\
\n\
Parser is the interface that an XML parser's SAX2 driver must\n\
implement. This interface allows an application to set and query\n\
features and properties in the parser, to register event handlers\n\
for document processing, and to initiate a document parse.\n\
\n\
All SAX interfaces are assumed to be synchronous: the parse\n\
methods must not return until parsing is complete, and readers\n\
must wait for an event-handler callback to return before reporting\n\
the next event.";

static PyTypeObject XMLParser_Type = {
  /* PyObject_HEAD     */ PyObject_HEAD_INIT(NULL)
  /* ob_size           */ 0,
  /* tp_name           */ "Ft.Xml.cDomlette.Parser",
  /* tp_basicsize      */ sizeof(XMLParserObject),
  /* tp_itemsize       */ 0,
  /* tp_dealloc        */ (destructor) parser_dealloc,
  /* tp_print          */ (printfunc) 0,
  /* tp_getattr        */ (getattrfunc) 0,
  /* tp_setattr        */ (setattrfunc) 0,
  /* tp_compare        */ (cmpfunc) 0,
  /* tp_repr           */ (reprfunc) 0,
  /* tp_as_number      */ (PyNumberMethods *) 0,
  /* tp_as_sequence    */ (PySequenceMethods *) 0,
  /* tp_as_mapping     */ (PyMappingMethods *) 0,
  /* tp_hash           */ (hashfunc) 0,
  /* tp_call           */ (ternaryfunc) 0,
  /* tp_str            */ (reprfunc) 0,
  /* tp_getattro       */ (getattrofunc) 0,
  /* tp_setattro       */ (setattrofunc) 0,
  /* tp_as_buffer      */ (PyBufferProcs *) 0,
  /* tp_flags          */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
  /* tp_doc            */ (char *) parser_doc,
  /* tp_traverse       */ (traverseproc) parser_traverse,
  /* tp_clear          */ (inquiry) parser_clear,
  /* tp_richcompare    */ (richcmpfunc) 0,
  /* tp_weaklistoffset */ 0,
  /* tp_iter           */ (getiterfunc) 0,
  /* tp_iternext       */ (iternextfunc) 0,
  /* tp_methods        */ (PyMethodDef *) parser_methods,
  /* tp_members        */ (PyMemberDef *) parser_members,
  /* tp_getset         */ (PyGetSetDef *) 0,
  /* tp_base           */ (PyTypeObject *) 0,
  /* tp_dict           */ (PyObject *) 0,
  /* tp_descr_get      */ (descrgetfunc) 0,
  /* tp_descr_set      */ (descrsetfunc) 0,
  /* tp_dictoffset     */ 0,
  /* tp_init           */ (initproc) 0,
  /* tp_alloc          */ (allocfunc) 0,
  /* tp_new            */ (newfunc) 0,
  /* tp_free           */ 0,
};

static ExpatParser createParser(XMLParserObject *self)
{
  ExpatParser parser = Expat_ParserCreate(self);
  if (parser != NULL) {
    /* document callbacks */
    Expat_SetStartDocumentHandler(parser, parser_StartDocument);
    Expat_SetEndDocumentHandler(parser, parser_EndDocument);

    /* element callbacks */
    Expat_SetStartElementHandler(parser, parser_StartElement);
    Expat_SetEndElementHandler(parser, parser_EndElement);

    /* character data (text and cdata section) callback */
    Expat_SetCharacterDataHandler(parser, parser_CharacterData);

    /* namespace callbacks */
    Expat_SetStartNamespaceDeclHandler(parser, parser_StartNamespaceDecl);
    Expat_SetEndNamespaceDeclHandler(parser, parser_EndNamespaceDecl);
  }

  return parser;
}


static PyObject *XMLParser_New(int feature_external_entities)
{
  XMLParserObject *self;
  int i;

  self = PyObject_GC_New(XMLParserObject, &XMLParser_Type);
  if (self != NULL) {
    /* create the XML parser */
    self->parser = createParser(self);
    if (self->parser == NULL) {
      PyObject_GC_Del(self);
      return NULL;
    }

    for (i = 0; i < TotalHandlers; i++)
      self->handlers[i] = NULL;

    self->feature_external_entities = feature_external_entities;
    self->feature_process_xincludes = 1;

    self->whitespace_rules = NULL;
    self->dom_node = NULL;

    PyObject_GC_Track(self);
  }

  return (PyObject *) self;
}

/********** ATTRIBUTES **********/

#define ATTRIBUTES_METHOD_DOC(NAME) METHOD_DOC(attributes, NAME)
#define ATTRIBUTES_METHOD(NAME) METHOD_DEF(attributes, NAME, AttributesObject)
#define Py_ATTRIBUTES_METHOD(NAME) Py_METHOD(attributes, NAME)
#define Py_ATTRIBUTES_MEMBER(NAME,TYPE) Py_MEMBER(NAME, TYPE, AttributesObject)

ATTRIBUTES_METHOD_DOC(getValue) = \
"getValue(name)\n\
Returns the value of the attribute with the given expanded name.";

ATTRIBUTES_METHOD(getValue)
{
  PyObject *name;
  PyObject *result;

  if (!PyArg_ParseTuple(args, "O:getValue", &name)) return NULL;

  result = PyDict_GetItem(self->values, name);
  Py_XINCREF(result);
  return result;
}

ATTRIBUTES_METHOD_DOC(getQNameByName) = \
"getQNameByName(name)\n\
Returns the qualified name of the attribute with the given expanded name.";

ATTRIBUTES_METHOD(getQNameByName)
{
  PyObject *name;
  PyObject *result;

  if (!PyArg_ParseTuple(args, "O:getQNameByName", &name)) return NULL;

  result = PyDict_GetItem(self->qnames, name);
  Py_XINCREF(result);
  return result;
}

ATTRIBUTES_METHOD_DOC(has_key) = \
"has_key(name)\n\
Returns True if the attribute name is in the list, False otherwise.";

ATTRIBUTES_METHOD(has_key)
{
  PyObject *name;
  PyObject *result;

  if (!PyArg_ParseTuple(args, "O:has_key", &name)) return NULL;

  result = PyMapping_HasKey(self->values, name) ? Py_True : Py_False;
  Py_INCREF(result);
  return result;
}

ATTRIBUTES_METHOD_DOC(get) = \
"get(name[, alternative=None])\n\
Return the value associated with attribute name; if it is not available,\n\
then return the alternative.";

ATTRIBUTES_METHOD(get)
{
  PyObject *name, *alternative = Py_None;
  PyObject *result;

  if (!PyArg_ParseTuple(args, "O|O:get", &name, &alternative)) return NULL;

  result = PyDict_GetItem(self->values, name);
  if (result == NULL) {
    result = alternative;
  }
  Py_INCREF(result);
  return result;
}

ATTRIBUTES_METHOD_DOC(keys) = \
"keys()\n\
Returns a list of the names of all attribute in the list.";

ATTRIBUTES_METHOD(keys)
{
  if (!PyArg_ParseTuple(args, ":keys")) return NULL;

  return PyDict_Keys(self->values);
}

ATTRIBUTES_METHOD_DOC(items) = \
"items()\n\
Return a list of (attribute_name, value) pairs.";

ATTRIBUTES_METHOD(items)
{
  if (!PyArg_ParseTuple(args, ":items")) return NULL;

  return PyDict_Items(self->values);
}

ATTRIBUTES_METHOD_DOC(values) = \
"values()\n\
Return a list of all attribute values.";

ATTRIBUTES_METHOD(values)
{
  if (!PyArg_ParseTuple(args, ":values")) return NULL;

  return PyDict_Values(self->values);
}

static PyMethodDef attributes_methods[] = {
  Py_ATTRIBUTES_METHOD(getValue),
  Py_ATTRIBUTES_METHOD(getQNameByName),

  /* named mapping methods */
  Py_ATTRIBUTES_METHOD(has_key),
  Py_ATTRIBUTES_METHOD(get),
  Py_ATTRIBUTES_METHOD(keys),
  Py_ATTRIBUTES_METHOD(items),
  Py_ATTRIBUTES_METHOD(values),
  { NULL }
};

/** PySequenceMethods **********/

static int attributes_length(AttributesObject *self)
{
  return self->length;
}

static int attributes_contains(AttributesObject *self, PyObject *key)
{
  return PyDict_GetItem(self->values, key) != NULL;
}

static PySequenceMethods attributes_as_sequence = {
  /* sq_length         */ (inquiry) attributes_length,
  /* sq_concat         */ (binaryfunc) 0,
  /* sq_repeat         */ (intargfunc) 0,
  /* sq_item           */ (intargfunc) 0,
  /* sq_slice          */ (intintargfunc) 0,
  /* sq_ass_item       */ (intobjargproc) 0,
  /* sq_ass_slice      */ (intintobjargproc) 0,
  /* sq_contains       */ (objobjproc) attributes_contains,
  /* sq_inplace_concat */ (binaryfunc) 0,
  /* sq_inplace_repeat */ (intargfunc) 0,
};

/** PyMappingMethods **********/

static PyObject *attributes_subscript(AttributesObject *self, PyObject *name)
{
  PyObject *result = PyDict_GetItem(self->values, name);
  Py_XINCREF(result);
  return result;
}

static int attributes_ass_subscript(AttributesObject *self, PyObject *name,
                                    PyObject *value)
{
  int rc;

  if (value == NULL) {
    /* delete item */
    if ((rc = PyDict_DelItem(self->values, name)) == 0) {
      rc = PyDict_DelItem(self->qnames, name);
    }
  } else {
    PyErr_SetString(PyExc_TypeError, 
                    "object does not support item assignment");
    rc = -1;
  }

  return rc;
}

static PyMappingMethods attributes_as_mapping = {
  /* mp_length        */ (inquiry) attributes_length,
  /* mp_subscript     */ (binaryfunc) attributes_subscript,
  /* mp_ass_subscript */ (objobjargproc) attributes_ass_subscript,
};

/** Type Methods **************/

static void attributes_dealloc(AttributesObject *self)
{
  PyObject_GC_UnTrack((PyObject *) self);

  self->length = 0;

  if (self->values != NULL) {
    Py_DECREF(self->values);
    self->values = NULL;
  }

  if (self->qnames != NULL) {
    Py_DECREF(self->qnames);
    self->qnames = NULL;
  }

  if (num_free_attrs < MAX_FREE_ATTRS) {
    free_attrs[num_free_attrs++] = self;
  } else {
    PyObject_GC_Del(self);
  }
}

static int attributes_print(AttributesObject *self, FILE *fp, int flags)
{
  return PyObject_Print(self->values, fp, flags);
}

static PyObject *attributes_repr(AttributesObject *self)
{
  return PyObject_Repr(self->values);
}

static int attributes_traverse(AttributesObject *self, visitproc visit, 
                               void *arg)
{
  int err;

  if (self->values != NULL) {
    err = visit(self->values, arg);
    if (err) return err;
  }

  if (self->qnames != NULL) {
    err = visit(self->qnames, arg);
    if (err) return err;
  }

  return 0;
}

static int attributes_clear(AttributesObject *self)
{
  PyObject *tmp;

  if (self->values != NULL) {
    tmp = self->values;
    self->values = NULL;
    Py_DECREF(tmp);
  }

  if (self->qnames != NULL) {
    tmp = self->qnames;
    self->qnames = NULL;
    Py_DECREF(tmp);
  }

  return 0;
}

static PyObject *attributes_iter(AttributesObject *self)
{
  return PyObject_GetIter(self->values);
}

static char attributes_doc[] = 
"Interface for a list of XML attributes.\n\
\n\
Contains a list of XML attributes, accessible by name.";

static PyTypeObject Attributes_Type = {
  /* PyObject_HEAD     */ PyObject_HEAD_INIT(NULL)
  /* ob_size           */ 0,
  /* tp_name           */ "Ft.Xml.cDomlette.Attributes",
  /* tp_basicsize      */ sizeof(AttributesObject),
  /* tp_itemsize       */ 0,
  /* tp_dealloc        */ (destructor) attributes_dealloc,
  /* tp_print          */ (printfunc) attributes_print,
  /* tp_getattr        */ (getattrfunc) 0,
  /* tp_setattr        */ (setattrfunc) 0,
  /* tp_compare        */ (cmpfunc) 0,
  /* tp_repr           */ (reprfunc) attributes_repr,
  /* tp_as_number      */ (PyNumberMethods *) 0,
  /* tp_as_sequence    */ (PySequenceMethods *) &attributes_as_sequence,
  /* tp_as_mapping     */ (PyMappingMethods *) &attributes_as_mapping,
  /* tp_hash           */ (hashfunc) 0,
  /* tp_call           */ (ternaryfunc) 0,
  /* tp_str            */ (reprfunc) 0,
  /* tp_getattro       */ (getattrofunc) 0,
  /* tp_setattro       */ (setattrofunc) 0,
  /* tp_as_buffer      */ (PyBufferProcs *) 0,
  /* tp_flags          */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
  /* tp_doc            */ (char *) attributes_doc,
  /* tp_traverse       */ (traverseproc) attributes_traverse,
  /* tp_clear          */ (inquiry) attributes_clear,
  /* tp_richcompare    */ (richcmpfunc) 0,
  /* tp_weaklistoffset */ 0,
  /* tp_iter           */ (getiterfunc) attributes_iter,
  /* tp_iternext       */ (iternextfunc) 0,
  /* tp_methods        */ (PyMethodDef *) attributes_methods,
  /* tp_members        */ (PyMemberDef *) 0,
  /* tp_getset         */ (PyGetSetDef *) 0,
  /* tp_base           */ (PyTypeObject *) 0,
  /* tp_dict           */ (PyObject *) 0,
  /* tp_descr_get      */ (descrgetfunc) 0,
  /* tp_descr_set      */ (descrsetfunc) 0,
  /* tp_dictoffset     */ 0,
  /* tp_init           */ (initproc) 0,
  /* tp_alloc          */ (allocfunc) 0,
  /* tp_new            */ (newfunc) 0,
  /* tp_free           */ 0,
};

static AttributesObject *Attributes_New(void)
{
  AttributesObject *self;

  if (num_free_attrs) {
    num_free_attrs--;
    self = free_attrs[num_free_attrs];
    _Py_NewReference((PyObject *) self);
  } else {
    self = PyObject_GC_New(AttributesObject, &Attributes_Type);
    if (self == NULL)
      return NULL;
  }

  self->length = 0;
  self->values = PyDict_New();
  self->qnames = PyDict_New();
  if (self->values == NULL || self->qnames == NULL) {
    Py_XDECREF(self->values);
    Py_XDECREF(self->qnames);
    Py_DECREF(self);
    return NULL;
  }

  PyObject_GC_Track(self);

  return self;
}

/**********************************************************************/
/** External interfaces ***********************************************/
/**********************************************************************/

static int read_external_dtd;

char CreateParser_doc[] = 
"CreateParser([readExtDtd]) -> Parser\n\
\n\
Return a new XML parser object.";

PyObject *Domlette_CreateParser(PyObject *dummy, PyObject *args, PyObject *kw)
{
  static char *kwlist[] = { "readExtDtd", NULL };
  PyObject *readExtDtd=NULL;
  int read_ext_dtd=read_external_dtd;

  if (!PyArg_ParseTupleAndKeywords(args, kw, "|O:CreateParser", kwlist,
                                   &readExtDtd))
    return NULL;

  if (readExtDtd) {
    read_ext_dtd = PyObject_IsTrue(readExtDtd);
    if (read_ext_dtd == -1) return NULL;
  }

  return XMLParser_New(read_ext_dtd);
}

int DomletteParser_Init(PyObject *module)
{
  PyObject *import, *constant;

  import = PyImport_ImportModule("Ft.Lib.Uri");
  if (import == NULL) return -1;
  uri_resolver = PyObject_GetAttrString(import, "BASIC_RESOLVER");
  if (uri_resolver == NULL) {
    Py_DECREF(import);
    return -1;
  }
  Py_DECREF(import);

  import = PyImport_ImportModule("Ft.Xml");
  if (import == NULL) return -1;
  constant = PyObject_GetAttrString(import, "READ_EXTERNAL_DTD");
  if (constant == NULL) {
    Py_DECREF(import);
    return -1;
  }
  Py_DECREF(import);

  read_external_dtd = PyObject_IsTrue(constant);
  Py_DECREF(constant);
  if (read_external_dtd == -1) return -1;

  if (PyType_Ready(&InputSource_Type) < 0)
    return -1;

  if (PyType_Ready(&XMLParser_Type) < 0)
    return -1;

  if (PyType_Ready(&Attributes_Type) < 0)
    return -1;

  /* define 4Suite's extended feature & property constants */
#define ADD_STRING_CONST(cname, pname, string)          \
  if ((cname = PyString_FromString(string)) == NULL)    \
    return -1;                                          \
  if (PyModule_AddObject(module, pname, cname) == -1) { \
    Py_DECREF(cname);                                   \
    return -1;                                          \
  }                                                     \
  Py_INCREF(cname);

  ADD_STRING_CONST(feature_process_xincludes, "FEATURE_PROCESS_XINCLUDES",
                   "http://4suite.org/sax/features/process-xincludes");
  ADD_STRING_CONST(property_whitespace_rules, "PROPERTY_WHITESPACE_RULES",
                   "http://4suite.org/sax/properties/whitespace-rules");

#define GET_MODULE_CONST(name)                  \
  name = PyObject_GetAttrString(import, #name); \
  if (name == NULL) {                           \
    Py_DECREF(import);                          \
    return -1;                                  \
  }

  /* load the SAX exceptions */
  import = PyImport_ImportModule("xml.sax");
  if (import == NULL) return -1;
  GET_MODULE_CONST(SAXNotRecognizedException);
  GET_MODULE_CONST(SAXNotSupportedException);
  Py_DECREF(import);

  /* load the SAX standard feature & property constants */
  import = PyImport_ImportModule("xml.sax.handler");
  if (import == NULL) return -1;
  GET_MODULE_CONST(feature_external_ges);
  GET_MODULE_CONST(feature_namespaces);
  GET_MODULE_CONST(feature_namespace_prefixes);
  GET_MODULE_CONST(property_dom_node);
  Py_DECREF(import);

  /* load the SAX InputSource class */
  import = PyImport_ImportModule("xml.sax.xmlreader");
  if (import == NULL) return -1;
  sax_input_source = PyObject_GetAttrString(import, "InputSource");
  if (sax_input_source == NULL) {
    Py_DECREF(import);
    return -1;
  }
  Py_DECREF(import);

  return 0;
}

void DomletteParser_Fini(void)
{
  AttributesObject *attr;
  int i;

  while(num_free_attrs) {
    num_free_attrs--;
    attr = free_attrs[num_free_attrs];
    free_attrs[num_free_attrs] = NULL;
    PyObject_GC_Del(attr);
  }

  for (i = 0; i < TotalHandlers; i++) {
    PyCodeObject *code = tb_codes[i];
    if (code != NULL) {
      tb_codes[i] = NULL;
      Py_DECREF(code);
    }
  }
  
  Py_DECREF(uri_resolver);
  Py_DECREF(feature_process_xincludes);
  Py_DECREF(property_whitespace_rules);
  Py_DECREF(SAXNotRecognizedException);
  Py_DECREF(SAXNotSupportedException);
  Py_DECREF(feature_external_ges);
  Py_DECREF(feature_namespaces);
  Py_DECREF(feature_namespace_prefixes);
  Py_DECREF(property_dom_node);
  Py_DECREF(sax_input_source);
}
