#include "expat_module.h"
#include "state_machine.h"
#include "util.h"
#include "xmlchar.h"
#include "exceptions.h"
#include "cStringIO.h"

/* Should not be a valid  XML character to ensure that splitting is correct. 
   Using a form-feed (ASCII character 12).
*/
#define EXPAT_NSSEP '\f'

/* using a 64K buffer helps read performance for large documents */
#define EXPAT_BUFSIZ 65536

/* 8K buffer should be plenty for most documents (it does resize if needed) */
#define XMLCHAR_BUFSIZ 8192

/* States */
#define PARSE_STREAM_STATE 10
#define START_ELEMENT_CALLBACK 11
#define END_ELEMENT_CALLBACK 12
#define CHARACTER_DATA_CALLBACK 13
#define COMMENT_CALLBACK 14
#define PI_CALLBACK 15
#define START_NS_SCOPE_CALLBACK 16
#define END_NS_SCOPE_CALLBACK 17

#define XPTR_START_STATE 20

static PyObject *encoding_string;
static PyObject *uri_string;
static PyObject *stream_string;
static PyObject *asterisk_string;
static PyObject *space_string;
static PyObject *preserve_string;
static PyObject *default_string;
static PyObject *xinclude_hint_string;
static PyObject *external_entity_hint_string;
static PyObject *xpointer_close_event;
static PyObject *absolutize_function;
static PyObject *expat_library_error;

typedef struct _input_source {
  struct _input_source *next;    /* next item on the stack */
  PyObject *object;              /* the Python InputSource object */
  PyObject *uri;                 /* the URI of the current document */
  PyObject *stream;              /* the stream for the current document */
} InputSource;

enum NameTestType {
  ELEMENT_TEST,
  NAMESPACE_TEST,
  EXPANDED_NAME_TEST,
};

typedef struct {
  enum NameTestType test_type;
  PyObject *test_namespace;
  PyObject *test_name;
  PyObject *preserve_flag;
} WhitespaceRule;

typedef struct {
  int size;
  WhitespaceRule items[1];
} WhitespaceRules;

struct ExpatParserStruct {
  /* the value passed as the userState argument to callbacks */
  void *userState;

  /* the state machine for handling callbacks */
  StateTable *table;

  /* the Expat parser */
  XML_Parser parser;

  /* our handlers */
  ExpatStartDocumentHandler start_document_handler;
  ExpatEndDocumentHandler end_document_handler;
  ExpatStartElementHandler start_element_handler;
  ExpatEndElementHandler end_element_handler;
  ExpatCharacterDataHandler character_data_handler;
  ExpatProcessingInstructionHandler processing_instruction_handler;
  ExpatCommentHandler comment_handler;
  ExpatStartNamespaceDeclHandler start_namespace_decl_handler;
  ExpatEndNamespaceDeclHandler end_namespace_decl_handler;
  ExpatDoctypeDeclHandler doctype_decl_handler;
  ExpatUnparsedEntityDeclHandler unparsed_entity_decl_handler;

  /* caching members */
  PyObject *name_cache;          /* element name parts */
  HashTable *interned_unicode;   /* XMLChar to unicode mapping */
  PyObject **attrs;              /* reusable attributes list */
  int attrs_size;                /* allocated size of attributes list */

  /* character data buffering */
  XML_Char *buffer;              /* buffer used for accumulating characters */
  int buffer_size;               /* size of buffer (in XML_Char units) */
  int buffer_used;               /* buffer units in use */

  /* parsing options */
  int process_xincludes;         /* flag for XInclude processing */

  /* parsing data */
  InputSource *input_source;         /* stack of open InputSources */
  WhitespaceRules *whitespace_rules; /* array of whitespace stripping rules */
  Stack *xml_space_stack;            /* indicates xml:space='preserve' */
  Stack *preserve_whitespace_stack;  /* XSLT WS-stripping allowed */
  Stack *xpointer_event_stack;       /* XPTR match transition event */
  int xinclude_depth;            /* flag for ignoring <xi:include> content */

  /* XPointer state */
  const XML_Char *expat_name;
  const XML_Char **expat_atts;
  PyObject *xpointer_depth_event;
};

#define INITIAL_ATTR_SIZE 11  /* 5 attribute pairs + terminator */

/** EXPAT callbacks *****************************************************/

static void expat_StartElement(void *userData, const XML_Char *name,
                               const XML_Char **atts);
static void expat_EndElement(void *userData, const XML_Char *name);

static void expat_CharacterData(void *userData, const XML_Char *s, int len);

static void expat_ProcessingInstruction(void *userData, const XML_Char *target,
                                        const XML_Char *data);

static void expat_Comment(void *userData, const XML_Char *data);

static void expat_StartNamespaceDecl(void *userData, const XML_Char *prefix,
                                     const XML_Char *uri);

static void expat_EndNamespaceDecl(void *userData, const XML_Char *prefix);

static void expat_UnparsedEntityDecl(void *userData,
                                     const XML_Char *entityName,
                                     const XML_Char *base,
                                     const XML_Char *systemId,
                                     const XML_Char *publicId,
                                     const XML_Char *notationName);

static void expat_StartDoctypeDecl(void *userData, const XML_Char *name,
                                   const XML_Char *sysid,
                                   const XML_Char *pubid,
                                   int has_internal_subset);

static void expat_EndDoctypeDecl(void *userData);

static int expat_ExternalEntityRef(XML_Parser parser, const XML_Char *context,
                                   const XML_Char *base, 
                                   const XML_Char *systemId,
                                   const XML_Char *publicId);

static int expat_UnknownEncodingHandler(void *encodingHandlerData,
                                        const XML_Char *name,
                                        XML_Encoding *info);


/** InputSource helpers ***********************************************/


static InputSource *createInputSource(PyObject *source)
{
  PyObject *uri;
  PyObject *stream;
  InputSource *is;

  uri = PyObject_GetAttr(source, uri_string);
  if (uri == NULL) {
    return NULL;
  }

  if (!PyUnicode_Check(uri)) {
    /* Convert the URI to a unicode object */
    PyObject *temp = PyObject_Unicode(uri);
    Py_DECREF(uri);
    if (temp == NULL) {
      return NULL;
    }
    uri = temp;
  }

  stream = PyObject_GetAttr(source, stream_string);
  if (stream == NULL) {
    Py_DECREF(uri);
    return NULL;
  }

  if ((is = PyMem_New(InputSource, 1)) == NULL) {
    Py_DECREF(uri);
    Py_DECREF(stream);
    PyErr_NoMemory();
    return NULL;
  }

  is->next = NULL;
  Py_INCREF(source);
  is->object = source;
  is->uri = uri;
  is->stream = stream;
  return is;
}


static void freeInputSource(InputSource *is)
{
  Py_DECREF(is->object);
  Py_DECREF(is->uri);
  Py_DECREF(is->stream);
  PyMem_Del(is);
}


static InputSource *resolveInputSource(InputSource *inputSource, 
                                       PyObject *publicId,
                                       PyObject *systemId,
                                       PyObject *hint)
{
  PyObject *source;
  InputSource *is;

  /* use the given InputSource to find the requested reference */
  source = PyObject_CallMethod(inputSource->object, "resolve", "OOO",
                               systemId, publicId, hint);
  if (source == NULL) {
    return NULL;
  }

  is = createInputSource(source);
  Py_DECREF(source);
  return is;
}


/** Whitespace Stripping **********************************************/


static void freeWhitespaceRules(WhitespaceRules *rules)
{
  int i;

  i = rules->size;
  while (--i >= 0) {
    WhitespaceRule rule = rules->items[i];
    switch (rule.test_type) {
    case EXPANDED_NAME_TEST:
      Py_DECREF(rule.test_name);
      /* fall through */
    case NAMESPACE_TEST:
      Py_DECREF(rule.test_namespace);
      /* fall through */
    case ELEMENT_TEST:
      break;
    }
  }
  PyMem_Free(rules);
}


static WhitespaceRules *createWhitespaceRules(PyObject *stripElements)
{
  int i, length, nbytes;
  WhitespaceRules *rules;

  if (stripElements == NULL) {
    PyErr_BadInternalCall();
    return 0;
  }

  stripElements = PySequence_Tuple(stripElements);
  if (stripElements == NULL) {
    return 0;
  }

  length = PyTuple_GET_SIZE(stripElements);
  nbytes = SIZEOF_INT + (sizeof(WhitespaceRule) * length);
  if ((rules = (WhitespaceRules *) PyMem_Malloc(nbytes)) == NULL) {
    PyErr_NoMemory();
    Py_DECREF(stripElements);
    return 0;
  }
  rules->size = length;

  for (i = 0; i < length; i++) {
    PyObject *rule, *namespace_uri, *local_name;

    rule = PyTuple_GET_ITEM(stripElements, i);
    if (!PyTuple_Check(rule) || PyTuple_GET_SIZE(rule) != 3) {
      PyErr_SetString(PyExc_TypeError, 
                      "stripElements must be a list of 3-item tuples");
      rules->size = i; /* prevent processing of items not yet initialized */
      freeWhitespaceRules(rules);
      Py_DECREF(stripElements);
      return NULL;
    }

    /* Each strip element specifies a NS and a local name,
       The localname can be a name or *
       If the localname is * then the NS could be None.
       ns:local is a complete match
       ns:* matches any element in the given namespace
       * matches any element.

       NOTE:  There are precedence rules to stripping as (according to XSLT)
       the matches should be treated as node tests.  The precedence is based
       on level of specificity.  This code assumes the list of white space
       rules has already been sorted by precedence.
    */
    namespace_uri = PyTuple_GET_ITEM(rule, 0);
    local_name = PyTuple_GET_ITEM(rule, 1);
    if (PyObject_RichCompareBool(local_name, asterisk_string, Py_EQ)) {
      if (namespace_uri == Py_None) {
        /* rule matches every element */
        rules->items[i].test_type = ELEMENT_TEST;
      } else {
        /* rule matches any element in the target namespace */
        rules->items[i].test_type = NAMESPACE_TEST;
        rules->items[i].test_namespace = namespace_uri;
        Py_INCREF(namespace_uri);
      }
    } else {
      rules->items[i].test_type = EXPANDED_NAME_TEST;
      rules->items[i].test_namespace = namespace_uri;
      rules->items[i].test_name = local_name;
      Py_INCREF(namespace_uri);
      Py_INCREF(local_name);
    }

    if (PyObject_IsTrue(PyTuple_GET_ITEM(rule, 2))) {
      rules->items[i].preserve_flag = Py_False;
    } else {
      rules->items[i].preserve_flag = Py_True;
    }
  }
  Py_DECREF(stripElements);

  return rules;
}


/** Unicode Interning *************************************************/


static PyObject *makeUnicodeSize(ExpatParser parser, const XML_Char *s, int n)
{
  PyObject *unistr;
  unistr = HashTable_Lookup(parser->interned_unicode, s, n);
  if (unistr == NULL)
    return NULL;

  Py_INCREF(unistr);
  return unistr;
}
#define makeUnicode(p, xc) makeUnicodeSize((p), (xc), XMLChar_Len(xc))


