The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*****************************************************************
** ExpatXS.xs
**
** This file is derived from the original Expat.xs file written 
** for XML::Parser by Larry Wall and Clark Cooper.
**
** The file has been modified for purposes of XML::SAX::ExpatXS 
** by Matt Sergeant and Petr Cimprich.
**
** Copyright 1998-2007 by authors of respective parts.
** All rights reserved.
**
** This program is free software; you can redistribute it and/or
** modify it under the same terms as Perl itself.
**
*/


#include <expat.h>

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#undef convert

#include "patchlevel.h"
#include "encoding.h"

#include <string.h>

/* Version 5.005_5x (Development version for 5.006) doesn't like sv_...
   anymore, but 5.004 doesn't know about PL_sv..
   Don't want to push up required version just for this. */

#if PATCHLEVEL < 5
#define PL_sv_undef sv_undef
#define PL_sv_no    sv_no
#define PL_sv_yes   sv_yes
#define PL_na       na
#endif

#define BUFSIZE 32768

#define NSDELIM -1

typedef struct {
  SV* self_sv;
  XML_Parser p;

  AV* context;
  AV *ns_stack;

  int skip_until;
  int feat_join;
  int feat_nsatts;
  int feat_locator;
  int feat_recstring;
  int feat_xmlns;
  int feat_perlxmlns;
  int feat_entgen;
  int feat_entpar;

  SV *recstring;
  char * delim;
  STRLEN delimlen;

  unsigned ns:1;
  unsigned no_expand:1;

  /* Callback handlers */
  SV* start_sv;
  SV* end_sv;
  SV* char_sv;
  SV* comment_sv;

  HV* atts;
  int atts_ready;

  HV* locator_hv;
  HV* extern_hv;  
  SV* chrbuffer;
  HV* chr_hv;

} CallbackVector;


static HV* EncodingTable = NULL;

static XML_Char nsdelim[] = {NSDELIM, '\0'};

static char *QuantChar[] = {"", "?", "*", "+"};

static U32 PrefixHash; /* pre-computed */
static U32 NamespaceURIHash;
static U32 NameHash;
static U32 LocalNameHash;
static U32 AttributesHash;
static U32 ValueHash;
static U32 DataHash;
static U32 TargetHash;
static U32 VersionHash;
static U32 XMLVersionHash;
static U32 EncodingHash;
static U32 PublicIdHash;
static U32 SystemIdHash;

static SV *empty_sv;

/* These are few macros used within C functions */

/* Macro to generate external entity name key */
#define xse_extern_ent_key(key, base, sysid, pubid) \
key = strncat(strncat(strncpy(key, base ? base : "", 299), \
sysid ? sysid : "", 299), pubid ? pubid : "", 299);

/* Macro to emit characters */
#define xse_characters(userData, buffer) \
if (SvCUR(buffer) > 0) {\
    sendCharacterData(userData, buffer);\
    sv_setpv(buffer, "");\
}

/* Macro to determine current end position */
#define xse_get_end_position(p, ln, cl, string, len) \
ln = XML_GetCurrentLineNumber(p); \
cl = XML_GetCurrentColumnNumber(p); \
for (i = 0; i < len; i++) { \
  if (string[i] < 0xffffff80 || string[i] > 0xffffffbf) cl++; \
  if (string[i] == '\n') {if (i < len - 1) {ln++; cl=0;}} \
};

/* Forward declarations */

#if PATCHLEVEL < 5 && SUBVERSION < 5

/* ================================================================
** This is needed where the length is explicitly given. The expat
** library may sometimes give us zero-length strings. Perl's newSVpv
** interprets a zero length as a directive to do a strlen. This
** function is used when we want to force length to mean length, even
** if zero.
*/

static SV *
newSVpvn(char *s, STRLEN len)
{
  register SV *sv;

  sv = newSV(0);
  sv_setpvn(sv, s, len);
  return sv;
}  /* End newSVpvn */

#define ERRSV GvSV(errgv)
#endif

#ifdef SvUTF8_on

static SV *
newUTF8SVpv(char *s, STRLEN len) {
  register SV *sv;

  sv = newSVpv(s, len);
  SvUTF8_on(sv);
  return sv;
}  /* End new UTF8SVpv */

static SV *
newUTF8SVpvn(char *s, STRLEN len) {
  register SV *sv;

  sv = newSV(0);
  sv_setpvn(sv, s, len);
  SvUTF8_on(sv);
  return sv;
}

#else  /* SvUTF8_on not defined */

#define newUTF8SVpv newSVpv
#define newUTF8SVpvn newSVpvn
#define SvUTF8_on(a) (a)

#endif

static void*
mymalloc(size_t size) {
#ifndef LEAKTEST
  return safemalloc(size);
#else
  return safexmalloc(328,size);
#endif
}

static void*
myrealloc(void *p, size_t s) {
#ifndef LEAKTEST
  return saferealloc(p, s);
#else
  return safexrealloc(p, s);
#endif
}

static void
myfree(void *p) {
  Safefree(p);
}

static XML_Memory_Handling_Suite ms = {mymalloc, myrealloc, myfree};

static int 
get_feature(CallbackVector *cbv, char *fname)
{
    SV ** aux = hv_fetch((HV*)SvRV(cbv->self_sv), "Features", 8, 0);
    if (! aux) {
        return 0;
    } else {
        aux = hv_fetch((HV*)SvRV(*aux), fname, strlen(fname), 0);
        if (! aux) {
            return 0;
        } else {
            return SvIV(*aux);
        }
    }
}

static HV*
add_ns_mapping(AV *ns_stack, char *prefix, char *uri)
{
    HV *ret;
    SV *sv_prefix, *sv_uri;
    AV *new_entry;

    /* warn("add_ns_mapping(%s => %s)\n", prefix, uri); */

    sv_prefix = (prefix == NULL) ? SvREFCNT_inc(empty_sv) 
                                 : newUTF8SVpv(prefix, strlen(prefix));
    sv_uri = (uri == NULL) ? SvREFCNT_inc(empty_sv)
                           : newUTF8SVpv(uri, strlen(uri));

    ret = newHV();
    hv_store(ret, "Prefix", 6, sv_prefix, PrefixHash);
    hv_store(ret, "NamespaceURI", 12, sv_uri, NamespaceURIHash);

    new_entry = newAV();
    av_push(new_entry, newSVsv(sv_prefix));
    av_push(new_entry, newSVsv(sv_uri));

    /* store at front of array for faster access */
    av_unshift(ns_stack, 1);
    av_store(ns_stack, 0, newRV_noinc((SV*)new_entry));

    return ret;
}

static void
del_ns_mapping(AV *ns_stack, char *prefix)
{
    SV *entry;
    I32 key = 0;

    /* warn("del_ns_mapping: %s\n", prefix); */

    entry = av_shift(ns_stack);
    if (entry) SvREFCNT_dec(entry);
}

static AV*
get_ns_mapping(AV *ns_stack, char *prefix, char *uri)
{
    SV **entry;
    I32 key = 0;

    for (key = 0; key <= av_len(ns_stack); key++) {
        entry = av_fetch(ns_stack, key, 0);
        if (entry && *entry && SvOK(*entry)) {
            SV **svthing = av_fetch((AV*)SvRV(*entry), prefix == NULL ? 1 : 0, 0);
            if (svthing && *svthing && (strcmp(SvPV(*svthing, PL_na), prefix == NULL ? uri : prefix) == 0)) {
                return (AV*)SvRV(*entry);
            }
        }
    }

    return NULL;
}

static void
append_error(XML_Parser parser, char * err)
{
  dSP;
  CallbackVector * cbv;
  HV * exc = newHV();
  SV **public;
  SV **system;	  
  char *msg;

  cbv = (CallbackVector*) XML_GetUserData(parser);

  if (! err)
       err = (char *) XML_ErrorString(XML_GetErrorCode(parser));

  msg = (char *)mymalloc(strlen(err) + 50);
  sprintf(msg, "%s at line %d, column %d, byte %d",
          err,
          (int)XML_GetCurrentLineNumber(parser),
          (int)XML_GetCurrentColumnNumber(parser)+1,
          (int)XML_GetCurrentByteIndex(parser));

  public = hv_fetch(cbv->locator_hv, "PublicId", 8, 0);
  system = hv_fetch(cbv->locator_hv, "SystemId", 8, 0);

  hv_store(exc, "PublicId", 8, 
           public ? *public : SvREFCNT_inc(empty_sv), PublicIdHash);
  hv_store(exc, "SystemId", 8, 
	   system ? *system : SvREFCNT_inc(empty_sv), SystemIdHash);
  hv_store(exc, "Message", 7, newUTF8SVpv((char*)msg, 0), 0);
  hv_store(exc, "Exception", 9, newUTF8SVpv((char*)err, 0), 0);
  hv_store(exc, "LineNumber", 10, 
           newSViv((IV)XML_GetCurrentLineNumber(parser)), 0);
  hv_store(exc, "ColumnNumber", 12, 
           newSViv((IV)XML_GetCurrentColumnNumber(parser)+1), 0);

  hv_store((HV*)SvRV(cbv->self_sv), "ErrorMessage", 12, 
           newUTF8SVpv((char*)msg, 0), 0);

  ENTER;
  SAVETMPS;
  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(newRV_noinc((SV*)exc));
  PUTBACK;
  perl_call_method("fatal_error", G_DISCARD);
  FREETMPS;
  LEAVE;

  myfree(msg);

}  /* End append_error */


static SV *
generate_model(XML_Content *model) {
  HV * hash = newHV();
  SV * obj = newRV_noinc((SV *) hash);

  sv_bless(obj, gv_stashpv("XML::SAX::ExpatXS::ContentModel", 1));

  hv_store(hash, "Type", 4, newSViv(model->type), 0);
  if (model->quant != XML_CQUANT_NONE) {
    hv_store(hash, "Quant", 5, newSVpv(QuantChar[model->quant], 1), 0);
  }

  switch(model->type) {
  case XML_CTYPE_NAME:
    hv_store(hash, "Tag", 3, newSVpv((char *)model->name, 0), 0);
    break;

  case XML_CTYPE_MIXED:
  case XML_CTYPE_CHOICE:
  case XML_CTYPE_SEQ:
    if (model->children && model->numchildren)
      {
    AV * children = newAV();
    int i;

    for (i = 0; i < model->numchildren; i++) {
      av_push(children, generate_model(&model->children[i]));
    }

    hv_store(hash, "Children", 8, newRV_noinc((SV *) children), 0);
      }
    break;
  }

  return obj;
}
  /* End generate_model */

static int
parse_stream(XML_Parser parser, SV * ioref)
{
  dSP;
  SV *      tbuff;
  SV *      tsiz;
  char *    linebuff;
  STRLEN    lblen;
  STRLEN    br = 0;
  int       buffsize;
  int       done = 0;
  int       ret = 1;
  char *    msg = NULL;
  CallbackVector * cbv;
  char      *buff = (char *) 0;

  cbv = (CallbackVector*) XML_GetUserData(parser);

  ENTER;
  SAVETMPS;

  if (cbv->delim) {
    int cnt;
    SV *tline;

    PUSHMARK(SP);
    XPUSHs(ioref);
    PUTBACK ;

    cnt = perl_call_method("getline", G_SCALAR);

    SPAGAIN;

    if (cnt != 1)
      croak("getline method call failed");

    tline = POPs;

    if (! SvOK(tline)) {
      lblen = 0;
    }
    else {
      char *    chk;
      linebuff = SvPV(tline, lblen);
      chk = &linebuff[lblen - cbv->delimlen - 1];

      if (lblen > cbv->delimlen + 1
      && *chk == *cbv->delim
      && chk[cbv->delimlen] == '\n'
      && strnEQ(++chk, cbv->delim + 1, cbv->delimlen - 1))
    lblen -= cbv->delimlen + 1;
    }

    PUTBACK ;
    buffsize = lblen;
    done = lblen == 0;
  }
  else {
    tbuff = newSV(0);
    tsiz = newSViv(BUFSIZE);
    buffsize = BUFSIZE;
  }

  while (! done)
    {
      char *buffer = XML_GetBuffer(parser, buffsize);

      if (! buffer)
    croak("Ran out of memory for input buffer");

      SAVETMPS;

      if (cbv->delim) {
    Copy(linebuff, buffer, lblen, char);
    br = lblen;
    done = 1;
      }
      else {
    int cnt;
    SV * rdres;
    char * tb;

    PUSHMARK(SP);
    EXTEND(SP, 3);
    PUSHs(ioref);
    PUSHs(tbuff);
    PUSHs(tsiz);
    PUTBACK ;

    cnt = perl_call_method("read", G_SCALAR);

    SPAGAIN ;

    if (cnt != 1)
      croak("read method call failed");

    rdres = POPs;

    if (! SvOK(rdres))
      croak("read error");

    tb = SvPV(tbuff, br);
    if (br > 0)
      Copy(tb, buffer, br, char);
    else
      done = 1;

    PUTBACK ;
      }

      ret = XML_ParseBuffer(parser, br, done);

      SPAGAIN; /* resync local SP in case callbacks changed global stack */

      if (! ret)
    break;

      FREETMPS;
    }

  if (! ret)
    append_error(parser, msg);

  if (! cbv->delim) {
    SvREFCNT_dec(tsiz);
    SvREFCNT_dec(tbuff);
  }

  FREETMPS;
  LEAVE;

  return ret;
}  /* End parse_stream */

static HV *
gen_ns_node(const char * name, AV * ns_stack)
{
  char *pos = strchr(name, NSDELIM);
  HV *ret = newHV();

  if (pos && pos > name)
  {
      SV *new_name;
      SV *uri = newUTF8SVpv((char *)name, pos - name);
      AV *ns_entry = get_ns_mapping(ns_stack, NULL, SvPV(uri, PL_na));
      SV **prefix = av_fetch(ns_entry, 0, 0);

      if (SvOK(*prefix)) {
          /* generate Name = prefix:localname */
          if (SvCUR(*prefix)) {
              char *localname = pos;
              localname++;
              new_name = newSVsv(*prefix);
              sv_catpvn(new_name, ":", 1);
              sv_catpv(new_name, localname);
              SvUTF8_on(new_name);
          }
          else {
              /* xmlns default */
              char *localname = pos;
              localname++;
              new_name = newUTF8SVpv(localname, 0);
          }
      }
      else {
          new_name = newUTF8SVpv((char *)name, 0);
      }

      hv_store(ret, "Name", 4, new_name, NameHash);
      hv_store(ret, "Prefix", 6, newSVsv(*prefix), PrefixHash);
      hv_store(ret, "NamespaceURI", 12, uri, NamespaceURIHash);
      hv_store(ret, "LocalName", 9, newUTF8SVpv((char *)++pos, 0), LocalNameHash);
  }
  else {
    SV *svname = newUTF8SVpv((char *)name, 0);
    hv_store(ret, "Name", 4, svname, NameHash);
    hv_store(ret, "Prefix", 6, SvREFCNT_inc(empty_sv), PrefixHash);
    hv_store(ret, "NamespaceURI", 12, SvREFCNT_inc(empty_sv), NamespaceURIHash);
    hv_store(ret, "LocalName", 9, SvREFCNT_inc(svname), LocalNameHash);
  }

  return ret;
}  /* End gen_ns_node */