/** Character Data Buffering ******************************************/


/* Returns boolean indicating success */
static int writeCharacterBuffer(ExpatParser parser, const XML_Char *data,
                                int len)
{
  XML_Char *buffer = parser->buffer;
  register int new_len = parser->buffer_used + len;
  register int new_size = parser->buffer_size;

  if (len == 0) return 1;

  while (new_len > new_size) {
    new_size <<= 1;
    if (PyMem_Resize(buffer, XML_Char, new_size) == NULL) {
      PyErr_NoMemory();
      return 0;
    }
    parser->buffer = buffer;
    parser->buffer_size = new_size;
  }

  /* store the new data */
  if (len == 1)
    buffer[parser->buffer_used] = data[0];
  else
    memcpy(buffer + parser->buffer_used, data, len * sizeof(XML_Char));
  parser->buffer_used = new_len;
  return 1;
}

/* Returns boolean indicating success */
#define IS_XMLSPACE(c) (((c) == 0x09) || \
			((c) == 0x0A) || \
			((c) == 0x0D) || \
			((c) == 0x20))

static int flushCharacterBuffer(ExpatParser parser)
{
  register int len = parser->buffer_used;
  PyObject *data, *preserve_whitespace;
  Py_UNICODE *ustr;
  int i, length;

  if (len > 0) {
    /* strings of length 1 are quite common (linefeeds between elements) */
    if (len == 1) {
      data = makeUnicodeSize(parser, parser->buffer, len);
    } else {
      data = Unicode_FromXMLCharAndSize(parser->buffer, len);
    }

    if (data == NULL) {
      StateTable_SignalError(parser->table);
      return 0;
    }
    parser->buffer_used = 0;

    preserve_whitespace = Stack_PEEK(parser->preserve_whitespace_stack);
    if (preserve_whitespace == Py_False) {
      /* Always preserve the text node if it contains non XML space chars */
      length = PyUnicode_GET_SIZE(data);
      ustr = PyUnicode_AS_UNICODE(data);
      for (i = 0; i < length; i++) {
        Py_UNICODE ch = ustr[i];
        if (!((ch == 0x09) || (ch == 0x0A) || (ch == 0x0D) || (ch == 0x20))) {
          preserve_whitespace = Py_True;
          break;
        }
      }
    }

    if (preserve_whitespace == Py_True) {
      if (StateTable_Transit(parser->table, CHARACTER_DATA_EVENT)
          == CHARACTER_DATA_CALLBACK) {
        parser->character_data_handler(parser->userState, data);
      }
    }
    Py_DECREF(data);

    StateTable_Transit(parser->table, PARSE_RESUME_EVENT);
  }

  return 1;
}


/** Parsing routines **************************************************/


static void copyExpatHandlers(ExpatParser parser, XML_Parser new_parser)
{
  if (parser->start_element_handler)
    XML_SetStartElementHandler(new_parser, expat_StartElement);

  if (parser->end_element_handler)
    XML_SetEndElementHandler(new_parser, expat_EndElement);

  if (parser->character_data_handler)
    XML_SetCharacterDataHandler(new_parser, expat_CharacterData);

  if (parser->processing_instruction_handler)
    XML_SetProcessingInstructionHandler(new_parser, 
                                        expat_ProcessingInstruction);

  if (parser->comment_handler)
    XML_SetCommentHandler(new_parser, expat_Comment);

  if (parser->start_namespace_decl_handler)
    XML_SetStartNamespaceDeclHandler(new_parser, expat_StartNamespaceDecl);

  if (parser->end_namespace_decl_handler)
    XML_SetEndNamespaceDeclHandler(new_parser, expat_EndNamespaceDecl);

  if (parser->unparsed_entity_decl_handler)
    XML_SetUnparsedEntityDeclHandler(new_parser, expat_UnparsedEntityDecl);

  XML_SetDoctypeDeclHandler(new_parser, expat_StartDoctypeDecl, 
                            expat_EndDoctypeDecl);
  XML_SetExternalEntityRefHandler(new_parser, expat_ExternalEntityRef);
}

#define setExpatHandlers(parser) copyExpatHandlers(parser, parser->parser)


static void setExpatSubsetHandlers(ExpatParser parser)
{
  XML_SetProcessingInstructionHandler(parser->parser, NULL);
  XML_SetCommentHandler(parser->parser, NULL);
}


static void clearExpatHandlers(ExpatParser parser)
{
  XML_SetNamespaceDeclHandler(parser->parser, NULL, NULL);
  XML_SetElementHandler(parser->parser, NULL, NULL);
  XML_SetCharacterDataHandler(parser->parser, NULL);
  XML_SetProcessingInstructionHandler(parser->parser, NULL);
  XML_SetCommentHandler(parser->parser, NULL);
  XML_SetDoctypeDeclHandler(parser->parser, NULL, NULL);
  XML_SetUnparsedEntityDeclHandler(parser->parser, NULL);
  XML_SetExternalEntityRefHandler(parser->parser, NULL);
}


static XML_Parser createExpatParser(ExpatParser parser)
{
  XML_Parser new_parser = XML_ParserCreateNS(NULL, EXPAT_NSSEP);
  if (new_parser == NULL) {
    PyErr_NoMemory();
    return NULL;
  }

  /* enable prefix information in names (URI + sep + local + sep + prefix) */
  XML_SetReturnNSTriplet(new_parser, 1);

  /* enable use of all encodings available with Python */
  XML_SetUnknownEncodingHandler(new_parser, expat_UnknownEncodingHandler, 
                                NULL);

  XML_SetUserData(new_parser, (void *)parser);

  return new_parser;
}


/* optimized routine for reading from real file objects */
static int read_file(PyObject *file, char *buffer, int length)
{
  FILE *fp = (FILE *) file;
  size_t bytes_read;

  Py_BEGIN_ALLOW_THREADS;
  errno = 0;
  bytes_read = fread(buffer, sizeof(char), length, fp);
  Py_END_ALLOW_THREADS;

  if (bytes_read == 0 && ferror(fp)) {
    PyErr_SetFromErrno(PyExc_IOError);
    clearerr(fp);
    return -1;
  }

  return bytes_read;
}


/* optimized routine for reading from cStringIO objects */
static int read_stringio(PyObject *stream, char *buffer, int length)
{
  char *data;
  int bytes_read;

  bytes_read = PycStringIO->cread(stream, &data, length);

  if (bytes_read > 0)
    memcpy(buffer, data, bytes_read);

  return bytes_read;
}


/* generic routine for reading from any Python object */
static int read_object(PyObject *stream, char *buffer, int length)
{
  PyObject *str;
  char *data;
  int bytes_read = -1;

  str = PyObject_CallMethod(stream, "read", "i", length);
  if (str == NULL)
    return -1;

  /* bytes_read will be unmodified on error, so OK to ignore result */
  PyString_AsStringAndSize(str, &data, &bytes_read);

  if (bytes_read > 0)
    memcpy(buffer, data, bytes_read);

  Py_DECREF(str);
  return bytes_read;
}


/* Returns boolean indicating success */
static int doParse(ExpatParser parser)
{
  PyObject *encoding, *uri;
  int (*read_func)(PyObject *, char *, int);
  PyObject *read_arg;
  enum XML_Status status;
  int bytes_read;

  /* sanity check */
  if (parser->input_source == NULL) {
    PyErr_BadInternalCall();
    return 0;
  }

  /* Set externally defined encoding, if defined */
  encoding = PyObject_GetAttr(parser->input_source->object, encoding_string);
  if (encoding == NULL) {
    return 0;
  } else if (encoding == Py_None) {
    Py_DECREF(encoding);
  } else {
    XML_Char *encstr = XMLChar_FromObject(encoding);
    Py_DECREF(encoding);
    if (encstr == NULL) {
      return 0;
    }
    status = XML_SetEncoding(parser->parser, encstr);
    free(encstr);
    if (status != XML_STATUS_OK) {
      PyErr_NoMemory();
      return 0;
    }
  }

  /* Set the base URI for the stream */
  uri = PyObject_GetAttr(parser->input_source->object, uri_string);
  if (uri == NULL) {
    return 0;
  } else {
    XML_Char *base = XMLChar_FromObject(uri);
    Py_DECREF(uri);
    if (base == NULL) {
      return 0;
    }
    status = XML_SetBase(parser->parser, base);
    free(base);
    if (status != XML_STATUS_OK) {
      PyErr_NoMemory();
      return 0;
    }
  }

  if (PyFile_Check(parser->input_source->stream)) {
    read_func = read_file;
    read_arg = (PyObject *) PyFile_AsFile(parser->input_source->stream);
  }
  else if (PycStringIO_InputCheck(parser->input_source->stream)) {
    read_func = read_stringio;
    read_arg = parser->input_source->stream;
  } else {
    read_func = read_object;
    read_arg = parser->input_source->stream;
  }

  do {
    void *buffer = XML_GetBuffer(parser->parser, EXPAT_BUFSIZ);
    if (buffer == NULL) {
      PyErr_NoMemory();
      return 0;
    }

    bytes_read = read_func(read_arg, (char *)buffer, EXPAT_BUFSIZ);
    if (bytes_read < 0) {
      return 0;
    }

    StateTable_Transit(parser->table, PARSE_RESUME_EVENT);

    status = XML_ParseBuffer(parser->parser, bytes_read, bytes_read == 0);
    
    if (PyErr_Occurred()) {
      /* keep existing exception */
      return 0;
    }
    else if (status != XML_STATUS_OK || 
             StateTable_GetState(parser->table) == ERROR_STATE) {
      /* raise ReaderException(ReaderException.XML_PARSER_ERROR) */
      int expat_error = XML_GetErrorCode(parser->parser);
#ifdef DEBUG_PARSER
      fprintf(stderr, 
              "-- Parsing error ------------ \n"        \
              "Expat error: %s\n"                       \
              "Expat error code: %d\n"                  \
              "Current state: %d\n", 
              XML_ErrorString(expat_error), expat_error,
              StateTable_GetState(parser->table));
#endif
      ReaderException_XmlParseError(parser->input_source->uri,
                                    XML_GetErrorLineNumber(parser->parser),
                                    XML_GetErrorColumnNumber(parser->parser),
                                    XML_ErrorString(expat_error));
      return 0;
    }
  } while (bytes_read > 0);

  return flushCharacterBuffer(parser);
}


/* Returns boolean indicating success */
static int doExternalParse(ExpatParser parser, XML_Parser newParser,
                           PyObject *publicId, PyObject *systemId,
                           PyObject *hint)
{
  XML_Parser old_parser;
  InputSource *source, *other;
  int success;

  /* use current InputSource to get next InputSource */
  source = resolveInputSource(parser->input_source, publicId, systemId, hint);
  if (source == NULL) {
    return 0;
  }

  /* check for recursive processing */
  for (other = parser->input_source; other; other = other->next) {
    if (PyObject_RichCompareBool(source->uri, other->uri, Py_EQ)) {
      ReaderException_RecursiveParseError(source->uri);
      freeInputSource(source);
      return 0;
    }
  }

  /* exchange Expat parsers */
  old_parser = parser->parser;
  parser->parser = newParser;

  /* insert the new InputSource at the front */
  source->next = parser->input_source;
  parser->input_source = source;

  /* do it! */
  success = doParse(parser);

  /* undo state changes */
  parser->input_source = source->next;
  parser->parser = old_parser;

  /* cleanup */
  freeInputSource(source);

  return success;
}