static void
sendCharacterData(void *userData, SV *buffer)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;
  SV *data;

  if (cbv->feat_locator && !cbv->feat_join)
    XML_DefaultCurrent(cbv->p);

  /* warn("char refcnt: %d\n", SvREFCNT(cbv->chr_hv)); */
  if (SvREFCNT(cbv->chr_hv) == 1) {
      SvREFCNT_inc(cbv->chr_hv);

  } else {
      cbv->chr_hv = newHV();
  }

  /* can't be the same struct as buffer */
  data = newSVsv(buffer);

  ENTER;
  SAVETMPS;

  hv_store(cbv->chr_hv, "Data", 4, data, DataHash);

  PUSHMARK(sp);
  EXTEND(sp, 1);
  /* PUSHs(cbv->self_sv); */
  PUSHs(sv_2mortal(newRV_noinc((SV*)cbv->chr_hv)));
  PUTBACK;
  perl_call_sv(cbv->char_sv, G_DISCARD);

  FREETMPS;
  LEAVE;

  /* warn("leaving ch: %d\n", SvREFCNT((SV*)cvbv->chr_hv)); */
} /* End sendCharacterData */

static void
characterData(void *userData, const char *s, int len)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;

  /* joining character data or not */
  if (cbv->feat_join) {
    sv_catsv(cbv->chrbuffer, sv_2mortal(newUTF8SVpv((char *)s, len)));
    if (cbv->feat_locator) XML_DefaultCurrent(cbv->p);

  } else {
    sendCharacterData(userData, sv_2mortal(newUTF8SVpv((char *)s, len)));
  }

}  /* End characterData */

static void
startElement(void *userData, const char *name, const char **atts)
{
    dSP;
    CallbackVector* cbv = (CallbackVector*) userData;
    SV ** pcontext;
    HV *node;
    SV *element;

    xse_characters(userData, cbv->chrbuffer);
    if (cbv->feat_locator) XML_DefaultCurrent(cbv->p);

    if (!cbv->atts_ready) cbv->atts = newHV();

    node = gen_ns_node(name, cbv->ns_stack);

    while (*atts)
    {
        HV * attname;
        SV *keyname;
        char *key;
        STRLEN klen;
        char  *pos = strchr(*atts, NSDELIM);
        key = (char *)*atts;

        attname = gen_ns_node(key, cbv->ns_stack);

        atts++;
        if (*atts) {
            hv_store(attname, "Value", 5, newUTF8SVpv((char*)*atts++,0), ValueHash);
        }

        keyname = newUTF8SVpv("{", 1);
        if (pos && pos > key) {
            sv_catpvn(keyname, key, pos - key);
	    // -pc -
            sv_catpvn(keyname, "}", 1);
            sv_catpv(keyname, strchr(key, NSDELIM)+1);

        }
        else {
            sv_catpvn(keyname, "}", 1);
            sv_catpv(keyname, key);
        }
        hv_store_ent(cbv->atts, keyname, newRV_noinc((SV*)attname), 0);
        SvREFCNT_dec(keyname);
    }
    hv_store(node, "Attributes", 10, newRV_noinc((SV*)cbv->atts), AttributesHash);

    ENTER;
    SAVETMPS;

    element = newRV_noinc((SV*)node);

    PUSHMARK(sp);
    EXTEND(sp, 1);
    /* PUSHs(cbv->self_sv); */
    PUSHs(element);
    PUTBACK;
    perl_call_sv(cbv->start_sv, G_DISCARD);

    FREETMPS;
    LEAVE;

    av_push(cbv->context, element);
    cbv->atts_ready = 0;

  /* warn("leaving: %d\n", SvREFCNT((SV*)element)); */
} /* End startElement */

static void
endElement(void *userData, const char *name)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;
  SV *top;
  HV *node;
  HV *end_node;
  HE *next;

  xse_characters(userData, cbv->chrbuffer);
  if (cbv->feat_locator) XML_DefaultCurrent(cbv->p);

  top = av_pop(cbv->context);

  ENTER;
  SAVETMPS;

  node = (HV*)SvRV(top);
  /* warn("startEl refcnt: %d\n", SvREFCNT(node)); */

  if (SvREFCNT(node) == 1) {
      /* reusing the same struct as in start_element */
      end_node = node;
      hv_delete(end_node, "Attributes", 10, G_DISCARD);
      SvREFCNT_inc(node);

  } else {
      /* copy the node (can't be the same struct as in start_element */
      end_node = newHV();

      hv_iterinit(node);
      while (next = hv_iternext(node)) {
          U32 keylen;
          char *key = hv_iterkey(next, &keylen);
          SV *value = hv_iterval(node, next);
          if (strncmp(key, "Attributes", 10) != 0) {
              // copy everything except attributes 
              hv_store(end_node, key, keylen, newSVsv(value), 0);
          }
      }
  }

  PUSHMARK(sp);
  EXTEND(sp, 1);
  /* PUSHs(cbv->self_sv); */
  PUSHs(sv_2mortal(newRV_noinc((SV*)end_node)));
  PUTBACK;
  perl_call_sv(cbv->end_sv, G_DISCARD);

  FREETMPS;
  LEAVE;

  SvREFCNT_dec(top);
}  /* End endElement */

static void
processingInstruction(void *userData, const char *target, const char *data)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;
  HV *thing = newHV();

  xse_characters(userData, cbv->chrbuffer);
  if (cbv->feat_locator) XML_DefaultCurrent(cbv->p);

  hv_store(thing, "Target", 6, newUTF8SVpv((char*)target, 0), TargetHash);
  if (data)
    hv_store(thing, "Data", 4, newUTF8SVpv((char*)data, 0), DataHash);

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 3);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)thing)));
  PUTBACK;
  perl_call_method("processing_instruction", G_DISCARD);

  FREETMPS;
  LEAVE;

}  /* End processingInstruction */

static void
commenthandle(void *userData, const char *string)
{
  dSP;
  CallbackVector * cbv = (CallbackVector*) userData;
  HV *thing = newHV();

  xse_characters(userData, cbv->chrbuffer);

  hv_store(thing, "Data", 4, newUTF8SVpv((char*)string, 0), DataHash);

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 1);
  /* PUSHs(cbv->self_sv); */
  PUSHs(sv_2mortal(newRV_noinc((SV*)thing)));
  PUTBACK;
  perl_call_sv(cbv->comment_sv, G_DISCARD);
  /* perl_call_method("comment", G_DISCARD); */

  FREETMPS;
  LEAVE;

}  /* End commenthandler */

static void
startCdata(void *userData)
{
    dSP;
    CallbackVector* cbv = (CallbackVector*) userData;

    xse_characters(userData, cbv->chrbuffer);

    ENTER;
    SAVETMPS;

    PUSHMARK(sp);
    XPUSHs(cbv->self_sv);
    PUTBACK;
    perl_call_method("start_cdata", G_DISCARD);

    FREETMPS;
    LEAVE;
}  /* End startCdata */

static void
endCdata(void *userData)
{
    dSP;
    CallbackVector* cbv = (CallbackVector*) userData;

    xse_characters(userData, cbv->chrbuffer);

    ENTER;
    SAVETMPS;

    PUSHMARK(sp);
    XPUSHs(cbv->self_sv);
    PUTBACK;
    perl_call_method("end_cdata", G_DISCARD);

    FREETMPS;
    LEAVE;
}  /* End endCdata */

static void
nsStart(void *userdata, const XML_Char *prefix, const XML_Char *uri){
  dSP;
  CallbackVector* cbv = (CallbackVector*) userdata;
  HV *nsatt = newHV();

  xse_characters(userdata, cbv->chrbuffer);
  if (cbv->feat_locator) XML_DefaultCurrent(cbv->p);	

  /* NS declarations are reported as common attributes or not */
  if (cbv->feat_nsatts) {
    char *keyname;

    keyname = (char *) mymalloc(prefix ? (strlen(prefix) + 37) : 37);

    if (!cbv->atts_ready) {
      cbv->atts = newHV();
      cbv->atts_ready = 1;
    }     

    if (prefix) {
      char *a_name;

      a_name = (char *) mymalloc(strlen(prefix) + 7);
      strcpy(a_name, "xmlns:");
      strcat(a_name, prefix);

      strcpy(keyname, (cbv->feat_perlxmlns || cbv->feat_xmlns) 
             ? "{http://www.w3.org/2000/xmlns/}" : "{}");

      hv_store(nsatt, "Name", 4, newUTF8SVpv(a_name, strlen(a_name)), NameHash);
      hv_store(nsatt, "Prefix", 6, newUTF8SVpv("xmlns", 5), PrefixHash);
      hv_store(nsatt, "LocalName", 9, 
               newUTF8SVpv((char*)prefix, strlen(prefix)), LocalNameHash);
      hv_store(nsatt, "NamespaceURI", 12, (cbv->feat_perlxmlns || cbv->feat_xmlns) ?
               newUTF8SVpv("http://www.w3.org/2000/xmlns/", 29) 
               : SvREFCNT_inc(empty_sv), NamespaceURIHash);

      myfree(a_name);

    } else {

      strcpy(keyname, cbv->feat_xmlns ? "{http://www.w3.org/2000/xmlns/}" : "{}");

      hv_store(nsatt, "Name", 4, newUTF8SVpv("xmlns", 5), NameHash);
      hv_store(nsatt, "Prefix", 6, SvREFCNT_inc(empty_sv), PrefixHash);
      hv_store(nsatt, "LocalName", 9, newUTF8SVpv("xmlns", 5), LocalNameHash);
      hv_store(nsatt, "NamespaceURI", 12, cbv->feat_xmlns ?
               newUTF8SVpv("http://www.w3.org/2000/xmlns/", 29) 
               : SvREFCNT_inc(empty_sv), NamespaceURIHash);
    }

    strcat(keyname, prefix ? prefix : "xmlns");

    hv_store(nsatt, "Value", 5, 
             uri ? newUTF8SVpv((char*)uri, strlen(uri)) : SvREFCNT_inc(empty_sv), 
             ValueHash);

    hv_store(cbv->atts, keyname, strlen(keyname), newRV_noinc((SV*)nsatt), 0);

    myfree(keyname);
  }

  ENTER;
  SAVETMPS;
  
  PUSHMARK(sp);
  EXTEND(sp, 3);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)add_ns_mapping(cbv->ns_stack, (char *)prefix, (char *)uri))));
  PUTBACK;
  perl_call_method("start_prefix_mapping", G_DISCARD);
  
  FREETMPS;
  LEAVE;
  
}  /* End nsStart */

static void
nsEnd(void *userdata, const XML_Char *prefix) {
  dSP;
  CallbackVector* cbv = (CallbackVector*) userdata;
  HV *node = newHV();

  xse_characters(userdata, cbv->chrbuffer);
  if (cbv->feat_locator) XML_DefaultCurrent(cbv->p);	

  hv_store(node, "Prefix", 6, (prefix == NULL) ? SvREFCNT_inc(empty_sv) 
                              : newUTF8SVpv((char *)prefix, 0), PrefixHash);

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)node)));
  PUTBACK;
  perl_call_method("end_prefix_mapping", G_DISCARD);

  FREETMPS;
  LEAVE;

  del_ns_mapping(cbv->ns_stack, (char*)prefix);
}  /* End nsEnd */

static void
elementDecl(void *data,
        const char *name,
        XML_Content *model) {
  dSP;
  CallbackVector *cbv = (CallbackVector*) data;
  HV *thing = newHV();
  SV *cmod;

  ENTER;
  SAVETMPS;

  cmod = generate_model(model);

  hv_store(thing, "Name", 4, newUTF8SVpv((char*)name, 0), NameHash);
  hv_store(thing, "Model", 5, cmod, 0);

  Safefree(model);
  PUSHMARK(sp);
  EXTEND(sp, 3);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)thing)));
  PUTBACK;
  perl_call_method("element_decl", G_DISCARD);
  FREETMPS;
  LEAVE;

}  /* End elementDecl */

static void
attributeDecl(void * data,
          const char * elname,
          const char * attname,
          const char * att_type,
          const char * dflt,
          int          reqorfix) {
  dSP;
  CallbackVector *cbv = (CallbackVector*) data;
  HV * node = newHV();
  SV * dfltsv;
  SV * valsv;

  if (dflt && reqorfix) {
      dfltsv = newUTF8SVpv("#FIXED", 0);
      valsv = newUTF8SVpv((char*)dflt, 0);
  }
  else if (dflt) {
      dfltsv = &PL_sv_undef;
      valsv = newUTF8SVpv((char*)dflt, 0);
  }
  else {
      dfltsv = newUTF8SVpv(reqorfix ? "#REQUIRED" : "#IMPLIED", 0);
      valsv = &PL_sv_undef;
  }

  hv_store(node, "eName", 5, newUTF8SVpv((char *)elname, 0), 0);
  hv_store(node, "aName", 5, newUTF8SVpv((char *)attname, 0), 0);
  hv_store(node, "Type", 4, newUTF8SVpv((char *)att_type, 0), 0);
  hv_store(node, "Mode", 4, dfltsv, 0);
  hv_store(node, "Value", 5, valsv, ValueHash);

  ENTER;
  SAVETMPS;
  PUSHMARK(sp);
  EXTEND(sp, 5);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)node)));
  PUTBACK;
  perl_call_method("attribute_decl", G_DISCARD);

  FREETMPS;
  LEAVE;
}  /* End attributeDecl */

static void 
skippedEntity(void * data, 
              const char * name, 
              int isparam) {
    dSP;
    CallbackVector* cbv = (CallbackVector*) data;
    HV * entity = newHV();
    char* pname;
    
    xse_characters(data, cbv->chrbuffer);
    if (cbv->feat_locator) XML_DefaultCurrent(cbv->p);
    
    pname = (char*) mymalloc(strlen(name) + 2);
    strcpy(pname, "%");

    hv_store(entity, "Name", 4, 
             newUTF8SVpv((char*)(isparam ? strcat(pname, name) : name), 0), 
             NameHash);

    myfree(pname);
    
    ENTER;
    SAVETMPS;
    PUSHMARK(sp);
    EXTEND(sp, 2);
    PUSHs(cbv->self_sv);
    PUSHs(sv_2mortal(newRV_noinc((SV*)entity)));
    PUTBACK;
    perl_call_method("skipped_entity", G_DISCARD);

    FREETMPS;
    LEAVE;
    
} /* skippedEntity */

static void
entityDecl(void *data,
       const char *name,
       int isparam,
       const char *value,
       int vlen,
       const char *base,
       const char *sysid,
       const char *pubid,
       const char *notation) {
  dSP;
  CallbackVector *cbv = (CallbackVector*) data;
  HV * node = newHV();
  char* pname; 

  pname = (char*) mymalloc(strlen(name) + 2);
  strcpy(pname, "%");

  hv_store(node, "Name", 4, 
           newUTF8SVpv((char *)(isparam ? strcat(pname, name) : name), 0), 
	   NameHash);

  myfree(pname);

  ENTER;
  SAVETMPS;
  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);

  /* --- internal entity --- */
  if (value) {
    hv_store(node, "Value", 5, newUTF8SVpv((char *)value, vlen), 0);

    PUSHs(sv_2mortal(newRV_noinc((SV*)node)));
    PUTBACK;
    perl_call_method("internal_entity_decl", G_DISCARD);

  /* --- external parsed entity --- */
  } else {
    char* key;

    hv_store(node, "SystemId", 8, 
	     sysid ? newUTF8SVpv((char*)sysid, 0) : SvREFCNT_inc(empty_sv), 
	     SystemIdHash);
    hv_store(node, "PublicId", 8, 
             pubid ? newUTF8SVpv((char*)pubid, 0) : SvREFCNT_inc(empty_sv), 
	     PublicIdHash);

    PUSHs(sv_2mortal(newRV_noinc((SV*)node)));
    PUTBACK;
    perl_call_method("external_entity_decl", G_DISCARD);

    /* storing entity name */
    key = (char*) mymalloc(300);
    xse_extern_ent_key(key, base, sysid, pubid);
    hv_store(cbv->extern_hv, key, strlen(key),
             newUTF8SVpv((char*)name, 0), 0);
    myfree(key);
  }
 
  FREETMPS;
  LEAVE;

}  /* End entityDecl */