/** XInclude Processing ***********************************************/

typedef struct _criteria {
  struct _criteria *next;
  int code;
  union {
    struct {
      XML_Char *name;
    } element_match;
    struct {
      int value;
      int counter;
    } element_count;
    struct {
      XML_Char *name;
      XML_Char *value;
    } attribute_match;
  } criterion;
} XPointerCriteria;

static int expat_name_compare(const XML_Char *universal_name,
                              const XML_Char *expat_name)
{
  size_t len = XMLChar_Len(universal_name);
  if (XMLChar_NCmp(universal_name, expat_name, len)) return 0;
  return (expat_name[len] == '\0' || expat_name[len] == EXPAT_NSSEP);
}

/* XPointer constants */
#define ELEMENT_MATCH 1
#define ELEMENT_COUNT 2
#define ATTRIBUTE_MATCH 3

static void xpointer_StartElementStateHandler(StateTable *table, void *params)
{
  ExpatParser parser = (ExpatParser) StateTable_GetUserData(table);
  XPointerCriteria *criteria = (XPointerCriteria *) params;
  int match;
  const XML_Char **ppattr;

  match = 0;
  do {
    switch (criteria->code) {
    case ELEMENT_MATCH:
      match = expat_name_compare(criteria->criterion.element_match.name,
                                 parser->expat_name);
      break;
    case ELEMENT_COUNT:
      match = (criteria->criterion.element_count.value ==
               criteria->criterion.element_count.counter);
      criteria->criterion.element_count.counter++;
      break;
    case ATTRIBUTE_MATCH:
      match = 0;
      for (ppattr = parser->expat_atts; *ppattr; ppattr += 2) {
        if (expat_name_compare(criteria->criterion.attribute_match.name,
                               ppattr[0])) {
          match = !XMLChar_Cmp(criteria->criterion.attribute_match.value,
                               ppattr[1]);

          break;
        }
      }
      break;
    };
  } while (match && ((criteria = criteria->next) != NULL));

  if (match) {
    parser->xpointer_depth_event = xpointer_close_event;
    StateTable_Transit(table, XPTR_MATCH_EVENT);
  }
}


/* simply transition to a new state, */
static void xpointer_EndElementStateHandler(StateTable *table, void *params)
{
  StateId *state_id = (StateId *)params;
  StateTable_Transit(table, *state_id);
}


static XPointerCriteria *new_criteria(void) {
  XPointerCriteria *criteria;

  if ((criteria = PyMem_New(XPointerCriteria, 1)) == NULL) {
    PyErr_NoMemory();
    return NULL;
  }
  criteria->next = NULL;
  criteria->code = 0;
  return criteria;
}

static void free_criteria(void *ptr) {
  XPointerCriteria *criteria = (XPointerCriteria *) ptr;
  if (criteria->next) {
    free_criteria(criteria->next);
    criteria->next = NULL;
  }
  switch (criteria->code) {
  case ELEMENT_MATCH:
    if (criteria->criterion.element_match.name) {
      free(criteria->criterion.element_match.name);
      criteria->criterion.element_match.name = NULL;
    }
    break;
  case ELEMENT_COUNT:
    break;
  case ATTRIBUTE_MATCH:
    if (criteria->criterion.attribute_match.name) {
      free(criteria->criterion.attribute_match.name);
      criteria->criterion.attribute_match.name = NULL;
    }
    if (criteria->criterion.attribute_match.value) {
      free(criteria->criterion.attribute_match.value);
      criteria->criterion.attribute_match.value = NULL;
    }
    break;
  };
  PyMem_Del(criteria);
}

static XML_Char *build_expat_name(PyObject *uri, PyObject *local)
{
  XML_Char *expat_name;

  if (uri == Py_None) {
    expat_name = XMLChar_FromObject(local);
  } else {
    PyObject *name = PyUnicode_FromUnicode(NULL,
                                           PyUnicode_GET_SIZE(uri) + \
                                           PyUnicode_GET_SIZE(local) + 1);
    if (name == NULL) return NULL;

    Py_UNICODE_COPY(PyUnicode_AS_UNICODE(name),
                    PyUnicode_AS_UNICODE(uri),
                    PyUnicode_GET_SIZE(uri));

    PyUnicode_AS_UNICODE(name)[PyUnicode_GET_SIZE(uri)] = EXPAT_NSSEP;

    Py_UNICODE_COPY(PyUnicode_AS_UNICODE(name) + PyUnicode_GET_SIZE(uri) + 1,
                    PyUnicode_AS_UNICODE(local),
                    PyUnicode_GET_SIZE(local));
    expat_name = XMLChar_FromObject(name);
    Py_DECREF(name);
  }

  return expat_name;
}

static XPointerCriteria *xpointer_build_criteria(PyObject *params)
{
  XPointerCriteria *criteria, *current;
  int length, i;

  if ((length = PyList_Size(params)) < 0) {
    return NULL;
  }

  criteria = new_criteria();
  current = NULL;

  /* params is a list of tuples */
  for (i = 0; i < length; i++) {
    PyObject *criterion;
    PyObject *uri, *local, *value;

    if (current == NULL) {
      current = criteria;
    } else {
      if ((current->next = new_criteria()) == NULL) {
        free_criteria(criteria);
        return NULL;
      }
      current = current->next;
    }

    criterion = PyList_GET_ITEM(params, i);
    if (!PyTuple_Check(criterion)) {
      PyErr_SetString(PyExc_TypeError,
                      "xpointer_build_criteria: params not list of tuples");
      free_criteria(criteria);
      return NULL;
    }

    current->code = PyInt_AsLong(PyTuple_GET_ITEM(criterion, 0));
    if (PyErr_Occurred()) {
      free_criteria(criteria);
      return NULL;
    }
    switch (current->code) {
    case ELEMENT_MATCH:
      uri = PyTuple_GET_ITEM(criterion, 1);
      local = PyTuple_GET_ITEM(criterion, 2);
      current->criterion.element_match.name = build_expat_name(uri, local);
      if (current->criterion.element_match.name == NULL) {
        free_criteria(criteria);
        return NULL;
      }
      break;
    case ELEMENT_COUNT:
      value = PyTuple_GET_ITEM(criterion, 1);
      current->criterion.element_count.value = PyInt_AsLong(value);
      if (PyErr_Occurred()) {
        PyErr_SetString(PyExc_ValueError,
                        "xpointer_build_criteria: ELEMENT_COUNT target");
        free_criteria(criteria);
        return NULL;
      }
      current->criterion.element_count.counter = 1;
      break;
    case ATTRIBUTE_MATCH:
      uri = PyTuple_GET_ITEM(criterion, 1);
      local = PyTuple_GET_ITEM(criterion, 2);
      value = PyTuple_GET_ITEM(criterion, 3);
      current->criterion.attribute_match.name = build_expat_name(uri, local);
      if (current->criterion.attribute_match.name == NULL) {
        free_criteria(criteria);
        return NULL;
      }
      current->criterion.attribute_match.value = XMLChar_FromObject(value);
      if (current->criterion.attribute_match.value == NULL) {
        free_criteria(criteria);
        return NULL;
      }
      break;
    default:
      PyErr_Format(PyExc_ValueError, "Bad typecode: %d", current->code);
      free_criteria(criteria);
      return NULL;
    };
  }

  return criteria;
}

static StateId handleXPointer(ExpatParser parser, PyObject *transitions)
{
  int i, length;
  StateId new_start_state = 0;
  StateTable *table = parser->table;

  if (!PyList_Check(transitions)) {
    PyErr_SetString(PyExc_TypeError, 
                    "handleXPointer: transitions must be of type list");
    return 0;
  }

  /* Set up base state (equivalent of PARSE_STREAM_STATE for this XInclude) */
  length = PyList_GET_SIZE(transitions);
  for (i = 0; i < length; i++) {
    StateId prev_base, prev_start, prev_end;
    StateId base, start, end;
    StateId *state_id;
    PyObject *spec;
    XPointerCriteria *criteria;

    spec = PyList_GET_ITEM(transitions, i);
    if (!PyTuple_Check(spec)) {
      PyErr_SetString(PyExc_TypeError, 
                      "handleXPointer: transitions must be a list of tuples");
      return 0;
    }

    prev_base = (StateId) PyInt_AsLong(PyTuple_GET_ITEM(spec, 0));
    prev_start = (prev_base == PARSE_STREAM_STATE ? 
                  PARSE_STREAM_STATE : prev_base + 1);
    prev_end = prev_start + 1;

    base = (StateId) PyInt_AsLong(PyTuple_GET_ITEM(spec, 1));
    start = (StateId) PyInt_AsLong(PyTuple_GET_ITEM(spec, 2));
    end = (StateId) PyInt_AsLong(PyTuple_GET_ITEM(spec, 3));
    criteria = xpointer_build_criteria(PyTuple_GET_ITEM(spec, 4));
    if (criteria == NULL) {
      return 0;
    }

    if (new_start_state == 0) {
      new_start_state = base;
    }

    if (StateTable_AddState(table, base) == 0) {
      free_criteria(criteria);
      return 0;
    }

    /* Set up start element handler */
    if (StateTable_AddStateWithHandlerParams(table, start,
                                             xpointer_StartElementStateHandler,
                                             criteria, free_criteria) == 0) {
      free_criteria(criteria);
      return 0;
    }

    if (i == length - 1) {
      /* Final state */
      if (StateTable_AddTransition(table, start, XPTR_MATCH_EVENT,
                                   START_ELEMENT_CALLBACK) == 0) {
        return 0;
      }
    }

    if (!StateTable_AddTransition(table, prev_start, XPTR_MATCH_EVENT, base))
      return 0;
    if (!StateTable_AddTransition(table, base, START_NS_SCOPE_EVENT, base))
      return 0;
    if (!StateTable_AddTransition(table, base, COMMENT_EVENT, base))
      return 0;
    if (!StateTable_AddTransition(table, base, PI_EVENT, base))
      return 0;
    if (!StateTable_AddTransition(table, base, CHARACTER_DATA_EVENT, base))
      return 0;
    if (!StateTable_AddTransition(table, base, START_ELEMENT_EVENT, start))
      return 0;
    if (!StateTable_AddTransition(table, base, PARSE_RESUME_EVENT, base))
      return 0;
    if (!StateTable_AddTransition(table, start, PARSE_RESUME_EVENT, base))
      return 0;

    /* Set up end element handler */
    state_id = (StateId *) malloc(sizeof(StateId));
    if (state_id == NULL) {
      PyErr_NoMemory();
      return 0;
    }
    *state_id = i ? base : prev_base;

    if (StateTable_AddStateWithHandlerParams(table, end,
                                             xpointer_EndElementStateHandler,
                                             state_id, free) == 0) {
      free(state_id);
      return 0;
    }

    if (!StateTable_AddTransition(table, base, END_ELEMENT_EVENT, base))
      return 0;
    if (!StateTable_AddTransition(table, base, XPTR_CLOSE_EVENT, prev_end))
      return 0;

    if (i == length - 1) {
      /* Final state */
      if (StateTable_AddTransition(table, END_ELEMENT_CALLBACK,
                                   XPTR_CLOSE_EVENT, base) == 0)
        return 0;
    }
  }

  return new_start_state;
}

static int xincludeAsXml(ExpatParser parser, PyObject *href)
{
  PyObject *module, *spec;
  StateId start_state;
  int success;
  XML_Parser expat_parser;

#ifdef DEBUG_PARSER
  fprintf(stderr, "XInclude parse='xml'\n");
#endif

  /* Get any XPointer fragment */
  module = PyDict_GetItemString(PyImport_GetModuleDict(),
                                "Ft.Xml.cDomlette"); /* borrowed */
  if (module == NULL) {
    module = PyImport_ImportModule("Ft.Xml.cDomlette");
    if (module == NULL) {
      return 0;
    }
    /* Make it a borrowed reference, a copy exists in sys.modules */
    Py_DECREF(module);
  }

  spec = PyObject_CallMethod(module, "ProcessFragment", "O", href);
  /* We shall use xptr_spec as the key to holding all the refs of the state specs */
  if (spec == NULL) {
    return 0;
  } else if (spec == Py_None) {
    Py_DECREF(spec);
    start_state = PARSE_STREAM_STATE;
  } else {
    PyObject *states = PyObject_GetAttrString(spec, "states");
    Py_DECREF(spec);
    if (states == NULL) {
      return 0;
    }
    start_state = handleXPointer(parser, states);
    Py_DECREF(states);
    if (start_state == 0) {
      return 0;
    }
  }

  expat_parser = createExpatParser(parser);
  if (expat_parser) {
    StateId current_state = StateTable_GetState(parser->table);

    copyExpatHandlers(parser, expat_parser);

    StateTable_SetState(parser->table, start_state);

    success = doExternalParse(parser, expat_parser, Py_None, href, 
                              xinclude_hint_string);
    XML_ParserFree(expat_parser);

    StateTable_SetState(parser->table, current_state);
  } else {
    success = 0;
  }

  return success;
}


static int xincludeAsText(ExpatParser parser, PyObject *href,
                          PyObject *encoding)
{
  InputSource *source;
  PyObject *decoder, *content;
  XML_Char *data;
  int success;

#ifdef DEBUG_PARSER
  fprintf(stderr, "XInclude parse='text'\n");
#endif
  source = resolveInputSource(parser->input_source, Py_None, href,
                              xinclude_hint_string);
  if (source == NULL) return 0;
    
  /* Convert raw stream data to Unicode */
  if (encoding) {
    char *enc = PyString_AsString(encoding);
    if (enc) 
      decoder = PyCodec_StreamReader(enc, source->stream, "strict");
    else 
      decoder = NULL;
  }
  else {
    /* default encoding is UTF-8 */
    decoder = PyCodec_StreamReader("utf-8", source->stream, "strict");
  }
  freeInputSource(source);

  if (decoder == NULL) return 0;

  /* Convert Unicode from decoder to XML_Char for Expat handler */
  content = PyObject_CallMethod(decoder, "read", NULL);
  Py_DECREF(decoder);
  if (content == NULL) return 0;
  else if (!PyUnicode_Check(content)) {
    PyErr_Format(PyExc_TypeError, 
                 "%s decoder did not return a unicode object (type=%s)",
                 encoding ? PyString_AS_STRING(encoding) : "UTF-8",
                 content->ob_type->tp_name);
    Py_DECREF(content);
    return 0;
  }

  data = XMLChar_FromObject(content);
  Py_DECREF(content);
  if (data == NULL) return 0;

  success = writeCharacterBuffer(parser, data, XMLChar_Len(data));
  free(data);

  return success;
}


static int xinclude_CheckName(const XML_Char *expat_name)
{
  static XML_Char universal_name[] = {
    'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', '.', 'w', '3', '.',
    'o', 'r', 'g', '/', '2', '0', '0', '1', '/', 'X', 'I', 'n', 'c', 'l',
    'u', 'd', 'e', EXPAT_NSSEP, 
    'i', 'n', 'c', 'l', 'u', 'd', 'e'
  };
#ifdef XML_UNICODE  /* XML_Char 2 bytes wide (UTF-16) */
  size_t len = sizeof(universal_name) >> 1;
#else /* XML_Char 1 byte wide (UTF-8) */  
  size_t len = sizeof(universal_name);
#endif
  if (XMLChar_NCmp(universal_name, expat_name, len)) return 0;
  return (expat_name[len] == EXPAT_NSSEP || expat_name[len] == '\0');
}


/* StartElementHandler for content of the xi:include element */
static void xinclude_StartElement(void *userData, 
                                  const XML_Char *name,
                                  const XML_Char *atts[])
{
  ExpatParser parser = (ExpatParser) userData;

  if (xinclude_CheckName(name)) {
    parser->xinclude_depth++;
  }
}


/* EndElementHandler for content of the xi:include element */
static void xinclude_EndElement(void *userData, const XML_Char *name)
{
  ExpatParser parser = (ExpatParser) userData;

  if (xinclude_CheckName(name)) {
    parser->xinclude_depth--;

    if (parser->xinclude_depth == 0) {
      /* resume normal event processing */
      setExpatHandlers(parser);
    }
  }
}


static int processXInclude(ExpatParser parser, const XML_Char *atts[])
{
  static const XML_Char href_const[] = { 'h', 'r', 'e', 'f', '\0' };
  static const XML_Char parse_const[] = { 'p', 'a', 'r', 's', 'e', '\0' };
  static const XML_Char text_const[] = { 't', 'e', 'x', 't', '\0' };
  static const XML_Char xml_const[] = { 'x', 'm', 'l', '\0' };
  static const XML_Char encoding_const[] = 
    { 'e', 'n', 'c', 'o', 'd', 'i', 'n', 'g', '\0' };
  PyObject *href, *encoding;
  int i, parse_xml = 1;
  int success;

  href = NULL;
  encoding = NULL;

  for (i = 0; atts[i]; i += 2) {
    const XML_Char *name = atts[i];
    const XML_Char *value = atts[i+1];

    if (XMLChar_Cmp(name, href_const) == 0) {
      /* treat empty href as non-existent (for exception) */
      if (*value) href = Unicode_FromXMLChar(value);
    }
    else if (XMLChar_Cmp(name, encoding_const) == 0) {
      encoding = Unicode_FromXMLChar(value);
    }
    else if (XMLChar_Cmp(name, parse_const) == 0) {
      if (XMLChar_Cmp(value, text_const) == 0) {
        parse_xml = 0;
      }
      else if (XMLChar_Cmp(value, xml_const) != 0) {
        XIncludeException_InvalidParseAttr(Unicode_FromXMLChar(value));
        Py_XDECREF(href);
        Py_XDECREF(encoding);
	return 0;
      }
    }
  }

  if (href == NULL) {
    XIncludeException_MissingHref();
    Py_XDECREF(encoding);
    return 0;
  }

#ifdef DEBUG_PARSER
  fprintf(stderr, "Begin XInclude parsing.\n");
#endif

  if (parse_xml)
    success = xincludeAsXml(parser, href);
  else
    success = xincludeAsText(parser, href, encoding);

#ifdef DEBUG_PARSER
  if (success)
    fprintf(stderr, "End XInclude parsing.\n");
  else 
    fprintf(stderr, "XInclude parsing failed!\n");
#endif

  Py_DECREF(href);
  Py_XDECREF(encoding);

  if (success) {
    /* Switch Expat to the XInclude element content handlers.  The existing
       handlers will be restored when the current start-tag is closed.
    */
    clearExpatHandlers(parser);
    XML_SetElementHandler(parser->parser, xinclude_StartElement,
                          xinclude_EndElement);

    /* Set the number of XInclude start-tags seen */
    parser->xinclude_depth = 1;
  }

  return success;
}

/** EXPAT callbacks ***************************************************/

static int compare_xmlspace_name(const XML_Char *expat_name)
{
  static XML_Char universal_name[] = {
    'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', '.', 'w', '3', '.',
    'o', 'r', 'g', '/', 'X', 'M', 'L', '/', '1', '9', '9', '8', '/', 'n',
    'a', 'm', 'e', 's', 'p', 'a', 'c', 'e', EXPAT_NSSEP, 
    's', 'p', 'a', 'c', 'e'
  };
#ifdef XML_UNICODE  /* XML_Char 2 bytes wide (UTF-16) */
  size_t len = sizeof(universal_name) >> 1;
#else /* XML_Char 1 byte wide (UTF-8) */  
  size_t len = sizeof(universal_name);
#endif
  if (XMLChar_NCmp(universal_name, expat_name, len)) return 0;
  return (expat_name[len] == EXPAT_NSSEP || expat_name[len] == '\0');
}