static void
doctypeStart(void *userData,
         const char* name,
         const char* sysid,
         const char* pubid,
         int hasinternal) {
  dSP;
  CallbackVector *cbv = (CallbackVector*) userData;
  HV *node = newHV();
  char* key;

  hv_store(node, "Name", 4, newUTF8SVpv((char*)name, 0), NameHash);
  hv_store(node, "SystemId", 8, 
           sysid ? newUTF8SVpv((char*)sysid, 0) : SvREFCNT_inc(empty_sv), 
	   SystemIdHash);
  hv_store(node, "PublicId", 8, 
           pubid ? newUTF8SVpv((char*)pubid, 0) : SvREFCNT_inc(empty_sv), 
	   PublicIdHash);

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)node)));
  PUTBACK;
  perl_call_method("start_dtd", G_DISCARD);
  FREETMPS;
  LEAVE;

  /* storing entity name */
  key = (char*) mymalloc(300);
  xse_extern_ent_key(key, "", sysid, pubid);
  hv_store(cbv->extern_hv, key, strlen(key),
           newUTF8SVpv("[dtd]", 0), 0);
  myfree(key);

}  /* End doctypeStart */

static void
doctypeEnd(void *userData) {
  dSP;
  CallbackVector *cbv = (CallbackVector*) userData;

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 1);
  PUSHs(cbv->self_sv);
  PUTBACK;
  perl_call_method("end_dtd", G_DISCARD);
  FREETMPS;
  LEAVE;
}  /* End doctypeEnd */

static void
xmlDecl(void *userData,
    const char *version,
    const char *encoding,
    int standalone) 
{
  dSP;
  CallbackVector *cbv = (CallbackVector*) userData;
  HV *node = newHV();

  hv_store(node, "Version", 7, version ? newUTF8SVpv((char*)version, 0) 
		 : SvREFCNT_inc(empty_sv), VersionHash);
  hv_store(node, "Encoding", 8, encoding ? newUTF8SVpv((char*)encoding, 0)
		 : SvREFCNT_inc(empty_sv), EncodingHash);
  hv_store(node, "Standalone", 10, standalone == -1 ? &PL_sv_undef
		 : (standalone ? newUTF8SVpv("yes",0) : newUTF8SVpv("no",0)), 0);

  /* 1/0 instead of 'yes'/'no'
  hv_store(node, "Standalone", 10, standalone == -1 ? &PL_sv_undef
		 : (standalone ? &PL_sv_yes : &PL_sv_no), 0); 
  */

  /* writing to locator */
  hv_store(cbv->locator_hv, "XMLVersion", 10, version ? 
           newUTF8SVpv((char*)version, 0) : newUTF8SVpv("1.0", 3), XMLVersionHash);
  if (SvCUR(*(hv_fetch(cbv->locator_hv, "Encoding", 8, 0))) == 0) {
    hv_store(cbv->locator_hv, "Encoding", 8, encoding ? 
             newUTF8SVpv((char*)encoding, 0) : newUTF8SVpv("utf-8",5), EncodingHash);
  } 

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)node)));
  PUTBACK;
  perl_call_method("xml_decl", G_DISCARD);
  FREETMPS;
  LEAVE;
}  /* End xmlDecl */

static void
unparsedEntityDecl(void *userData,
           const char* entity,
           const char* base,
           const char* sysid,
           const char* pubid,
           const char* notation)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;
  HV *node = newHV();

  hv_store(node, "Name", 4, newUTF8SVpv((char*)entity, 0), NameHash);
  hv_store(node, "PublicId", 8, pubid ? newUTF8SVpv((char*)pubid, 0) 
                  : SvREFCNT_inc(empty_sv), PublicIdHash);
  hv_store(node, "SystemId", 8, newUTF8SVpv((char*)sysid, 0), SystemIdHash);
  hv_store(node, "Notation", 8, newUTF8SVpv((char*)notation, 0), 0);

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 6);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)node)));
  PUTBACK;
  perl_call_method("unparsed_entity_decl", G_DISCARD);

  FREETMPS;
  LEAVE;
}  /* End unparsedEntityDecl */

static void
notationDecl(void *userData,
         const char *name,
         const char *base,
         const char *sysid,
         const char *pubid)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;
  HV *node = newHV();

  hv_store(node, "Name", 4, newUTF8SVpv((char*)name, 0), NameHash);
  hv_store(node, "SystemId", 8, sysid ? newUTF8SVpv((char*)sysid, 0) 
		 : SvREFCNT_inc(empty_sv), SystemIdHash);
  hv_store(node, "PublicId", 8, pubid ? newUTF8SVpv((char*)pubid, 0) 
		 : SvREFCNT_inc(empty_sv), PublicIdHash);

  PUSHMARK(sp);
  XPUSHs(cbv->self_sv);
  XPUSHs(sv_2mortal(newRV_noinc((SV*)node)));
  PUTBACK;
  perl_call_method("notation_decl", G_DISCARD);
}  /* End notationDecl */

static int
externalEntityRef(XML_Parser parser,
          const char* open,
          const char* base,
          const char* sysid,
          const char* pubid)
{
  dSP;
#if defined(USE_THREADS) && PATCHLEVEL==6
  dTHX;
#endif
  int count;
  int ret = 0;
  int parse_done = 0;
  CallbackVector* cbv = (CallbackVector*) XML_GetUserData(parser);
  char* key;
  SV **name;
  HV *start = newHV();
  HV *end = newHV();
  char* pname;

  xse_characters((void*)cbv, cbv->chrbuffer);

  /* fetching entity name */
  key = (char*) mymalloc(300);
  xse_extern_ent_key(key, base, sysid, pubid);
  name = hv_fetch(cbv->extern_hv, key, strlen(key), 0);
  myfree(key);

  pname = SvPV_nolen(*name);

  if (( (pname[0] == '[' || pname[0] == '%') && !cbv->feat_entpar) || 
      ( (pname[0] != '[' && pname[0] != '%') && !cbv->feat_entgen)) {

      skippedEntity(cbv, pname, 0);
      return 1;
  }

  ENTER ;
  SAVETMPS ;

  /* start_entity */
  hv_store(start, "Name", 4, SvREFCNT_inc(*name), NameHash);

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)start)));
  PUTBACK;
  perl_call_method("start_entity", G_DISCARD);
  SPAGAIN;

  /* open the entity */
  PUSHMARK(sp);
  EXTEND(sp, 4);
  PUSHs(cbv->self_sv);
  PUSHs(base ? sv_2mortal(newUTF8SVpv((char*) base, 0)) : &PL_sv_undef);
  PUSHs(sysid ? sv_2mortal(newUTF8SVpv((char*) sysid, 0)) : &PL_sv_undef);
  PUSHs(pubid ? sv_2mortal(newUTF8SVpv((char*) pubid, 0)) : &PL_sv_undef);
  PUTBACK;
  count = perl_call_method("_get_external_entity", G_SCALAR);

  SPAGAIN;

  if (count >= 1) {
    SV * result = POPs;

    int type;

    if (result && (type = SvTYPE(result)) > 0) {
      SV **pval = hv_fetch((HV*)SvRV(cbv->self_sv), "Parser", 6, 0);

      if (! pval || ! SvIOK(*pval)) {
        append_error(parser, 
	"Can't find parser entry in XML::SAX::ExpatXS object");

      } else {
        XML_Parser entpar;
	char *errmsg = (char *) 0;

	entpar = XML_ExternalEntityParserCreate(parser, open, 0);

	XML_SetBase(entpar, XML_GetBase(parser));

	sv_setiv(*pval, (IV) entpar);

	cbv->p = entpar;

	PUSHMARK(sp);
	EXTEND(sp, 2);
	PUSHs(*pval);
	PUSHs(result);
	PUTBACK;
	count = perl_call_pv("XML::SAX::ExpatXS::Do_External_Parse",
		               G_SCALAR | G_EVAL);
        SPAGAIN;

	if (SvTRUE(ERRSV)) {
	  char  *hold;
	  STRLEN   len;

	  POPs;
          hold = SvPV(ERRSV, len);
          New(326, errmsg, len + 1, char);
          if (len)
            Copy(hold, errmsg, len, char);
          goto Extparse_Cleanup;
	}

        if (count > 0)
        ret = POPi;
	/* warn("external parsing return value: %d", ret); */
        parse_done = 1;

        Extparse_Cleanup:
        cbv->p = parser;
        sv_setiv(*pval, (IV) parser);
        XML_ParserFree(entpar);

        if (SvTRUE(ERRSV))
          append_error(parser, SvPV(ERRSV, PL_na));
      }
    }
  }

  if (! ret && ! parse_done)
    append_error(parser, "Handler couldn't resolve external entity");

  /* end entity */
  hv_store(end, "Name", 4, SvREFCNT_inc(*name), NameHash);

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newRV_noinc((SV*)end)));
  PUTBACK;
  perl_call_method("end_entity", G_DISCARD);

  FREETMPS ;
  LEAVE ;

  return ret;

}  /* End externalEntityRef */