void expat_StartElement(void *userData, const XML_Char *name,
                        const XML_Char **atts)
{
  ExpatParser parser = (ExpatParser) userData;
  int i;

  if (flushCharacterBuffer(parser) == 0) {
    return;
  }

  if (parser->process_xincludes && xinclude_CheckName(name)) {
    if (processXInclude(parser, atts) == 0)
      StateTable_SignalError(parser->table);
    return;
  }

  parser->expat_name = name;
  parser->expat_atts = atts;
  parser->xpointer_depth_event = Py_None;

  if (StateTable_Transit(parser->table, START_ELEMENT_EVENT)
      == START_ELEMENT_CALLBACK) {
    const XML_Char **ppattr;
    PyObject *python_name, **python_atts, **python_ppattr;
    PyObject *preserve_whitespace, *xml_space_preserve;
    int attrs_size;

    python_name = makeUnicode(parser, name);
    if (python_name == NULL) {
      StateTable_SignalError(parser->table);
      return;
    }

    /** XSLT Whitespace Stripping *********************************/

    /* by default, all elements are whitespace-preserving */
    preserve_whitespace = Py_True;
    if (parser->whitespace_rules != NULL) {
      PyObject *namespaceURI, *localName, *tagName;
      int found;
      if (Expat_SplitName(parser, python_name, &namespaceURI, &localName,
                          &tagName, NULL) == 0) {
        StateTable_SignalError(parser->table);
        Py_DECREF(python_name);
        return;
      }
      
      for (i = 0, found = 0; found == 0 &&
             i < parser->whitespace_rules->size; i++) {
        WhitespaceRule rule = parser->whitespace_rules->items[i];
        switch (rule.test_type) {
        case EXPANDED_NAME_TEST:
          if (PyObject_RichCompareBool(rule.test_name, localName, Py_NE))
            break;
          /* else, fall through for namespace-uri test */
        case NAMESPACE_TEST:
          if (PyObject_RichCompareBool(rule.test_namespace, namespaceURI, 
                                       Py_NE))
            break;
          /* else, fall through to handle match */
        case ELEMENT_TEST:
          preserve_whitespace = rule.preserve_flag;
          found = 1;
        }
      }
      Py_DECREF(namespaceURI);
      Py_DECREF(localName);
      Py_DECREF(tagName);
    }

    /** Attributes ************************************************/

    /* Determine how much memory is needed for the attributes array */
    for (ppattr = atts; *ppattr; ppattr += 2);
    attrs_size = (ppattr - atts) + 1;
    python_atts = parser->attrs;
    if (attrs_size > parser->attrs_size) {
      if (PyMem_Resize(python_atts, PyObject *, attrs_size) == NULL) {
        Py_DECREF(python_name);
        PyErr_NoMemory();
        StateTable_SignalError(parser->table);
        return;
      }
      parser->attrs = python_atts;
      parser->attrs_size = attrs_size;
    }

    xml_space_preserve = Stack_PEEK(parser->xml_space_stack);
    for (ppattr = atts, python_ppattr = python_atts; *ppattr; ppattr += 2) {
      PyObject *attr_name = Unicode_FromXMLChar(ppattr[0]);
      PyObject *attr_value = Unicode_FromXMLChar(ppattr[1]);
      if (attr_name == NULL || attr_value == NULL) {
        Py_XDECREF(attr_name);
        Py_XDECREF(attr_value);
        for (python_ppattr = python_atts; *python_ppattr; python_ppattr++) {
          Py_DECREF(*python_ppattr);
        }
        Py_DECREF(python_name);
        StateTable_SignalError(parser->table);
        return;
      }
      /* store attribute name/value pair */
      *python_ppattr++ = attr_name;
      *python_ppattr++ = attr_value;

      /* check for xml:space */
      if (compare_xmlspace_name(ppattr[0])) {
        if (PyObject_RichCompareBool(preserve_string, attr_value, Py_EQ)) {
          xml_space_preserve = Py_True;
        } 
        else if (PyObject_RichCompareBool(default_string, attr_value, Py_EQ)) {
          xml_space_preserve = Py_False;
        }
      }
    }
    *python_ppattr = 0;

    /* update whitespace stripping flags */
    if (xml_space_preserve == Py_True)
      preserve_whitespace = Py_True;

    Stack_Push(parser->xml_space_stack, xml_space_preserve);
    Stack_Push(parser->preserve_whitespace_stack, preserve_whitespace);

    parser->start_element_handler(parser->userState, python_name, python_atts);

    /* set XPointer event for this element */
    Stack_Push(parser->xpointer_event_stack, parser->xpointer_depth_event);

    Py_DECREF(python_name);
    for (python_ppattr = python_atts; *python_ppattr; python_ppattr++) {
      Py_DECREF(*python_ppattr);
    }
  }

  StateTable_Transit(parser->table, PARSE_RESUME_EVENT);
}


void expat_EndElement(void *userData, const XML_Char *name)
{
  ExpatParser parser = (ExpatParser) userData;

  if (flushCharacterBuffer(parser) == 0) {
    return;
  }

  if (StateTable_Transit(parser->table,
                         END_ELEMENT_EVENT) == END_ELEMENT_CALLBACK) {
    PyObject *python_name, *xpointer_event, *temp;
    
    python_name = makeUnicode(parser, name);
    if (python_name == NULL) {
      StateTable_SignalError(parser->table);
      return;
    }

    parser->end_element_handler(parser->userState, python_name);

    temp = Stack_Pop(parser->xml_space_stack);
    Py_DECREF(temp);

    temp = Stack_Pop(parser->preserve_whitespace_stack);
    Py_DECREF(temp);

    xpointer_event = Stack_Pop(parser->xpointer_event_stack);
    if (xpointer_event != Py_None) {
      EventId event = (EventId) PyInt_AS_LONG(xpointer_event);
      StateTable_Transit(parser->table, event);
    }
    Py_DECREF(xpointer_event);

    Py_DECREF(python_name);
  }

  StateTable_Transit(parser->table, PARSE_RESUME_EVENT);
}


void expat_CharacterData(void *userData, const XML_Char *data, int len)
{
  ExpatParser parser = (ExpatParser) userData;
  if (writeCharacterBuffer(parser, data, len) == 0)
    StateTable_SignalError(parser->table);
}


void expat_ProcessingInstruction(void *userData, const XML_Char *target, 
                                 const XML_Char *data)
{
  ExpatParser parser = (ExpatParser) userData;

  if (flushCharacterBuffer(parser) == 0) {
    return;
  }

  if (StateTable_Transit(parser->table, PI_EVENT) == PI_CALLBACK) {
    PyObject *python_target, *python_data;

    python_target = makeUnicode(parser, target);
    if (python_target == NULL) {
      StateTable_SignalError(parser->table);
      return;
    }
    
    python_data = makeUnicode(parser, data);
    if (python_data == NULL) {
      Py_DECREF(python_target);
      StateTable_SignalError(parser->table);
      return;
    }

    parser->processing_instruction_handler(parser->userState, python_target,
                                           python_data);

    Py_DECREF(python_target);
    Py_DECREF(python_data);
  }

  StateTable_Transit(parser->table, PARSE_RESUME_EVENT);
}


void expat_Comment(void *userData, const XML_Char *data)
{
  ExpatParser parser = (ExpatParser) userData;

  if (flushCharacterBuffer(parser) == 0) {
    return;
  }

  if (StateTable_Transit(parser->table, COMMENT_EVENT) == COMMENT_CALLBACK) {
    PyObject *python_data = Unicode_FromXMLChar(data);
    if (python_data == NULL) {
      StateTable_SignalError(parser->table);
      return;
    }
    
    parser->comment_handler(parser->userState, python_data);

    Py_DECREF(python_data);
  }

  StateTable_Transit(parser->table, PARSE_RESUME_EVENT);
}


void expat_StartNamespaceDecl(void *userData, const XML_Char *prefix, 
                              const XML_Char *uri)
{
  ExpatParser parser = (ExpatParser) userData;

  if (flushCharacterBuffer(parser) == 0) {
    return;
  }

  if (StateTable_Transit(parser->table,
                         START_NS_SCOPE_EVENT) == START_NS_SCOPE_CALLBACK) {
    PyObject *python_prefix, *python_uri;

    if (prefix) {
      python_prefix = Unicode_FromXMLChar(prefix);
      if (python_prefix == NULL) {
        StateTable_SignalError(parser->table);
        return;
      }
    } else {
      Py_INCREF(Py_None);
      python_prefix = Py_None;
    }

    if (uri) {
      python_uri = Unicode_FromXMLChar(uri);
      if (python_uri == NULL) {
        Py_DECREF(python_prefix);
        StateTable_SignalError(parser->table);
        return;
      }
    } else {
      Py_INCREF(Py_None);
      python_uri = Py_None;
    }

    parser->start_namespace_decl_handler(parser->userState, python_prefix,
                                         python_uri);

    Py_DECREF(python_prefix);
    Py_DECREF(python_uri);
  }
  
  StateTable_Transit(parser->table, PARSE_RESUME_EVENT);
}


void expat_EndNamespaceDecl(void *userData, const XML_Char *prefix)
{
  ExpatParser parser = (ExpatParser) userData;

  if (flushCharacterBuffer(parser) == 0) {
    StateTable_SignalError(parser->table);
    return;
  }

  if (StateTable_Transit(parser->table, 
                         END_NS_SCOPE_EVENT) == END_NS_SCOPE_CALLBACK) {
    PyObject *python_prefix;

    if (prefix) {
      python_prefix = Unicode_FromXMLChar(prefix);
      if (python_prefix == NULL) {
        StateTable_SignalError(parser->table);
        return;
      }
    } else {
      Py_INCREF(Py_None);
      python_prefix = Py_None;
    }

    parser->end_namespace_decl_handler(parser->userState, python_prefix);

    Py_DECREF(python_prefix);
  }

  StateTable_Transit(parser->table, PARSE_RESUME_EVENT);
}


void expat_StartDoctypeDecl(void *userData, const XML_Char *name,
                            const XML_Char *sysid, const XML_Char *pubid,
                            int has_internal_subset)
{
  ExpatParser parser = (ExpatParser) userData;

  if (flushCharacterBuffer(parser) == 0) {
    return;
  }

  if (parser->doctype_decl_handler) {
    PyObject *python_sysid, *python_pubid;

    if (sysid) {
      if ((python_sysid = Unicode_FromXMLChar(sysid)) == NULL) {
        StateTable_SignalError(parser->table);
        return;
      }
    } else {
      Py_INCREF(Py_None);
      python_sysid = Py_None;
    }

    if (pubid) {
      if ((python_pubid = Unicode_FromXMLChar(pubid)) == NULL) {
        Py_DECREF(python_sysid);
        StateTable_SignalError(parser->table);
        return;
      }
    } else {
      Py_INCREF(Py_None);
      python_pubid = Py_None;
    }

    parser->doctype_decl_handler(parser->userState, python_sysid, 
                                 python_pubid);

    Py_DECREF(python_sysid);
    Py_DECREF(python_pubid);
  }

  setExpatSubsetHandlers(parser);
}


void expat_UnparsedEntityDecl(void *userData, const XML_Char *entityName,
                              const XML_Char *base, const XML_Char *systemId,
                              const XML_Char *publicId, 
                              const XML_Char *notationName)
{
  ExpatParser parser = (ExpatParser) userData;
  PyObject *python_entityName, *python_base, *python_systemId, *python_uri;

  if (flushCharacterBuffer(parser) == 0) {
    return;
  }

  python_base = Unicode_FromXMLChar(base);
  if (python_base == NULL) {
    StateTable_SignalError(parser->table);
    return;
  }

  python_systemId = Unicode_FromXMLChar(systemId);
  if (python_systemId == NULL) {
    Py_DECREF(python_base);
    StateTable_SignalError(parser->table);
    return;
  }

  python_uri = PyObject_CallFunction(absolutize_function, "NN",
                                     python_systemId, python_base);
  if (python_uri == NULL) {
    StateTable_SignalError(parser->table);
    return;
  }

  python_entityName = Unicode_FromXMLChar(entityName);
  if (python_entityName == NULL) {
    Py_DECREF(python_uri);
    StateTable_SignalError(parser->table);
    return;
  }

  parser->unparsed_entity_decl_handler(parser->userState, python_entityName,
                                       python_uri);

  Py_DECREF(python_uri);
  Py_DECREF(python_entityName);
}


/* internal handler */
void expat_EndDoctypeDecl(void *userData)
{
  ExpatParser parser = (ExpatParser) userData;

  if (flushCharacterBuffer(parser) == 0) {
    return;
  }

  setExpatHandlers(parser);
}


/* internal handler */
int expat_ExternalEntityRef(XML_Parser p, const XML_Char *context,
                            const XML_Char *base, const XML_Char *systemId,
                            const XML_Char *publicId)
{
  ExpatParser parser = (ExpatParser) XML_GetUserData(p);
  PyObject *python_systemId, *python_publicId;
  XML_Parser new_parser;
  int rc;

  if (flushCharacterBuffer(parser) == 0) {
    return 0;
  }

  python_systemId = Unicode_FromXMLChar(systemId);
  if (publicId) {
    python_publicId = Unicode_FromXMLChar(publicId);
  } else {
    Py_INCREF(Py_None);
    python_publicId = Py_None;
  }
  if (python_publicId == NULL || python_systemId == NULL) {
    Py_XDECREF(python_publicId);
    Py_XDECREF(python_systemId);
    StateTable_SignalError(parser->table);
    return 0;
  }

  new_parser = XML_ExternalEntityParserCreate(p, context, NULL);
  if (new_parser) {
#ifdef DEBUG_PARSER
    fprintf(stderr, "Begin external entity parsing.\n");
#endif

    rc = doExternalParse(parser, new_parser, python_publicId, python_systemId,
                         external_entity_hint_string);
    XML_ParserFree(new_parser);

#ifdef DEBUG_PARSER
    if (rc == 0)
      fprintf(stderr, "External entity parsing failed.\n");
    else
      fprintf(stderr, "End external entity parsing.\n");
#endif
  } else {
    PyErr_NoMemory();
    StateTable_SignalError(parser->table);
    rc = 0;
  }

  Py_DECREF(python_publicId);
  Py_DECREF(python_systemId);

  return rc;
}

/* unknown encoding support */

typedef struct {
  PyObject *decoder;
  int length[256];
} UnknownEncoding;

static int encoding_convert(void *userData, const char *bytes)
{
  UnknownEncoding *encoding = (UnknownEncoding *) userData;
  PyObject *result;
  int ch;

  result = PyObject_CallFunction(encoding->decoder, "s#s", bytes,
                                 encoding->length[(unsigned char)(*bytes)], 
                                 "strict");
  if (result == NULL)
    return -1;

  if (PyTuple_Check(result) && PyTuple_GET_SIZE(result) == 2 &&
      PyUnicode_Check(PyTuple_GET_ITEM(result, 0))) {
    ch = (int)*PyUnicode_AS_UNICODE(PyTuple_GET_ITEM(result, 0));
  }
  else {
    PyErr_SetString(PyExc_TypeError,
                    "decoder must return a tuple (unicode, integer)");
    ch = -1;
  }
  Py_DECREF(result);
  
  return ch;
}

static void encoding_release(void *userData)
{
  UnknownEncoding *encoding = (UnknownEncoding *) userData;

  Py_DECREF(encoding->decoder);
  PyMem_Del(encoding);
}

static const unsigned char template[] = {
  0x00,  0x01,  0x02,  0x03,  0x04,  0x05,  0x06,  0x07,
  0x08,  0x09,  0x0A,  0x0B,  0x0C,  0x0D,  0x0E,  0x0F,
  0x10,  0x11,  0x12,  0x13,  0x14,  0x15,  0x16,  0x17,
  0x18,  0x19,  0x1A,  0x1B,  0x1C,  0x1D,  0x1E,  0x1F,
  0x20,  0x21,  0x22,  0x23,  0x24,  0x25,  0x26,  0x27,
  0x28,  0x29,  0x2A,  0x2B,  0x2C,  0x2D,  0x2E,  0x2F,
  0x30,  0x31,  0x32,  0x33,  0x34,  0x35,  0x36,  0x37,
  0x38,  0x39,  0x3A,  0x3B,  0x3C,  0x3D,  0x3E,  0x3F,
  0x40,  0x41,  0x42,  0x43,  0x44,  0x45,  0x46,  0x47,
  0x48,  0x49,  0x4A,  0x4B,  0x4C,  0x4D,  0x4E,  0x4F,
  0x50,  0x51,  0x52,  0x53,  0x54,  0x55,  0x56,  0x57,
  0x58,  0x59,  0x5A,  0x5B,  0x5C,  0x5D,  0x5E,  0x5F,
  0x60,  0x61,  0x62,  0x63,  0x64,  0x65,  0x66,  0x67,
  0x68,  0x69,  0x6A,  0x6B,  0x6C,  0x6D,  0x6E,  0x6F,
  0x70,  0x71,  0x72,  0x73,  0x74,  0x75,  0x76,  0x77,
  0x78,  0x79,  0x7A,  0x7B,  0x7C,  0x7D,  0x7E,  0x7F,
  0x80,  0x81,  0x82,  0x83,  0x84,  0x85,  0x86,  0x87,
  0x88,  0x89,  0x8A,  0x8B,  0x8C,  0x8D,  0x8E,  0x8F,
  0x90,  0x91,  0x92,  0x93,  0x94,  0x95,  0x96,  0x97,
  0x98,  0x99,  0x9A,  0x9B,  0x9C,  0x9D,  0x9E,  0x9F,
  0xA0,  0xA1,  0xA2,  0xA3,  0xA4,  0xA5,  0xA6,  0xA7,
  0xA8,  0xA9,  0xAA,  0xAB,  0xAC,  0xAD,  0xAE,  0xAF,
  0xB0,  0xB1,  0xB2,  0xB3,  0xB4,  0xB5,  0xB6,  0xB7,
  0xB8,  0xB9,  0xBA,  0xBB,  0xBC,  0xBD,  0xBE,  0xBF,
  0xC0,  0xC1,  0xC2,  0xC3,  0xC4,  0xC5,  0xC6,  0xC7,
  0xC8,  0xC9,  0xCA,  0xCB,  0xCC,  0xCD,  0xCE,  0xCF,
  0xD0,  0xD1,  0xD2,  0xD3,  0xD4,  0xD5,  0xD6,  0xD7,
  0xD8,  0xD9,  0xDA,  0xDB,  0xDC,  0xDD,  0xDE,  0xDF,
  0xE0,  0xE1,  0xE2,  0xE3,  0xE4,  0xE5,  0xE6,  0xE7,
  0xE8,  0xE9,  0xEA,  0xEB,  0xEC,  0xED,  0xEE,  0xEF,
  0xF0,  0xF1,  0xF2,  0xF3,  0xF4,  0xF5,  0xF6,  0xF7,
  0xF8,  0xF9,  0xFA,  0xFB,  0xFC,  0xFD,  0xFE,  0xFF,
  /* terminator */
  0x00
};

static int expat_UnknownEncodingHandler(void *encodingHandlerData,
                                        const XML_Char *name,
                                        XML_Encoding *info)
{
  PyObject *_u_name, *_s_name;
  PyObject *encoder, *decoder;
  PyObject *result;
  Py_UNICODE unichr;
  int i;
  UnknownEncoding *encoding;

  _u_name = Unicode_FromXMLChar(name);
  if (_u_name == NULL)
    return 0;

  /* Encodings must be ASCII per the XML spec */
  _s_name = PyUnicode_EncodeASCII(PyUnicode_AS_UNICODE(_u_name),
                                  PyUnicode_GET_SIZE(_u_name),
                                  NULL);
  Py_DECREF(_u_name);
  if (_s_name == NULL)
    return 0;

  encoder = PyCodec_Encoder(PyString_AS_STRING(_s_name));
  decoder = PyCodec_Decoder(PyString_AS_STRING(_s_name));
  Py_DECREF(_s_name);
  if (encoder == NULL || decoder == NULL) {
    Py_XDECREF(encoder);
    Py_XDECREF(decoder);
    return 0;
  }

  /* Check if we can use the direct replacement method (8-bit encodings) */
  result = PyObject_CallFunction(decoder, "s#s", template, 256, "replace");
  if (result && PyUnicode_Check(result) && PyUnicode_GET_SIZE(result) == 256) {
    /* we have a valid 8-bit encoding */
    for (i = 0; i < 256; i++) {
      unichr = PyUnicode_AS_UNICODE(result)[i];
      if (unichr == Py_UNICODE_REPLACEMENT_CHARACTER)
        info->map[i] = -1;
      else
        info->map[i] = unichr;
    }
    Py_DECREF(result);
    Py_DECREF(encoder);
    Py_DECREF(decoder);
    return 1;
  }

  /* Use the convert function method (multibyte encodings) */
  if (result == NULL) 
    PyErr_Clear();
  else
    Py_DECREF(result);

  if ((encoding = PyMem_New(UnknownEncoding, 1)) == NULL) {
    Py_DECREF(encoder);
    Py_DECREF(decoder);
    return 0;
  }

  for (unichr = 0; unichr <= 0xFFFD; unichr++) {
    result = PyObject_CallFunction(encoder, "u#s", &unichr, 1, "ignore");
    if (result == NULL) {
      Py_DECREF(encoder);
      Py_DECREF(decoder);
      PyMem_Del(encoding);
      return 0;
    }

    /* treat non-string results as invalid value */
    if (PyString_Check(result)) {
      int c = *PyString_AS_STRING(result);
      int n = PyString_GET_SIZE(result);
      if (n == 1) {
        /* one-to-one replacement */
        info->map[c] = unichr;
      }
      else if (n > 1) {
        /* multibyte replacement */
        info->map[c] = -n;
      }
      encoding->length[c] = n;
    }
    Py_DECREF(result);
  }
  
  /* consume the reference */
  encoding->decoder = decoder;
  info->data = (void *) encoding;
  info->convert = encoding_convert;
  info->release = encoding_release;

  Py_DECREF(encoder);
  return 1;
}


/** External Routines *************************************************/


static int add_state(ExpatParser parser, StateId state)
{
  if (StateTable_AddState(parser->table, state) == 0)
    return 0;
  /* always add transition to PARSE_STREAM_STATE */
  if (StateTable_AddTransition(parser->table, state, PARSE_RESUME_EVENT, 
                               PARSE_STREAM_STATE) == 0)
    return 0;
  return 1;
}