/*================================================================
** This is the function that expat calls to convert multi-byte sequences
** for external encodings. Each byte in the sequence is used to index
** into the current map to either set the next map or, in the case of
** the final byte, to get the corresponding Unicode scalar, which is
** returned.
*/

static int
convert_to_unicode(void *data, const char *seq) {
  Encinfo *enc = (Encinfo *) data;
  PrefixMap *curpfx;
  int count;
  int index = 0;

  for (count = 0; count < 4; count++) {
    unsigned char byte = (unsigned char) seq[count];
    unsigned char bndx;
    unsigned char bmsk;
    int offset;

    curpfx = &enc->prefixes[index];
    offset = ((int) byte) - curpfx->min;
    if (offset < 0)
      break;
    if (offset >= curpfx->len && curpfx->len != 0)
      break;

    bndx = byte >> 3;
    bmsk = 1 << (byte & 0x7);

    if (curpfx->ispfx[bndx] & bmsk) {
      index = enc->bytemap[curpfx->bmap_start + offset];
    }
    else if (curpfx->ischar[bndx] & bmsk) {
      return enc->bytemap[curpfx->bmap_start + offset];
    }
    else
      break;
  }

  return -1;
}  /* End convert_to_unicode */

static int
unknownEncoding(void *unused, const char *name, XML_Encoding *info)
{
  SV ** encinfptr;
  Encinfo *enc;
  int namelen;
  int i;
  char buff[42];

  namelen = strlen(name);
  if (namelen > 40)
    return 0;

  /* Make uppercase */
  for (i = 0; i < namelen; i++) {
    char c = name[i];
    if (c >= 'a' && c <= 'z')
      c -= 'a' - 'A';
    buff[i] = c;
  }

  if (! EncodingTable) {
    EncodingTable = perl_get_hv("XML::SAX::ExpatXS::Encoding::Encoding_Table", FALSE);
    if (! EncodingTable)
      croak("Can't find XML::SAX::ExpatXS::Encoding::Encoding_Table");
  }

  encinfptr = hv_fetch(EncodingTable, buff, namelen, 0);

  if (! encinfptr || ! SvOK(*encinfptr)) {
    /* Not found, so try to autoload */
    dSP;
    int count;

    ENTER;
    SAVETMPS;
    PUSHMARK(sp);
    XPUSHs(sv_2mortal(newSVpvn(buff,namelen)));
    PUTBACK;
    perl_call_pv("XML::SAX::ExpatXS::Encoding::load_encoding", G_DISCARD);

    encinfptr = hv_fetch(EncodingTable, buff, namelen, 0);
    FREETMPS;
    LEAVE;

    if (! encinfptr || ! SvOK(*encinfptr))
      return 0;
  }

  if (! sv_derived_from(*encinfptr, "XML::SAX::ExpatXS::Encinfo"))
    croak("Entry in XML::SAX::ExpatXS::Encoding::Encoding_Table not an Encinfo object");

  enc = (Encinfo *) SvIV((SV*)SvRV(*encinfptr));
  Copy(enc->firstmap, info->map, 256, int);
  info->release = NULL;
  if (enc->prefixes_size) {
    info->data = (void *) enc;
    info->convert = convert_to_unicode;
  }
  else {
    info->data = NULL;
    info->convert = NULL;
  }

  return 1;
}  /* End unknownEncoding */


static void
recString(void *userData, const char *string, int len)
{
  int i, ln = 0, cl = 0;

  CallbackVector *cbv = (CallbackVector*) userData;
  xse_get_end_position(cbv->p, ln, cl, string, len);
  
  /* printf("-->%d, %d\n", ln, cl); */
  hv_store(cbv->locator_hv, "LineNumber", 10, newSViv(ln), 0);
  hv_store(cbv->locator_hv, "ColumnNumber", 12, newSViv(cl), 0);

  if (cbv->feat_recstring) {
    if (SvCUR(cbv->chrbuffer) > 0) {
      sv_setsv(cbv->recstring, cbv->chrbuffer);
    } else {
      sv_setpvn(cbv->recstring, (char *) string, len);
    }
  }
}  /* End recString */

MODULE = XML::SAX::ExpatXS PACKAGE = XML::SAX::ExpatXS    PREFIX = XML_

PROTOTYPES: DISABLE

BOOT:
    PERL_HASH(PrefixHash, "Prefix", 6);
    PERL_HASH(NamespaceURIHash, "NamespaceURI", 12);
    PERL_HASH(NameHash, "Name", 4);
    PERL_HASH(LocalNameHash, "LocalName", 9);
    PERL_HASH(AttributesHash, "Attributes", 10);
    PERL_HASH(ValueHash, "Value", 5);
    PERL_HASH(DataHash, "Data", 4);
    PERL_HASH(TargetHash, "Target", 6);
    PERL_HASH(VersionHash, "Version", 7);
    PERL_HASH(XMLVersionHash, "XMLVersion", 10);
    PERL_HASH(EncodingHash, "Encoding", 8);
    PERL_HASH(PublicIdHash, "PublicId", 8);
    PERL_HASH(SystemIdHash, "SystemId", 8);
    empty_sv = newUTF8SVpv("", 0);

XML_Parser
XML_ParserCreate(self_sv, enc_sv, namespaces)
    SV *            self_sv
    SV *            enc_sv
    int            namespaces
    CODE:
    {
      CallbackVector *cbv;
      enum XML_ParamEntityParsing pep = XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE;
      char *enc = (char *) (SvTRUE(enc_sv) ? SvPV(enc_sv,PL_na) : 0);
      SV ** spp;

      Newz(320, cbv, 1, CallbackVector);
      cbv->self_sv = SvREFCNT_inc(self_sv);

      spp = hv_fetch((HV*)SvRV(cbv->self_sv), "NoExpand", 8, 0);
      if (spp && SvTRUE(*spp))
        cbv->no_expand = 1;

      spp = hv_fetch((HV*)SvRV(cbv->self_sv), "Context", 7, 0);
      if (! spp || ! *spp || !SvROK(*spp))
        croak("XML::SAX::ExpatXS instance missing Context");

      cbv->context = (AV*) SvRV(*spp);

      spp = hv_fetch((HV*)SvRV(cbv->self_sv), "Namespace_Stack",
            15, FALSE);

      if (!spp || !*spp || !SvROK(*spp))
        croak("XML::SAX::ExpatXS instance missing Namespace_Stack");

      cbv->ns_stack = (AV *)SvRV(*spp);

      cbv->ns = (unsigned) namespaces;
      if (namespaces)
        {
          RETVAL = XML_ParserCreate_MM(enc, &ms, nsdelim);
          XML_SetNamespaceDeclHandler(RETVAL, nsStart, nsEnd);
        }
      else
        {
          RETVAL = XML_ParserCreate_MM(enc, &ms, NULL);
        }

      cbv->p = RETVAL;
      XML_SetUserData(RETVAL, (void *) cbv);

      XML_SetElementHandler(RETVAL, startElement, endElement);
      XML_SetCharacterDataHandler(RETVAL, characterData);
      XML_SetProcessingInstructionHandler(RETVAL, processingInstruction);
      XML_SetCommentHandler(RETVAL, commenthandle);
      XML_SetCdataSectionHandler(RETVAL, startCdata, endCdata);
      XML_SetElementDeclHandler(RETVAL, elementDecl);
      XML_SetAttlistDeclHandler(RETVAL, attributeDecl);
      XML_SetEntityDeclHandler(RETVAL, entityDecl);
      XML_SetUnparsedEntityDeclHandler(RETVAL, unparsedEntityDecl);
      XML_SetNotationDeclHandler(RETVAL, notationDecl);
      XML_SetExternalEntityRefHandler(RETVAL, externalEntityRef);
      XML_SetXmlDeclHandler(RETVAL, xmlDecl);
      XML_SetStartDoctypeDeclHandler(RETVAL, doctypeStart);
      XML_SetEndDoctypeDeclHandler(RETVAL, doctypeEnd);
      XML_SetSkippedEntityHandler(RETVAL, skippedEntity);
      XML_SetUnknownEncodingHandler(RETVAL, unknownEncoding, 0);

      /* reading features */
      cbv->feat_perlxmlns = get_feature(cbv, "http://xmlns.perl.org/sax/xmlns-uris");
      cbv->feat_xmlns = cbv->feat_perlxmlns 
          ? 0 : get_feature(cbv, "http://xml.org/sax/features/xmlns-uris");
      cbv->feat_join = get_feature(cbv, "http://xmlns.perl.org/sax/join-character-data");
      cbv->feat_nsatts = get_feature(cbv, "http://xmlns.perl.org/sax/ns-attributes");
      cbv->feat_locator = get_feature(cbv, "http://xmlns.perl.org/sax/locator");
      cbv->feat_recstring = get_feature(cbv, "http://xmlns.perl.org/sax/recstring");
      cbv->feat_entgen = get_feature(cbv, "http://xml.org/sax/features/external-general-entities");
      cbv->feat_entpar = get_feature(cbv, "http://xml.org/sax/features/external-parameter-entities");
      /* end of reading features */

      if (cbv->no_expand)
	XML_SetDefaultHandler(RETVAL, recString);
      else
	XML_SetDefaultHandlerExpand(RETVAL, recString);

      if (!cbv->feat_entpar) {
          spp = hv_fetch((HV*)SvRV(cbv->self_sv), "ParseParamEnt",
                         13, FALSE);

          if (spp && SvTRUE(*spp)) {
              cbv->feat_entpar = 1;
          }
      }

      XML_SetParamEntityParsing(RETVAL, pep);

      cbv->atts_ready = 0;
      cbv->chrbuffer = newUTF8SVpv("", 0);
      cbv->chr_hv = newHV();
    }
    OUTPUT:
    RETVAL

void
XML_ParserRelease(parser)
      XML_Parser parser
    CODE:
      {
        CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

	 SvREFCNT_dec(cbv->self_sv);
      }

void
XML_ParserFree(parser)
    XML_Parser parser
    CODE:
    {
      CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

      SvREFCNT_dec(cbv->start_sv);
      SvREFCNT_dec(cbv->end_sv);
      SvREFCNT_dec(cbv->char_sv);
      SvREFCNT_dec(cbv->comment_sv);
      SvREFCNT_dec(cbv->chrbuffer);
      SvREFCNT_dec(cbv->self_sv);

      SvREFCNT_dec(cbv->locator_hv);
      SvREFCNT_dec(cbv->extern_hv);
      SvREFCNT_dec(cbv->chr_hv);

      Safefree(cbv);
      XML_ParserFree(parser);
    }

int
XML_ParseString(parser, str)
        XML_Parser            parser
        SV *                  str
    CODE:
        {
      CallbackVector * cbv;
      char * s;
      STRLEN len;
      
      s = SvPV(str, len);

          cbv = (CallbackVector *) XML_GetUserData(parser);

      RETVAL = XML_Parse(parser, s, len, 1);
      SPAGAIN; /* XML_Parse might have changed stack pointer */
      if (! RETVAL)
        append_error(parser, NULL);
    }

    OUTPUT:
    RETVAL

int
XML_ParseStream(parser, ioref, delim=NULL)
    XML_Parser            parser
    SV *                ioref
    SV *                delim
    CODE:
    {
      SV **delimsv;
      CallbackVector * cbv;

      cbv = (CallbackVector *) XML_GetUserData(parser);
      if (delim && SvOK(delim)) {
        cbv->delim = SvPV(delim, cbv->delimlen);
      }
      else {
        cbv->delim = (char *) 0;
      }

      RETVAL = parse_stream(parser, ioref);
      SPAGAIN; /* parse_stream might have changed stack pointer */
    }

    OUTPUT:
    RETVAL

int
XML_ParsePartial(parser, str)
    XML_Parser            parser
    SV *                  str
    CODE:
    {
      CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);
      char * s;
      STRLEN len;
      
      s = SvPV(str, len);

      RETVAL = XML_Parse(parser, s, len, 0);
      if (! RETVAL)
        append_error(parser, NULL);
    }

    OUTPUT:
    RETVAL


int
XML_ParseDone(parser)
    XML_Parser            parser
    CODE:
    {
      RETVAL = XML_Parse(parser, "", 0, 1);
      if (! RETVAL)
        append_error(parser, NULL);
    }

    OUTPUT:
    RETVAL


void
XML_SetBase(parser, base)
    XML_Parser            parser
    SV *                base
    CODE:
    {
      char * b;

      if (! SvOK(base)) {
        b = (char *) 0;
      }
      else {
        b = SvPV(base, PL_na);
      }

      XML_SetBase(parser, b);
    }


SV *
XML_GetBase(parser)
    XML_Parser            parser
    CODE:
    {
      const char *ret = XML_GetBase(parser);
      if (ret) {
        ST(0) = sv_newmortal();
        sv_setpv(ST(0), ret);
      }
      else {
        ST(0) = &PL_sv_undef;
      }
    }


HV *
XML_GetLocator(parser, pubid, sysid, encoding)
    XML_Parser            parser
    SV *		  pubid
    SV *		  sysid
    SV *		  encoding
    CODE:
    {
      CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

      cbv->locator_hv = newHV();
      hv_store(cbv->locator_hv, "LineNumber", 10, 
               newSViv(1), 0);
      hv_store(cbv->locator_hv, "ColumnNumber", 12, 
               newSViv(1), 0);
      hv_store(cbv->locator_hv, "XMLVersion", 10, 
               newUTF8SVpv("1.0", 3), XMLVersionHash);
      hv_store(cbv->locator_hv, "Encoding", 8, 
               SvCUR(encoding) > 0 ? SvREFCNT_inc(encoding) : newUTF8SVpv("", 0), 
               EncodingHash);	       
      hv_store(cbv->locator_hv, "SystemId", 8, 
               SvCUR(sysid) > 0 ? SvREFCNT_inc(sysid) : newUTF8SVpv("", 0), 
               SystemIdHash);
      hv_store(cbv->locator_hv, "PublicId", 8, 
               SvCUR(pubid) > 0 ? SvREFCNT_inc(pubid) : newUTF8SVpv("", 0), 
               PublicIdHash);

      RETVAL = cbv->locator_hv;
    }

    OUTPUT:
    RETVAL