ExpatParser Expat_ParserCreate(void *userState)
{
  ExpatParser parser;

  if (expat_library_error != NULL) {
    PyErr_SetObject(PyExc_RuntimeError, expat_library_error);
    return NULL;
  }

  if ((parser = PyMem_New(struct ExpatParserStruct, 1)) == NULL) {
    PyErr_NoMemory();
    return NULL;
  }

  /* Expat parser (set prior to parse) */
  parser->parser = NULL;

  /* caching of split-names */
  if ((parser->name_cache = PyDict_New()) == NULL) {
    PyMem_Del(parser);
    return NULL;
  }

  if ((parser->interned_unicode = HashTable_New()) == NULL) {
    Py_DECREF(parser->name_cache);
    PyMem_Del(parser);
    return NULL;
  }

  /* character data buffering */
  if ((parser->buffer = PyMem_New(XML_Char, XMLCHAR_BUFSIZ)) == NULL) {
    PyErr_NoMemory();
    HashTable_Del(parser->interned_unicode);
    Py_DECREF(parser->name_cache);
    PyMem_Del(parser);
    return NULL;
  }
  parser->buffer_size = XMLCHAR_BUFSIZ;
  parser->buffer_used = 0;

  /* attribute buffering */
  if ((parser->attrs = PyMem_New(PyObject *, INITIAL_ATTR_SIZE)) == NULL) {
    PyErr_NoMemory();
    HashTable_Del(parser->interned_unicode);
    Py_DECREF(parser->name_cache);
    PyMem_Del(parser);
    return NULL;
  }
  parser->attrs_size = INITIAL_ATTR_SIZE;

  /* xml:space='preserve' state stack */
  if ((parser->xml_space_stack = Stack_New()) == NULL) {
    PyMem_Del(parser->attrs);
    PyMem_Del(parser->buffer);
    HashTable_Del(parser->interned_unicode);
    Py_DECREF(parser->name_cache);
    PyMem_Del(parser);
    return NULL;
  }
  Stack_Push(parser->xml_space_stack, Py_False); /* xml:space='default' */

  /* whitespace preserving state stack */
  if ((parser->preserve_whitespace_stack = Stack_New()) == NULL) {
    Stack_Del(parser->xml_space_stack);
    PyMem_Del(parser->attrs);
    PyMem_Del(parser->buffer);
    HashTable_Del(parser->interned_unicode);
    Py_DECREF(parser->name_cache);
    PyMem_Del(parser);
    return NULL;
  }
  Stack_Push(parser->preserve_whitespace_stack, Py_True);

  /* XPointer match transition event stack */
  if ((parser->xpointer_event_stack = Stack_New()) == NULL) {
    Stack_Del(parser->preserve_whitespace_stack);
    Stack_Del(parser->xml_space_stack);
    PyMem_Del(parser->attrs);
    PyMem_Del(parser->buffer);
    HashTable_Del(parser->interned_unicode);
    Py_DECREF(parser->name_cache);
    PyMem_Del(parser);
    return NULL;
  }

  if ((parser->table = StateTable_New(parser)) == NULL) {
    Stack_Del(parser->xpointer_event_stack);
    Stack_Del(parser->preserve_whitespace_stack);
    Stack_Del(parser->xml_space_stack);
    PyMem_Del(parser->attrs);
    PyMem_Del(parser->buffer);
    HashTable_Del(parser->interned_unicode);
    Py_DECREF(parser->name_cache);
    PyMem_Del(parser);
    return NULL;
  }

  parser->start_document_handler = NULL;
  parser->end_document_handler = NULL;
  parser->start_element_handler = NULL;
  parser->end_element_handler = NULL;
  parser->character_data_handler = NULL;
  parser->processing_instruction_handler = NULL;
  parser->comment_handler = NULL;
  parser->start_namespace_decl_handler = NULL;
  parser->end_namespace_decl_handler = NULL;
  parser->doctype_decl_handler = NULL;
  parser->unparsed_entity_decl_handler = NULL;
  parser->whitespace_rules = NULL;

  parser->userState = userState;

  parser->process_xincludes = 1;

  /* all fields are initialized at this point so it is safe to use
     Expat_ParserFree() to cleanup */

  if (add_state(parser, PARSE_STREAM_STATE) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  if (add_state(parser, START_ELEMENT_CALLBACK) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  if (add_state(parser, END_ELEMENT_CALLBACK) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  if (add_state(parser, START_NS_SCOPE_CALLBACK) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  if (add_state(parser, END_NS_SCOPE_CALLBACK) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  if (add_state(parser, CHARACTER_DATA_CALLBACK) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  if (add_state(parser, PI_CALLBACK) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  if (add_state(parser, COMMENT_CALLBACK) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  if (StateTable_AddTransition(parser->table, START_STATE, PARSE_RESUME_EVENT,
                               PARSE_STREAM_STATE) == 0) {
    Expat_ParserFree(parser);
    return NULL;
  }

  return parser;
}


void Expat_ParserFree(ExpatParser parser)
{
  /* set after creation */
  if (parser->whitespace_rules) {
    freeWhitespaceRules(parser->whitespace_rules);
    parser->whitespace_rules = NULL;
  }

  StateTable_Del(parser->table);
  parser->table = NULL;

  Stack_Del(parser->xpointer_event_stack);
  parser->xpointer_event_stack = NULL;

  Stack_Del(parser->preserve_whitespace_stack);
  parser->preserve_whitespace_stack = NULL;

  Stack_Del(parser->xml_space_stack);
  parser->xml_space_stack = NULL;

  PyMem_Del(parser->attrs);
  parser->attrs = NULL;

  PyMem_Del(parser->buffer);
  parser->buffer = NULL;

  HashTable_Del(parser->interned_unicode);
  Py_DECREF(parser->name_cache);

  PyMem_Del(parser);
}


void Expat_ParserStop(ExpatParser parser)
{
  StateTable_SignalError(parser->table);
}


PyObject *Expat_GetBase(ExpatParser parser)
{
  PyObject *base;

  if (parser->input_source)
    base = parser->input_source->uri;
  else
    base = Py_None;

  Py_INCREF(base);
  return base;
}


int Expat_GetLineNumber(ExpatParser parser)
{
  int line;

  if (parser->parser)
    line = XML_GetCurrentLineNumber(parser->parser);
  else
    line = 0;

  return line;
}


int Expat_GetColumnNumber(ExpatParser parser)
{
  int column;

  if (parser->parser)
    column = XML_GetCurrentColumnNumber(parser->parser);
  else
    column = 0;

  return column;
}


/* returns 1 if parsing, 0 otherwise */
int Expat_GetParsingStatus(ExpatParser parser)
{
  static XML_ParsingStatus status;
  if (parser->parser) {
    XML_GetParsingStatus(parser->parser, &status);
    return (status.parsing == XML_PARSING || status.parsing == XML_SUSPENDED);
  }
  return 0; 
}


/*
  If the name from the document had a prefix, then expat name is:
    namespace-uri + sep + localName + sep + prefix,
  otherwise if there is a default namespace:
    namespace-uri + sep + localName
  lastly, it could just be:
    localName
 */

static int split_name(PyObject *name, 
                      PyObject **namespaceURI, 
                      PyObject **localName,
                      PyObject **qualifiedName, 
                      PyObject **prefix)
{
  int len = PyUnicode_GET_SIZE(name);
  const Py_UNICODE *p = PyUnicode_AS_UNICODE(name);
  int i, j;

  /* scan for beginning of localName */
  for (i = 0; i < len && p[i] != EXPAT_NSSEP; i++);

  if (i == len) {
    /* just a localName, set namespace and prefix to None. */
    *namespaceURI = Py_None;
    *localName = name;
    *qualifiedName = name;
    *prefix = Py_None;
    Py_INCREF(*namespaceURI);
    Py_INCREF(*localName);
    Py_INCREF(*qualifiedName);
    Py_INCREF(*prefix);
    return 1;
  }

  /* found a namespace uri */
  if ((*namespaceURI = PyUnicode_FromUnicode(p, i)) == NULL) {
    return 0;
  }
  i++;

  for (j = i; j < len && p[j] != EXPAT_NSSEP; j++);

  if ((*localName = PyUnicode_FromUnicode(p + i, j - i)) == NULL) {
    Py_DECREF(*namespaceURI);
    return 0;
  }
  j++;

  if (j < len) {
    /* a prefix is given as well, build the qualifiedName */
    if ((*qualifiedName = PyUnicode_FromUnicode(NULL, len - i)) == NULL) {
      Py_DECREF(*namespaceURI);
      Py_DECREF(*localName);
      return 0;
    }

    /* copy the prefix to the qualifiedName string */
    Py_UNICODE_COPY(PyUnicode_AS_UNICODE(*qualifiedName), p + j, len - j);
    /* add the ':' separator */
    PyUnicode_AS_UNICODE(*qualifiedName)[len - j] = (Py_UNICODE) ':';
    /* add the localName after the ':' to finish the qualifiedName */
    Py_UNICODE_COPY(PyUnicode_AS_UNICODE(*qualifiedName) + len - j + 1,
                    PyUnicode_AS_UNICODE(*localName),
                    PyUnicode_GET_SIZE(*localName));

    if ((*prefix = PyUnicode_FromUnicode(p + j, len - j)) == NULL) {
      Py_DECREF(*namespaceURI);
      Py_DECREF(*localName);
      Py_DECREF(*qualifiedName);
      return 0;
    }
  } else {
    /* default namespace, re-use the localName */
    *qualifiedName = *localName;
    *prefix = Py_None;
    Py_INCREF(*qualifiedName);
    Py_INCREF(*prefix);
  }
  
  return 1;
}


int Expat_SplitName(ExpatParser parser, PyObject *name,
                    PyObject **namespaceURI, PyObject **localName,
                    PyObject **qualifiedName, PyObject **prefix)
{
  PyObject *parts;

  parts = PyDict_GetItem(parser->name_cache, name);
  if (parts == NULL) {
    if ((parts = PyTuple_New(4)) == NULL) {
      return 0;
    }

    if (split_name(name, &PyTuple_GET_ITEM(parts, 0),
                   &PyTuple_GET_ITEM(parts, 1),
                   &PyTuple_GET_ITEM(parts, 2),
                   &PyTuple_GET_ITEM(parts, 3)) == 0) {
      Py_DECREF(parts);
      return 0;
    }

    if (PyDict_SetItem(parser->name_cache, name, parts)) {
      Py_DECREF(parts);
      return 0;
    }
    Py_DECREF(parts);
  }

  *namespaceURI = PyTuple_GET_ITEM(parts, 0);
  Py_INCREF(*namespaceURI);

  *localName = PyTuple_GET_ITEM(parts, 1);
  Py_INCREF(*localName);

  *qualifiedName = PyTuple_GET_ITEM(parts, 2);
  Py_INCREF(*qualifiedName);

  if (prefix != NULL) {
    *prefix = PyTuple_GET_ITEM(parts, 3);
    Py_INCREF(*prefix);
  }

  return 1;
}


void Expat_SetStartDocumentHandler(ExpatParser parser,
                                   ExpatStartDocumentHandler handler)
{
  parser->start_document_handler = handler;
}


void Expat_SetEndDocumentHandler(ExpatParser parser,
                                 ExpatEndDocumentHandler handler)
{
  parser->end_document_handler = handler;
}


void Expat_SetStartElementHandler(ExpatParser parser,
                                  ExpatStartElementHandler handler)
{
  /* the state machine, by default, is configured to handle these
   * events and states. */
  StateTable_AddTransition(parser->table, PARSE_STREAM_STATE,
                           START_ELEMENT_EVENT, START_ELEMENT_CALLBACK);
  parser->start_element_handler = handler;
}


void Expat_SetEndElementHandler(ExpatParser parser,
                                ExpatEndElementHandler handler)
{
  /* the state machine, by default, is configured to handle these
   * events and states. */
  StateTable_AddTransition(parser->table, PARSE_STREAM_STATE,
                           END_ELEMENT_EVENT, END_ELEMENT_CALLBACK);
  parser->end_element_handler = handler;
}


void Expat_SetStartNamespaceDeclHandler(ExpatParser parser,
                                        ExpatStartNamespaceDeclHandler handler)
{
  /* the state machine, by default, is configured to handle these
   * events and states. */
  StateTable_AddTransition(parser->table, PARSE_STREAM_STATE, 
                           START_NS_SCOPE_EVENT, START_NS_SCOPE_CALLBACK);
  parser->start_namespace_decl_handler = handler;
}


void Expat_SetEndNamespaceDeclHandler(ExpatParser parser,
                                      ExpatEndNamespaceDeclHandler handler)
{
  /* the state machine, by default, is configured to handle these
   * events and states. */
  StateTable_AddTransition(parser->table, PARSE_STREAM_STATE,
                           END_NS_SCOPE_EVENT, END_NS_SCOPE_CALLBACK);
  parser->end_namespace_decl_handler = handler;
}


void Expat_SetCharacterDataHandler(ExpatParser parser,
                                   ExpatCharacterDataHandler handler)
{
  /* the state machine, by default, is configured to handle these
   * events and states. */
  StateTable_AddTransition(parser->table, PARSE_STREAM_STATE,
                           CHARACTER_DATA_EVENT, CHARACTER_DATA_CALLBACK);
  parser->character_data_handler = handler;
}


void Expat_SetProcessingInstructionHandler(ExpatParser parser,
                                           ExpatProcessingInstructionHandler handler)
{
  /* the state machine, by default, is configured to handle these
   * events and states. */
  StateTable_AddTransition(parser->table, PARSE_STREAM_STATE, PI_EVENT,
                           PI_CALLBACK);
  parser->processing_instruction_handler = handler;
}


void Expat_SetCommentHandler(ExpatParser parser,
                             ExpatCommentHandler handler)
{
  /* the state machine, by default, is configured to handle these
   * events and states. */
  StateTable_AddTransition(parser->table, PARSE_STREAM_STATE, 
                           COMMENT_EVENT, COMMENT_CALLBACK);
  parser->comment_handler = handler;
}


void Expat_SetDoctypeDeclHandler(ExpatParser parser,
                                 ExpatDoctypeDeclHandler handler)
{
  parser->doctype_decl_handler = handler;
}


void Expat_SetUnparsedEntityDeclHandler(ExpatParser parser,
                                        ExpatUnparsedEntityDeclHandler handler)
{
  parser->unparsed_entity_decl_handler = handler;
}


void Expat_SetXIncludeProcessing(ExpatParser parser, int do_xip)
{
  /* do not allowing changing after parsing has begun */
  if (parser->parser == NULL) {
    /* normalize value to 1 or 0 */
    parser->process_xincludes = do_xip ? 1 : 0;
  }
}


int Expat_GetXIncludeProcessing(ExpatParser parser)
{
  return parser->process_xincludes;
}


int Expat_SetWhitespaceRules(ExpatParser parser, PyObject *stripElements)
{
  /* do not allowing changing after parsing has begun */
  if (parser->parser == NULL) {
    WhitespaceRules *rules;
    if (stripElements == NULL) {
      rules = NULL;
    } else {
      rules = createWhitespaceRules(stripElements);
      if (rules == NULL) {
        return 0;
      }
    }
    if (parser->whitespace_rules != NULL) {
      freeWhitespaceRules(parser->whitespace_rules);
    }
    parser->whitespace_rules = rules;
  }
  return 1;
}


int Expat_ParseDocument(ExpatParser parser, PyObject *source, 
                        int doExternalEntityParsing)
{
  int success;

  parser->input_source = createInputSource(source);
  if (parser->input_source == NULL) {
    return 0;
  }

  parser->parser = createExpatParser(parser);
  if (parser->parser == NULL) {
    freeInputSource(parser->input_source);
    return 0;
  }
  setExpatHandlers(parser);

  if (doExternalEntityParsing) {
    XML_SetParamEntityParsing(parser->parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
  }

  StateTable_SetState(parser->table, START_STATE);

  if (parser->start_document_handler) 
    parser->start_document_handler(parser->userState);

  success = doParse(parser);

  if (success && parser->end_document_handler) 
    parser->end_document_handler(parser->userState);

  freeInputSource(parser->input_source);
  parser->input_source = NULL;

  XML_ParserFree(parser->parser);
  parser->parser = NULL;
    
  return success;
}

/* copied from xmlparse.c
   needed for entity parsing to recognize XML namespace
*/
static const XML_Char implicitContext[] = {
  'x', 'm', 'l', '=', 'h', 't', 't', 'p', ':', '/', '/',
  'w', 'w', 'w', '.', 'w', '3', '.', 'o', 'r', 'g', '/',
  'X', 'M', 'L', '/', '1', '9', '9', '8', '/',
  'n', 'a', 'm', 'e', 's', 'p', 'a', 'c', 'e', '\0'
};

int Expat_ParseEntity(ExpatParser parser, PyObject *source)
{
  XML_Parser expat_parser;
  int success;

  parser->input_source = createInputSource(source);
  if (parser->input_source == NULL) 
    return 0;

  expat_parser = createExpatParser(parser);
  if (expat_parser == NULL) {
    freeInputSource(parser->input_source);
    return 0;
  }

  parser->parser = XML_ExternalEntityParserCreate(expat_parser, 
                                                  implicitContext,
                                                  NULL);
  if (parser->parser == NULL) {
    freeInputSource(parser->input_source);
    XML_ParserFree(expat_parser);
    return 0;
  }
  setExpatHandlers(parser);

  StateTable_SetState(parser->table, START_STATE);

  if (parser->start_document_handler) 
    parser->start_document_handler(parser->userState);

  success = doParse(parser);

  if (success && parser->end_document_handler) 
    parser->end_document_handler(parser->userState);

  freeInputSource(parser->input_source);
  parser->input_source = NULL;

  XML_ParserFree(parser->parser);
  parser->parser = NULL;

  XML_ParserFree(expat_parser);
    
  return success;
}


static Expat_APIObject Expat_API = {
  Expat_ParserCreate,
  Expat_ParserFree,
  Expat_ParseDocument,
  Expat_ParseEntity,
  Expat_ParserStop,
  Expat_GetBase,
  Expat_GetLineNumber,
  Expat_GetColumnNumber,
  Expat_SplitName,
  Expat_SetStartDocumentHandler,
  Expat_SetEndDocumentHandler,
  Expat_SetStartElementHandler,
  Expat_SetEndElementHandler,
  Expat_SetCharacterDataHandler,
  Expat_SetProcessingInstructionHandler,
  Expat_SetCommentHandler,
  Expat_SetStartNamespaceDeclHandler,
  Expat_SetEndNamespaceDeclHandler,
  Expat_SetDoctypeDeclHandler,
  Expat_SetUnparsedEntityDeclHandler,
  Expat_SetXIncludeProcessing,
  Expat_SetWhitespaceRules,
};

int DomletteExpat_Init(PyObject *module)
{
  PyObject *import;
  XML_Expat_Version version = XML_ExpatVersionInfo();
  const XML_Feature *features = XML_GetFeatureList();
  const XML_Feature *f;
  PyObject *capi;

  if ((PycString_IMPORT) == NULL)
    return -1;

  encoding_string = PyString_FromString("encoding");
  if (encoding_string == NULL) return -1;

  uri_string = PyString_FromString("uri");
  if (uri_string == NULL) return -1;

  stream_string = PyString_FromString("stream");
  if (stream_string == NULL) return -1;

  asterisk_string = PyUnicode_DecodeASCII("*", 1, NULL);
  if (asterisk_string == NULL) return -1;

  space_string = PyUnicode_DecodeASCII("space", 5, NULL);
  if (space_string == NULL) return -1;

  preserve_string = PyUnicode_DecodeASCII("preserve", 8, NULL);
  if (preserve_string == NULL) return -1;

  default_string = PyUnicode_DecodeASCII("default", 7, NULL);
  if (default_string == NULL) return -1;

  xinclude_hint_string = PyString_FromString("XINCLUDE");
  if (xinclude_hint_string == NULL) return -1;

  external_entity_hint_string = PyString_FromString("EXTERNAL ENTITY");
  if (external_entity_hint_string == NULL) return -1;

  xpointer_close_event = PyInt_FromLong(XPTR_CLOSE_EVENT);
  if (xpointer_close_event == NULL) return -1;

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

  /* verify Expat linkage due to late binding on Linux */
  expat_library_error = NULL;

  /* ensure that we're going to be using the proper Expat functions */
  if (version.major != XML_MAJOR_VERSION || 
      version.minor != XML_MINOR_VERSION ||
      version.micro != XML_MICRO_VERSION) {
    expat_library_error = 
      PyString_FromFormat("Incompatible Expat library found; "          \
                          "version mismatch (expected %d.%d.%d, "       \
                          "found %d.%d.%d)", XML_MAJOR_VERSION, 
                          XML_MINOR_VERSION, XML_MICRO_VERSION,
                          version.major, version.minor, version.micro);
    if (expat_library_error == NULL) return -1;
    return PyErr_Warn(PyExc_RuntimeWarning, 
                      PyString_AS_STRING(expat_library_error));
  }

  /* check Expat features that we require depending on how we were
     compiled.  Unfortunately, we cannot test for namespace support.
  */
#ifdef XML_DTD
  for (f = features; f->feature != XML_FEATURE_DTD; f++) {
    if (features->feature == XML_FEATURE_END) {
      expat_library_error = 
        PyString_FromString("Incompatible Expat library found; "        \
                            "missing feature XML_DTD");
      if (expat_library_error == NULL) return -1;
      return PyErr_Warn(PyExc_RuntimeWarning, 
                        PyString_AS_STRING(expat_library_error));
    }
  }
#endif
#ifdef XML_UNICODE
  for (f = features; f->feature != XML_FEATURE_UNICODE; f++) {
    if (features->feature == XML_FEATURE_END) {
      expat_library_error = 
        PyString_FromString("Incompatible Expat library found; "        \
                            "missing feature XML_UNICODE");
      if (expat_library_error == NULL) return -1;
      return PyErr_Warn(PyExc_RuntimeWarning, 
                        PyString_AS_STRING(expat_library_error));
    }
  }
#endif

  PyModule_AddIntConstant(module, "PARSE_STREAM_STATE", PARSE_STREAM_STATE);
  PyModule_AddIntConstant(module, "XPTR_START_STATE", XPTR_START_STATE);
  PyModule_AddIntConstant(module, "XPTR_ELEMENT_MATCH", ELEMENT_MATCH);
  PyModule_AddIntConstant(module, "XPTR_ELEMENT_COUNT", ELEMENT_COUNT);
  PyModule_AddIntConstant(module, "XPTR_ATTRIBUTE_MATCH", ATTRIBUTE_MATCH);

  /* Export C API - done last to serve as a cleanup function as well */
  capi = PyCObject_FromVoidPtr((void *)&Expat_API, NULL);
  if (capi == NULL) return -1;
  PyModule_AddObject(module, "Expat_CAPI", capi);

  return 0;
}


void DomletteExpat_Fini(void)
{
  Py_DECREF(encoding_string);
  Py_DECREF(uri_string);
  Py_DECREF(stream_string);
  Py_DECREF(asterisk_string);
  Py_DECREF(space_string);
  Py_DECREF(preserve_string);
  Py_DECREF(default_string);
  Py_DECREF(xinclude_hint_string);
  Py_DECREF(external_entity_hint_string);
  Py_DECREF(xpointer_close_event);
  Py_DECREF(absolutize_function);
  Py_XDECREF(expat_library_error);
}