SV *
XML_GetRecognizedString(parser)
    XML_Parser            parser
    CODE:
    {
      CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

      cbv->recstring = newUTF8SVpvn("", 0);

      RETVAL = newRV_noinc(cbv->recstring);
    }

    OUTPUT:
    RETVAL


HV *
XML_GetExternEnt(parser)
    XML_Parser            parser
    CODE:
    {
      CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

      cbv->extern_hv = newHV();

      RETVAL = cbv->extern_hv;
    }

    OUTPUT:
    RETVAL

void
XML_SetCallbacks(parser, start, end, chars, comment)
    XML_Parser            parser
    SV *		  start
    SV *		  end
    SV *		  chars
    SV *		  comment
    CODE:
    {
      CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

      if (cbv->start_sv) sv_setsv(cbv->start_sv, start);    
      else cbv->start_sv = SvREFCNT_inc(start);

      if (cbv->end_sv) sv_setsv(cbv->end_sv, end);    
      else cbv->end_sv = SvREFCNT_inc(end);

      if (cbv->char_sv) sv_setsv(cbv->char_sv, chars);    
      else cbv->char_sv = SvREFCNT_inc(chars);

      if (cbv->comment_sv) sv_setsv(cbv->comment_sv, comment);    
      else cbv->comment_sv = SvREFCNT_inc(comment);
    }

void
XML_PositionContext(parser, lines)
    XML_Parser            parser
    int                lines
    PREINIT:
    int parsepos;
        int size;
        const char *pos = XML_GetInputContext(parser, &parsepos, &size);
    const char *markbeg;
    const char *limit;
    const char *markend;
    int length, relpos;
    int  cnt;

    PPCODE:
      if (! pos)
            return;

      for (markbeg = &pos[parsepos], cnt = 0; markbeg >= pos; markbeg--)
        {
          if (*markbeg == '\n')
        {
          cnt++;
          if (cnt > lines)
            break;
        }
        }

      markbeg++;

          relpos = 0;
      limit = &pos[size];
      for (markend = &pos[parsepos + 1], cnt = 0;
           markend < limit;
           markend++)
        {
          if (*markend == '\n')
        {
          if (cnt == 0)
                     relpos = (markend - markbeg) + 1;
          cnt++;
          if (cnt > lines)
            {
              markend++;
              break;
            }
        }
        }

      length = markend - markbeg;
          if (relpos == 0)
            relpos = length;

          EXTEND(sp, 2);
      PUSHs(sv_2mortal(newSVpvn((char *) markbeg, length)));
      PUSHs(sv_2mortal(newSViv(relpos)));

void
XML_DefaultCurrent(parser)
    XML_Parser            parser
    CODE:
    {
      CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

      XML_DefaultCurrent(parser);
    }

int
XML_GetErrorCode(parser)
    XML_Parser            parser

int
XML_GetCurrentLineNumber(parser)
    XML_Parser            parser

int
XML_GetCurrentColumnNumber(parser)
    XML_Parser            parser

const char *
XML_ExpatVersion()

long
XML_GetCurrentByteIndex(parser)
    XML_Parser            parser

int
XML_GetSpecifiedAttributeCount(parser)
    XML_Parser            parser

char *
XML_ErrorString(code)
    int                code
    CODE:
    const char *ret = XML_ErrorString(code);
    ST(0) = sv_newmortal();
    sv_setpv((SV*)ST(0), ret);

SV *
XML_LoadEncoding(data, size)
    char *             data
    int                size
    CODE:
    {
      Encmap_Header *emh = (Encmap_Header *) data;
      unsigned pfxsize, bmsize;

      if (size < sizeof(Encmap_Header)
          || ntohl(emh->magic) != ENCMAP_MAGIC) {
        RETVAL = &PL_sv_undef;
      }
      else {
        Encinfo    *entry;
        SV        *sv;
        PrefixMap    *pfx;
        unsigned short *bm;
        int namelen;
        int i;

        pfxsize = ntohs(emh->pfsize);
        bmsize  = ntohs(emh->bmsize);

        if (size != (sizeof(Encmap_Header)
             + pfxsize * sizeof(PrefixMap)
             + bmsize * sizeof(unsigned short))) {
          RETVAL = &PL_sv_undef;
        }
        else {
          /* Convert to uppercase and get name length */

          for (i = 0; i < sizeof(emh->name); i++) {
        char c = emh->name[i];

          if (c == (char) 0)
            break;

        if (c >= 'a' && c <= 'z')
          emh->name[i] -= 'a' - 'A';
          }
          namelen = i;

          RETVAL = newSVpvn(emh->name, namelen);

          New(322, entry, 1, Encinfo);
          entry->prefixes_size = pfxsize;
          entry->bytemap_size  = bmsize;
          for (i = 0; i < 256; i++) {
            entry->firstmap[i] = ntohl(emh->map[i]);
          }

          pfx = (PrefixMap *) &data[sizeof(Encmap_Header)];
          bm = (unsigned short *) (((char *) pfx)
                       + sizeof(PrefixMap) * pfxsize);

          New(323, entry->prefixes, pfxsize, PrefixMap);
          New(324, entry->bytemap, bmsize, unsigned short);

          for (i = 0; i < pfxsize; i++, pfx++) {
        PrefixMap *dest = &entry->prefixes[i];

        dest->min = pfx->min;
        dest->len = pfx->len;
        dest->bmap_start = ntohs(pfx->bmap_start);
        Copy(pfx->ispfx, dest->ispfx,
             sizeof(pfx->ispfx) + sizeof(pfx->ischar), unsigned char);
          }

          for (i = 0; i < bmsize; i++)
        entry->bytemap[i] = ntohs(bm[i]);

          sv = newSViv(0);
          sv_setref_pv(sv, "XML::SAX::ExpatXS::Encinfo", (void *) entry);

          if (! EncodingTable) {
        EncodingTable
          = perl_get_hv("XML::SAX::ExpatXS::Encoding::Encoding_Table",
                FALSE);
        if (! EncodingTable)
          croak("Can't find XML::SAX::ExpatXS::Encoding::Encoding_Table");
          }

          hv_store(EncodingTable, emh->name, namelen, sv, 0);
        }
      }
    }
    OUTPUT:
    RETVAL

void
XML_FreeEncoding(enc)
    Encinfo *            enc
    CODE:
    Safefree(enc->bytemap);
    Safefree(enc->prefixes);
    Safefree(enc);

SV *
XML_OriginalString(parser)
    XML_Parser            parser
    CODE:
    {
      int parsepos, size;
      const char *buff = XML_GetInputContext(parser, &parsepos, &size);
      if (buff) {
        RETVAL = newSVpvn((char *) &buff[parsepos],
                  XML_GetCurrentByteCount(parser));
      }
      else {
        RETVAL = newSVpv("", 0);
      }
    }
    OUTPUT:
    RETVAL

int
XML_Do_External_Parse(parser, result)
    XML_Parser          parser
    SV *                result
    CODE:
    {
     int type;

      CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

      if (SvROK(result) && SvOBJECT(SvRV(result))) {
        RETVAL = parse_stream(parser, result);
      }
      else if (isGV(result)) {
        RETVAL = parse_stream(parser,
                  sv_2mortal(newRV((SV*) GvIOp(result))));
      }
      else if (SvPOK(result)) {
        STRLEN  eslen;
        int pret;
        char *entstr = SvPV(result, eslen);

        RETVAL = XML_Parse(parser, entstr, eslen, 1);
      }
    }
    OUTPUT:
    RETVAL