/*****************************************************************
** Expat.xs
**
** Copyright 1998 Larry Wall and Clark Cooper
** 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 "encoding.h"

#define BUFSIZE 32768

#define NSDELIM  '|'

/* Macro to update handler fields. Used in the various handler setting
   XSUBS */

#define XMLP_UPD(fld) \
  RETVAL = cbv->fld ? newSVsv(cbv->fld) : &PL_sv_undef;\
  if (cbv->fld) {\
    if (cbv->fld != fld)\
      sv_setsv(cbv->fld, fld);\
  }\
  else\
    cbv->fld = newSVsv(fld)

/* Macro to push old handler value onto return stack. */

#define PUSHRET \
  ST(0) = RETVAL;\
  if (RETVAL != &PL_sv_undef && SvREFCNT(RETVAL)) sv_2mortal(RETVAL)

typedef struct {
  SV* self_sv;
  XML_Parser p;

  AV* context;
  AV* new_prefix_list;
  HV *nstab;
  AV *nslst;

  unsigned int st_serial;
  unsigned int st_serial_stackptr;
  unsigned int st_serial_stacksize;
  unsigned int * st_serial_stack;

  unsigned int skip_until;

  SV *recstring;
  char * delim;
  STRLEN delimlen;

  unsigned ns:1;
  unsigned no_expand:1;
  unsigned parseparam:1;

  /* Callback handlers */

  SV* start_sv;
  SV* end_sv;
  SV* char_sv;
  SV* proc_sv;
  SV* cmnt_sv;
  SV* dflt_sv;

  SV* entdcl_sv;
  SV* eledcl_sv;
  SV* attdcl_sv;
  SV* doctyp_sv;
  SV* doctypfin_sv;
  SV* xmldec_sv;

  SV* unprsd_sv;
  SV* notation_sv;

  SV* extent_sv;
  SV* extfin_sv;

  SV* startcd_sv;
  SV* endcd_sv;
} CallbackVector;


static HV* EncodingTable = NULL;

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

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

/* Forward declarations */

static void suspend_callbacks(CallbackVector *);
static void resume_callbacks(CallbackVector *);

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

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

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

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

static void*
mymalloc(size_t size) {
  return safemalloc(size);
}

static void*
myrealloc(void *p, size_t s) {
  return saferealloc(p, s);
}

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

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

static void
free_cbv(CallbackVector *cbv)
{
  if (cbv) {
    SvREFCNT_dec(cbv->self_sv);
    Safefree(cbv->st_serial_stack);
    Safefree(cbv);
  }
}

static void
append_error(XML_Parser parser, char * err)
{
  dSP;
  CallbackVector * cbv;
  SV ** errstr;

  cbv = (CallbackVector*) XML_GetUserData(parser);
  errstr = hv_fetch((HV*)SvRV(cbv->self_sv),
		    "ErrorMessage", 12, 0);

  if (errstr && SvPOK(*errstr)) {
    SV ** errctx = hv_fetch((HV*) SvRV(cbv->self_sv),
			    "ErrorContext", 12, 0);
    int dopos = !err && errctx && SvOK(*errctx);

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

    /* Avoid truncation on 32-bit perls when expat is built with
       XML_LARGE_SIZE (long long types).  Use NV (double, 53-bit
       integer precision) to preserve values up to ~9 PB.
       See https://github.com/cpan-authors/XML-Parser/issues/36
       and https://github.com/cpan-authors/XML-Parser/issues/48 */
#if (defined(XML_LARGE_SIZE) && IVSIZE < 8)
    sv_catpvf(*errstr, "\n%s at line %.0" NVff ", column %.0" NVff ", byte %.0" NVff "%s",
	      err,
	      (NV)XML_GetCurrentLineNumber(parser),
	      (NV)XML_GetCurrentColumnNumber(parser),
	      (NV)XML_GetCurrentByteIndex(parser),
	      dopos ? ":\n" : "");
#else
    sv_catpvf(*errstr, "\n%s at line %" UVuf ", column %" UVuf ", byte %" IVdf "%s",
	      err,
	      (UV)XML_GetCurrentLineNumber(parser),
	      (UV)XML_GetCurrentColumnNumber(parser),
	      (IV)XML_GetCurrentByteIndex(parser),
	      dopos ? ":\n" : "");
#endif
	      
    if (dopos)
      {
	int count;

	ENTER ;
	SAVETMPS ;
	PUSHMARK(sp);
	XPUSHs(cbv->self_sv);
	XPUSHs(*errctx);
	PUTBACK ;

	count = perl_call_method("position_in_context", G_SCALAR);

	SPAGAIN ;

	if (count >= 1) {
	  sv_catsv(*errstr, POPs);
	}

	PUTBACK ;
	FREETMPS ;
	LEAVE ;
      }

    if (XML_GetErrorCode(parser) == XML_ERROR_INVALID_TOKEN) {
      sv_catpv(*errstr,
        "(Hint: \"not well-formed\" often indicates unescaped '<', '>' or '&'"
        " in content — use &lt; &gt; or &amp; instead)\n");
    }
  }
}  /* 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::Parser::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, newUTF8SVpv((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;

  case XML_CTYPE_EMPTY:
  case XML_CTYPE_ANY:
    break;
  }

  return obj;
}  /* End generate_model */

static int
parse_stream(XML_Parser parser, SV * ioref)
{
  dSP;
  SV *		tbuff = NULL;
  SV *		tsiz = NULL;
  char *	linebuff = NULL;
  STRLEN	lblen;
  STRLEN	br = 0;
  int		buffsize;
  int		done = 0;
  int		ret = 1;
  CallbackVector * cbv;
  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 {
      linebuff = SvPV(tline, lblen);

      if (lblen > cbv->delimlen + 1) {
	char *chk = &linebuff[lblen - cbv->delimlen - 1];

	if (*chk == *cbv->delim
	    && chk[cbv->delimlen] == '\n'
	    && strnEQ(chk + 1, 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;
    /* Register for cleanup so croak() in the loop won't leak them */
    SAVEFREESV(tbuff);
    SAVEFREESV(tsiz);
  }

  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) {
	  if (br > buffsize) {
	    /* The byte count from SvPV can exceed buffsize when the
	       filehandle has a :utf8 layer, since Perl reads buffsize
	       characters but multi-byte UTF-8 chars produce more bytes.
	       Re-obtain the buffer at the required size. */
	    buffer = XML_GetBuffer(parser, br);
	    if (! buffer)
	      croak("Ran out of memory for input buffer");
	  }
	  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, NULL);

  /* tbuff and tsiz are freed by SAVEFREESV via FREETMPS/LEAVE below */
      
  FREETMPS;
  LEAVE;

  return ret;
}  /* End parse_stream */

static SV *
gen_ns_name(const char * name, HV * ns_table, AV * ns_list)
{
  char	*pos = strchr(name, NSDELIM);
  SV * ret;

  if (pos && pos > name)
    {
      SV ** name_ent = hv_fetch(ns_table, (char *) name,
				pos - name, TRUE);
      ret = newUTF8SVpv(&pos[1], 0);

      if (name_ent)
	{
	  int index;

	  if (SvOK(*name_ent))
	    {
	      index = SvIV(*name_ent);
	    }
	  else
	    {
	      av_push(ns_list,  newUTF8SVpv((char *) name, pos - name));
	      index = av_len(ns_list);
	      sv_setiv(*name_ent, (IV) index);
	    }

	  sv_setiv(ret, (IV) index);
	  SvPOK_on(ret);
	}
    }
  else
    ret = newUTF8SVpv((char *) name, 0);

  return ret;
}  /* End gen_ns_name */

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

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpvn((char*)s,len)));
  PUTBACK;
  perl_call_sv(cbv->char_sv, G_DISCARD|G_VOID);

  FREETMPS;
  LEAVE;
}  /* End characterData */

static void
startElement(void *userData, const char *name, const char **atts)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;
  unsigned   do_ns = cbv->ns;
  unsigned   skipping = 0;
  SV *  elname;

  cbv->st_serial++;

  if (cbv->skip_until) {
    skipping = cbv->st_serial < cbv->skip_until;
    if (! skipping) {
      resume_callbacks(cbv);
      cbv->skip_until = 0;
    }
  }

  if (cbv->st_serial_stackptr + 1 >= cbv->st_serial_stacksize) {
    unsigned int newsize = cbv->st_serial_stacksize + 512;

    Renew(cbv->st_serial_stack, newsize, unsigned int);
    cbv->st_serial_stacksize = newsize;
  }

  cbv->st_serial_stack[++cbv->st_serial_stackptr] =  cbv->st_serial;
  
  if (do_ns)
    elname = gen_ns_name(name, cbv->nstab, cbv->nslst);
  else
    elname = newUTF8SVpv((char *)name, 0);

  if (! skipping && SvTRUE(cbv->start_sv))
    {
      const char **attlim = atts;

      while (*attlim)
	attlim++;

      ENTER;
      SAVETMPS;

      PUSHMARK(sp);
      EXTEND(sp, attlim - atts + 2);
      PUSHs(cbv->self_sv);
      PUSHs(elname);
      while (*atts)
	{
	  SV * attname;

	  attname = (do_ns ? gen_ns_name(*atts, cbv->nstab, cbv->nslst)
		     : newUTF8SVpv((char *) *atts, 0));
	    
	  atts++;
	  PUSHs(sv_2mortal(attname));
	  if (*atts)
	    PUSHs(sv_2mortal(newUTF8SVpv((char*)*atts++,0)));
	}
      PUTBACK;
      perl_call_sv(cbv->start_sv, G_DISCARD|G_VOID);

      FREETMPS;
      LEAVE;
    }

  av_push(cbv->context, elname);

  if (cbv->ns) {
    av_clear(cbv->new_prefix_list);
  }
} /* End startElement */

static void
endElement(void *userData, const char *name)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;
  SV *elname;

  elname = av_pop(cbv->context);
  
  if (! cbv->st_serial_stackptr) {
    croak("endElement: Start tag serial number stack underflow");
  }

  if (! cbv->skip_until && SvTRUE(cbv->end_sv))
    {
      ENTER;
      SAVETMPS;

      PUSHMARK(sp);
      EXTEND(sp, 2);
      PUSHs(cbv->self_sv);
      PUSHs(elname);
      PUTBACK;
      perl_call_sv(cbv->end_sv, G_DISCARD|G_VOID);

      FREETMPS;
      LEAVE;
    }

  cbv->st_serial_stackptr--;

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

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

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 3);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpv((char*)target,0)));
  PUSHs(sv_2mortal(newUTF8SVpv((char*)data,0)));
  PUTBACK;
  perl_call_sv(cbv->proc_sv, G_DISCARD|G_VOID);

  FREETMPS;
  LEAVE;
}  /* End processingInstruction */

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

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpv((char*) string, 0)));
  PUTBACK;
  perl_call_sv(cbv->cmnt_sv, G_DISCARD|G_VOID);

  FREETMPS;
  LEAVE;
}  /* End commenthandler */

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

  if (cbv->startcd_sv) {
    ENTER;
    SAVETMPS;

    PUSHMARK(sp);
    XPUSHs(cbv->self_sv);
    PUTBACK;
    perl_call_sv(cbv->startcd_sv, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
  }
}  /* End startCdata */

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

  if (cbv->endcd_sv) {
    ENTER;
    SAVETMPS;

    PUSHMARK(sp);
    XPUSHs(cbv->self_sv);
    PUTBACK;
    perl_call_sv(cbv->endcd_sv, G_DISCARD|G_VOID);

    FREETMPS;
    LEAVE;
  }
}  /* End endCdata */

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

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 3);
  PUSHs(cbv->self_sv);
  PUSHs(prefix ? sv_2mortal(newUTF8SVpv((char *)prefix, 0)) : &PL_sv_undef);
  PUSHs(uri ? sv_2mortal(newUTF8SVpv((char *)uri, 0)) : &PL_sv_undef);
  PUTBACK;
  perl_call_method("NamespaceStart", G_DISCARD|G_VOID);

  FREETMPS;
  LEAVE;
}  /* End nsStart */

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

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(prefix ? sv_2mortal(newUTF8SVpv((char *)prefix, 0)) : &PL_sv_undef);
  PUTBACK;
  perl_call_method("NamespaceEnd", G_DISCARD|G_VOID);

  FREETMPS;
  LEAVE;
}  /* End nsEnd */

static void
defaulthandle(void *userData, const char *string, int len)
{
  dSP;
  CallbackVector* cbv = (CallbackVector*) userData;
  SV *handler;

  /* If a Char handler is registered and this is character data (not markup),
     forward to the Char handler instead of Default. Libexpat sends character
     data outside the root element to the default handler, but users expect
     the Char handler to be called (rt.cpan.org #46685). */
  if (SvTRUE(cbv->char_sv) && len > 0
      && string[0] != '<' && string[0] != '&')
    handler = cbv->char_sv;
  else
    handler = cbv->dflt_sv;

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 2);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpvn((char*)string, len)));
  PUTBACK;
  perl_call_sv(handler, G_DISCARD|G_VOID);

  FREETMPS;
  LEAVE;
}  /* End defaulthandle */

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

  ENTER;
  SAVETMPS;


  cmod = generate_model(model);

  XML_FreeContentModel(cbv->p, model);
  PUSHMARK(sp);
  EXTEND(sp, 3);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpv((char *)name, 0)));
  PUSHs(sv_2mortal(cmod));
  PUTBACK;
  perl_call_sv(cbv->eledcl_sv, G_DISCARD|G_VOID);
  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;
  SV * dfltsv;

  if (dflt) {
    dfltsv = newUTF8SVpv("'", 1);
    sv_catpv(dfltsv, (char *) dflt);
    sv_catpv(dfltsv, "'");
  }
  else {
    dfltsv = newUTF8SVpv(reqorfix ? "#REQUIRED" : "#IMPLIED", 0);
  }

  ENTER;
  SAVETMPS;
  PUSHMARK(sp);
  EXTEND(sp, 5);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpv((char *)elname, 0)));
  PUSHs(sv_2mortal(newUTF8SVpv((char *)attname, 0)));
  PUSHs(sv_2mortal(newUTF8SVpv((char *)att_type, 0)));
  PUSHs(sv_2mortal(dfltsv));
  if (dflt && reqorfix)
    XPUSHs(&PL_sv_yes);
  PUTBACK;
  perl_call_sv(cbv->attdcl_sv, G_DISCARD|G_VOID);

  FREETMPS;
  LEAVE;
}  /* End attributeDecl */

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;

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 6);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpv((char*)name, 0)));
  PUSHs(value ? sv_2mortal(newUTF8SVpvn((char*)value, vlen)) : &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);
  PUSHs(notation ? sv_2mortal(newUTF8SVpv((char *)notation, 0)) : &PL_sv_undef);
  if (isparam)
    XPUSHs(&PL_sv_yes);
  PUTBACK;
  perl_call_sv(cbv->entdcl_sv, G_DISCARD|G_VOID);

  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;

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 5);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpv((char*)name, 0)));
  PUSHs(sysid ? sv_2mortal(newUTF8SVpv((char*)sysid, 0)) : &PL_sv_undef);
  PUSHs(pubid ? sv_2mortal(newUTF8SVpv((char*)pubid, 0)) : &PL_sv_undef);
  PUSHs(hasinternal ? &PL_sv_yes : &PL_sv_no);
  PUTBACK;
  perl_call_sv(cbv->doctyp_sv, G_DISCARD|G_VOID);
  FREETMPS;
  LEAVE;
}  /* 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_sv(cbv->doctypfin_sv, G_DISCARD|G_VOID);
  FREETMPS;
  LEAVE;
}  /* End doctypeEnd */

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

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 4);
  PUSHs(cbv->self_sv);
  PUSHs(version ? sv_2mortal(newUTF8SVpv((char *)version, 0))
	: &PL_sv_undef);
  PUSHs(encoding ? sv_2mortal(newUTF8SVpv((char *)encoding, 0))
	: &PL_sv_undef);
  PUSHs(standalone == -1 ? &PL_sv_undef
	: sv_2mortal(standalone ? newSVpvn("yes", 3) : newSVpvn("no", 2)));
  PUTBACK;
  perl_call_sv(cbv->xmldec_sv, G_DISCARD|G_VOID);
  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;

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 6);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpv((char*) entity, 0)));
  PUSHs(base ? sv_2mortal(newUTF8SVpv((char*) base, 0)) : &PL_sv_undef);
  PUSHs(sv_2mortal(newUTF8SVpv((char*) sysid, 0)));
  PUSHs(pubid ? sv_2mortal(newUTF8SVpv((char*) pubid, 0)) : &PL_sv_undef);
  PUSHs(sv_2mortal(newUTF8SVpv((char*) notation, 0)));
  PUTBACK;
  perl_call_sv(cbv->unprsd_sv, G_DISCARD|G_VOID);

  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;

  ENTER;
  SAVETMPS;

  PUSHMARK(sp);
  EXTEND(sp, 5);
  PUSHs(cbv->self_sv);
  PUSHs(sv_2mortal(newUTF8SVpv((char*) name, 0)));
  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;
  perl_call_sv(cbv->notation_sv, G_DISCARD|G_VOID);

  FREETMPS;
  LEAVE;
}  /* End notationDecl */

static int
externalEntityRef(XML_Parser parser,
		  const char* open,
		  const char* base,
		  const char* sysid,
		  const char* pubid)
{
  dSP;

  int count;
  int ret = 0;
  int parse_done = 0;

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

  /* For parameter entities and DTD (context is NULL per expat docs),
     when the user did not explicitly request ParseParamEnt, silently
     treat the PE as empty and let expat continue processing subsequent
     DTD declarations normally.  See GH #53. */
  /* For parameter entities and DTD (context is NULL per expat docs),
     when the user did not explicitly request ParseParamEnt, treat the
     PE as empty content so expat continues dispatching subsequent DTD
     declarations to their proper handlers (Attlist, Element, etc.).

     We must actually create the sub-parser and feed it an empty document
     rather than simply returning 1, because expat needs the sub-parser's
     parse completion to finalize entity processing.  Without this, expat
     routes all subsequent DTD declarations to the Default handler instead
     of the dedicated handlers.  See GH #53 and GH #173. */
  if (open == NULL && !cbv->parseparam) {
    XML_Parser entpar = XML_ExternalEntityParserCreate(parser, open, 0);
    if (entpar) {
      XML_Parse(entpar, "", 0, 1);
      XML_ParserFree(entpar);
    }
    return 1;
  }

  if (! cbv->extent_sv)
    return 0;

  ENTER ;
  SAVETMPS ;
  PUSHMARK(sp);
  EXTEND(sp, pubid ? 4 : 3);
  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);
  if (pubid)
    PUSHs(sv_2mortal(newUTF8SVpv((char*) pubid, 0)));
  PUTBACK ;
  count = perl_call_sv(cbv->extent_sv, 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::Parser object");
      else {
	XML_Parser entpar;
	char *errmsg = (char *) 0;

	entpar = XML_ExternalEntityParserCreate(parser, open, 0);
	if (! entpar) {
	  append_error(parser,
	    "Couldn't create external entity sub-parser");
	  goto Extparse_Cleanup;
	}

	XML_SetBase(entpar, XML_GetBase(parser));

	sv_setiv(*pval, PTR2IV(entpar));

	cbv->p = entpar;

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

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

	  (void)POPs;
	  hold = SvPV(ERRSV, len);
	  New(326, errmsg, len + 1, char);
	  if (len)
	    Copy(hold, errmsg, len, char);
	  errmsg[len] = '\0';
	  goto Extparse_Cleanup;
	}

	if (count > 0)
	  ret = POPi;

	parse_done = 1;

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

	if (cbv->extfin_sv) {
	  PUSHMARK(sp);
	  PUSHs(cbv->self_sv);
	  PUTBACK;
	  perl_call_sv(cbv->extfin_sv, G_DISCARD|G_VOID);
	  SPAGAIN;
	}

	/* Use saved error from Do_External_Parse if available,
	   since the extfin callback above may have cleared ERRSV */
	if (errmsg)
	  append_error(parser, errmsg);
	else if (SvTRUE(ERRSV))
	  append_error(parser, SvPV_nolen(ERRSV));

	Safefree(errmsg);
      }
    }
  }

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

  PUTBACK ;
  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;
  PERL_UNUSED_VAR(unused);
  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::Parser::Expat::Encoding_Table", FALSE);
    if (! EncodingTable)
      croak("Can't find XML::Parser::Expat::Encoding_Table");
  }

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

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

    ENTER;
    SAVETMPS;
    PUSHMARK(sp);
    XPUSHs(sv_2mortal(newSVpvn(buff,namelen)));
    PUTBACK;
    perl_call_pv("XML::Parser::Expat::load_encoding", G_DISCARD|G_VOID);
    
    encinfptr = hv_fetch(EncodingTable, buff, namelen, 0);
    FREETMPS;
    LEAVE;

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

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

  enc = INT2PTR(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)
{
  CallbackVector *cbv = (CallbackVector*) userData;

  if (cbv->recstring) {
    sv_catpvn(cbv->recstring, (char *) string, len);
  }
  else {
    cbv->recstring = newUTF8SVpvn((char *) string, len);
  }
}  /* End recString */

static void
suspend_callbacks(CallbackVector *cbv) {
  if (SvTRUE(cbv->char_sv)) {
    XML_SetCharacterDataHandler(cbv->p,
				(XML_CharacterDataHandler) 0);
  }

  if (SvTRUE(cbv->proc_sv)) {
    XML_SetProcessingInstructionHandler(cbv->p,
					(XML_ProcessingInstructionHandler) 0);
  }

  if (SvTRUE(cbv->cmnt_sv)) {
    XML_SetCommentHandler(cbv->p,
			  (XML_CommentHandler) 0);
  }

  if (SvTRUE(cbv->startcd_sv)
      || SvTRUE(cbv->endcd_sv)) {
    XML_SetCdataSectionHandler(cbv->p,
			       (XML_StartCdataSectionHandler) 0,
			       (XML_EndCdataSectionHandler) 0);
  }

  if (SvTRUE(cbv->unprsd_sv)) {
    XML_SetUnparsedEntityDeclHandler(cbv->p,
				     (XML_UnparsedEntityDeclHandler) 0);
  }

  if (SvTRUE(cbv->notation_sv)) {
    XML_SetNotationDeclHandler(cbv->p,
			       (XML_NotationDeclHandler) 0);
  }

  if (SvTRUE(cbv->extent_sv)) {
    XML_SetExternalEntityRefHandler(cbv->p,
				    (XML_ExternalEntityRefHandler) 0);
  }

}  /* End suspend_callbacks */

static void
resume_callbacks(CallbackVector *cbv) {
  if (SvTRUE(cbv->char_sv)) {
    XML_SetCharacterDataHandler(cbv->p, characterData);
  }

  if (SvTRUE(cbv->proc_sv)) {
    XML_SetProcessingInstructionHandler(cbv->p, processingInstruction);
  }

  if (SvTRUE(cbv->cmnt_sv)) {
    XML_SetCommentHandler(cbv->p, commenthandle);
  }

  if (SvTRUE(cbv->startcd_sv)
      || SvTRUE(cbv->endcd_sv)) {
    XML_SetCdataSectionHandler(cbv->p, startCdata, endCdata);
  }

  if (SvTRUE(cbv->unprsd_sv)) {
    XML_SetUnparsedEntityDeclHandler(cbv->p, unparsedEntityDecl);
  }

  if (SvTRUE(cbv->notation_sv)) {
    XML_SetNotationDeclHandler(cbv->p, notationDecl);
  }

  if (SvTRUE(cbv->extent_sv)) {
    XML_SetExternalEntityRefHandler(cbv->p, externalEntityRef);
  }

}  /* End resume_callbacks */


MODULE = XML::Parser::Expat PACKAGE = XML::Parser::Expat	PREFIX = XML_

XML_Parser
XML_ParserCreate(self_sv, enc_sv, namespaces)
        SV *                    self_sv
	SV *			enc_sv
	int			namespaces
    CODE:
	{
	  CallbackVector *cbv;
	  char *enc = (char *) (SvTRUE(enc_sv) ? SvPV_nolen(enc_sv) : 0);
	  SV ** spp;

	  Newz(320, cbv, 1, CallbackVector);
	  cbv->self_sv = SvREFCNT_inc(self_sv);
	  Newz(325, cbv->st_serial_stack, 1024, unsigned int);
	  cbv->st_serial_stacksize = 1024;
	  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)) {
	    free_cbv(cbv);
	    croak("XML::Parser instance missing Context");
	  }

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

	  cbv->ns = (unsigned) namespaces;
	  if (namespaces)
	    {
	      spp = hv_fetch((HV*)SvRV(cbv->self_sv), "New_Prefixes", 12, 0);
	      if (! spp || ! *spp || !SvROK(*spp)) {
	        free_cbv(cbv);
	        croak("XML::Parser instance missing New_Prefixes");
	      }

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

	      spp = hv_fetch((HV*)SvRV(cbv->self_sv), "Namespace_Table",
			     15, FALSE);
	      if (! spp || ! *spp || !SvROK(*spp)) {
	        free_cbv(cbv);
	        croak("XML::Parser instance missing Namespace_Table");
	      }

	      cbv->nstab = (HV *) SvRV(*spp);

	      spp = hv_fetch((HV*)SvRV(cbv->self_sv), "Namespace_List",
			     14, FALSE);
	      if (! spp || ! *spp || !SvROK(*spp)) {
	        free_cbv(cbv);
	        croak("XML::Parser instance missing Namespace_List");
	      }

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

	      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_SetUnknownEncodingHandler(RETVAL, unknownEncoding, 0);
	  XML_SetExternalEntityRefHandler(RETVAL, externalEntityRef);

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

	  if (spp && SvTRUE(*spp))
	    cbv->parseparam = 1;

	  /* Always enable parameter entity parsing so that PE references
	     in the internal DTD subset don't cause expat to stop calling
	     specific declaration handlers (Attlist, Element, Entity, etc.).
	     When ParseParamEnt is not explicitly set, unresolvable PEs are
	     silently treated as empty in externalEntityRef(). */
	  XML_SetParamEntityParsing(RETVAL,
	    XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE);

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

	  if (spp && SvTRUE(*spp))
	    XML_UseForeignDTD(RETVAL, XML_TRUE);
	}
    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);

	  Safefree(cbv->st_serial_stack);


	  /* Clean up any SVs that we have */
	  /* (Note that self_sv must already be taken care of
	     or we couldn't be here */

	  if (cbv->recstring)
	    SvREFCNT_dec(cbv->recstring);

	  if (cbv->start_sv)
	    SvREFCNT_dec(cbv->start_sv);

	  if (cbv->end_sv)
	    SvREFCNT_dec(cbv->end_sv);

	  if (cbv->char_sv)
	    SvREFCNT_dec(cbv->char_sv);

	  if (cbv->proc_sv)
	    SvREFCNT_dec(cbv->proc_sv);

	  if (cbv->cmnt_sv)
	    SvREFCNT_dec(cbv->cmnt_sv);

	  if (cbv->dflt_sv)
	    SvREFCNT_dec(cbv->dflt_sv);

	  if (cbv->entdcl_sv)
	    SvREFCNT_dec(cbv->entdcl_sv);

	  if (cbv->eledcl_sv)
	    SvREFCNT_dec(cbv->eledcl_sv);

	  if (cbv->attdcl_sv)
	    SvREFCNT_dec(cbv->attdcl_sv);

	  if (cbv->doctyp_sv)
	    SvREFCNT_dec(cbv->doctyp_sv);

	  if (cbv->doctypfin_sv)
	    SvREFCNT_dec(cbv->doctypfin_sv);

	  if (cbv->xmldec_sv)
	    SvREFCNT_dec(cbv->xmldec_sv);

	  if (cbv->unprsd_sv)
	    SvREFCNT_dec(cbv->unprsd_sv);

	  if (cbv->notation_sv)
	    SvREFCNT_dec(cbv->notation_sv);

	  if (cbv->extent_sv)
	    SvREFCNT_dec(cbv->extent_sv);

	  if (cbv->extfin_sv)
	    SvREFCNT_dec(cbv->extfin_sv);

	  if (cbv->startcd_sv)
	    SvREFCNT_dec(cbv->startcd_sv);

	  if (cbv->endcd_sv)
	    SvREFCNT_dec(cbv->endcd_sv);

	  /* ================ */
	    
	  Safefree(cbv);
	  XML_ParserFree(parser);
	}

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

	  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)
	XML_Parser			parser
	SV *				ioref
	SV *				delim
    CODE:
	{
	  CallbackVector * cbv;

	  cbv = (CallbackVector *) XML_GetUserData(parser);
	  if (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, sv)
	XML_Parser			parser
	SV *				sv
    CODE:
	{
	  STRLEN len;
	  char *s = SvPV(sv, 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

SV *
XML_SetStartElementHandler(parser, start_sv)
	XML_Parser			parser
	SV *				start_sv
    CODE:
	{
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);
	  XMLP_UPD(start_sv);
	  PUSHRET;
	}

SV *
XML_SetEndElementHandler(parser, end_sv)
	XML_Parser			parser
	SV *				end_sv
    CODE:
	{
	  CallbackVector *cbv = (CallbackVector*) XML_GetUserData(parser);
	  XMLP_UPD(end_sv);
	  PUSHRET;
	}

SV *
XML_SetCharacterDataHandler(parser, char_sv)
	XML_Parser			parser
	SV *				char_sv
    CODE:
	{
	  XML_CharacterDataHandler charhndl = (XML_CharacterDataHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(char_sv);
	  if (SvTRUE(char_sv))
	    charhndl = characterData;

	  XML_SetCharacterDataHandler(parser, charhndl);
	  PUSHRET;
	}

SV *
XML_SetProcessingInstructionHandler(parser, proc_sv)
	XML_Parser			parser
	SV *				proc_sv
    CODE:
	{
	  XML_ProcessingInstructionHandler prochndl =
	    (XML_ProcessingInstructionHandler) 0;
	  CallbackVector* cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(proc_sv);
	  if (SvTRUE(proc_sv))
	    prochndl = processingInstruction;

	  XML_SetProcessingInstructionHandler(parser, prochndl);
	  PUSHRET;
	}

SV *
XML_SetCommentHandler(parser, cmnt_sv)
	XML_Parser			parser
	SV *				cmnt_sv
    CODE:
	{
	  XML_CommentHandler cmnthndl = (XML_CommentHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(cmnt_sv);
	  if (SvTRUE(cmnt_sv))
	    cmnthndl = commenthandle;

	  XML_SetCommentHandler(parser, cmnthndl);
	  PUSHRET;
	}

SV *
XML_SetDefaultHandler(parser, dflt_sv)
	XML_Parser			parser
	SV *				dflt_sv
    CODE:
	{
	  XML_DefaultHandler dflthndl = (XML_DefaultHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(dflt_sv);
	  if (SvTRUE(dflt_sv))
	    dflthndl = defaulthandle;

	  if (cbv->no_expand)
	    XML_SetDefaultHandler(parser, dflthndl);
	  else
	    XML_SetDefaultHandlerExpand(parser, dflthndl);

	  PUSHRET;
	}

SV *
XML_SetUnparsedEntityDeclHandler(parser, unprsd_sv)
	XML_Parser			parser
	SV *				unprsd_sv
    CODE:
	{
	  XML_UnparsedEntityDeclHandler unprsdhndl =
	    (XML_UnparsedEntityDeclHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(unprsd_sv);
	  if (SvTRUE(unprsd_sv))
	    unprsdhndl = unparsedEntityDecl;

	  XML_SetUnparsedEntityDeclHandler(parser, unprsdhndl);
	  PUSHRET;
	}

SV *
XML_SetNotationDeclHandler(parser, notation_sv)
	XML_Parser			parser
	SV *				notation_sv
    CODE:
	{
	  XML_NotationDeclHandler nothndlr = (XML_NotationDeclHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(notation_sv);
	  if (SvTRUE(notation_sv))
	    nothndlr = notationDecl;

	  XML_SetNotationDeclHandler(parser, nothndlr);
	  PUSHRET;
	}

SV *
XML_SetExternalEntityRefHandler(parser, extent_sv)
	XML_Parser			parser
	SV *				extent_sv
    CODE:
	{
	  XML_ExternalEntityRefHandler exthndlr =
	    (XML_ExternalEntityRefHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(extent_sv);
	  if (SvTRUE(extent_sv))
	    exthndlr = externalEntityRef;

	  XML_SetExternalEntityRefHandler(parser, exthndlr);
	  PUSHRET;
	}

SV *
XML_SetExtEntFinishHandler(parser, extfin_sv)
	XML_Parser			parser
	SV *				extfin_sv
    CODE:
	{
	  CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

	  /* There is no corresponding handler for this in expat. This is
	     called from the externalEntityRef function above after parsing
	     the external entity. */

	  XMLP_UPD(extfin_sv);
	  PUSHRET;
	}

	   
SV *
XML_SetEntityDeclHandler(parser, entdcl_sv)
	XML_Parser			parser
	SV *				entdcl_sv
    CODE:
	{
	  XML_EntityDeclHandler enthndlr =
	    (XML_EntityDeclHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(entdcl_sv);
	  if (SvTRUE(entdcl_sv))
	    enthndlr = entityDecl;

	  XML_SetEntityDeclHandler(parser, enthndlr);
	  PUSHRET;
	}

SV *
XML_SetElementDeclHandler(parser, eledcl_sv)
	XML_Parser			parser
	SV *				eledcl_sv
    CODE:
	{
	  XML_ElementDeclHandler eldeclhndlr =
	    (XML_ElementDeclHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(eledcl_sv);
	  if (SvTRUE(eledcl_sv))
	    eldeclhndlr = elementDecl;

	  XML_SetElementDeclHandler(parser, eldeclhndlr);
	  PUSHRET;
	}

SV *
XML_SetAttListDeclHandler(parser, attdcl_sv)
	XML_Parser			parser
	SV *				attdcl_sv
    CODE:
	{
	  XML_AttlistDeclHandler attdeclhndlr =
	    (XML_AttlistDeclHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(attdcl_sv);
	  if (SvTRUE(attdcl_sv))
	    attdeclhndlr = attributeDecl;

	  XML_SetAttlistDeclHandler(parser, attdeclhndlr);
	  PUSHRET;
	}

SV *
XML_SetDoctypeHandler(parser, doctyp_sv)
	XML_Parser			parser
	SV *				doctyp_sv
    CODE:
	{
	  XML_StartDoctypeDeclHandler dtsthndlr =
	    (XML_StartDoctypeDeclHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(doctyp_sv);
	  if (SvTRUE(doctyp_sv))
	    dtsthndlr = doctypeStart;

	  XML_SetStartDoctypeDeclHandler(parser, dtsthndlr);
	  PUSHRET;
	}

SV *
XML_SetEndDoctypeHandler(parser, doctypfin_sv)
	XML_Parser parser
	SV * doctypfin_sv     
    CODE:
	{
	  XML_EndDoctypeDeclHandler dtendhndlr =
	    (XML_EndDoctypeDeclHandler) 0;
	  CallbackVector * cbv = (CallbackVector*) XML_GetUserData(parser);

	  XMLP_UPD(doctypfin_sv);
	  if (SvTRUE(doctypfin_sv))
	    dtendhndlr = doctypeEnd;

	  XML_SetEndDoctypeDeclHandler(parser, dtendhndlr);
	  PUSHRET;
	}


SV *
XML_SetXMLDeclHandler(parser, xmldec_sv)
	XML_Parser			parser
	SV *				xmldec_sv
    CODE:
	{
	  XML_XmlDeclHandler xmldechndlr =
	    (XML_XmlDeclHandler) 0;
	  CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

	  XMLP_UPD(xmldec_sv);
	  if (SvTRUE(xmldec_sv))
	    xmldechndlr = xmlDecl;

	  XML_SetXmlDeclHandler(parser, xmldechndlr);
	  PUSHRET;
	}


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

	  if (! SvOK(base)) {
	    b = (char *) 0;
	  }
	  else {
	    b = SvPV_nolen(base);
	  }

	  XML_SetBase(parser, b);
	}	


void
XML_GetBase(parser)
	XML_Parser			parser
    PPCODE:
	{
	  const char *ret = XML_GetBase(parser);
	  if (ret) {
	    XPUSHs(sv_2mortal(newSVpv(ret, 0)));
	  }
	  else {
	    XPUSHs(&PL_sv_undef);
	  }
	}

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)));

SV *
GenerateNSName(name, xml_namespace, table, list)
	SV *				name
	SV *				xml_namespace
	SV *				table
	SV *				list
    CODE:
	{
	  STRLEN	nmlen, nslen;
	  char *	nmstr;
	  char *	nsstr;
	  char *	buff;
	  char *	bp;
	  char *	blim;

	  nmstr = SvPV(name, nmlen);
	  nsstr = SvPV(xml_namespace, nslen);

	  /* Form a namespace-name string that looks like expat's */
	  New(321, buff, nmlen + nslen + 2, char);
	  bp = buff;
	  blim = bp + nslen;
	  while (bp < blim)
	    *bp++ = *nsstr++;
	  *bp++ = NSDELIM;
	  blim = bp + nmlen;
	  while (bp < blim)
	    *bp++ = *nmstr++;
	  *bp = '\0';

	  RETVAL = gen_ns_name(buff, (HV *) SvRV(table), (AV *) SvRV(list));
	  Safefree(buff);
	}	
    OUTPUT:
	RETVAL

void
XML_DefaultCurrent(parser)
	XML_Parser			parser
    CODE:
	{
	  XML_DefaultCurrent(parser);
	}

SV *
XML_RecognizedString(parser)
	XML_Parser			parser
    CODE:
	{
	  XML_DefaultHandler dflthndl = (XML_DefaultHandler) 0;
	  CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);

	  if (cbv->dflt_sv) {
	    dflthndl = defaulthandle;
	  }

	  if (cbv->recstring) {
	    sv_setpvn(cbv->recstring, "", 0);
	  }

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

	  if (cbv->no_expand)
	    XML_SetDefaultHandler(parser, dflthndl);
	  else
	    XML_SetDefaultHandlerExpand(parser, dflthndl);

	  RETVAL = newSVsv(cbv->recstring);
	}
    OUTPUT:
	RETVAL

int
XML_GetErrorCode(parser)
	XML_Parser			parser

SV *
XML_GetCurrentLineNumber(parser)
	XML_Parser			parser
    CODE:
	{
	    XML_Size line = XML_GetCurrentLineNumber(parser);
#if (defined(XML_LARGE_SIZE) && UVSIZE < 8)
	    /* XML_Size is unsigned long long but UV is 32-bit; use NV */
	    RETVAL = newSVnv((NV)line);
#else
	    RETVAL = newSVuv((UV)line);
#endif
	}
    OUTPUT:
	RETVAL


SV *
XML_GetCurrentColumnNumber(parser)
	XML_Parser			parser
    CODE:
	{
	    XML_Size col = XML_GetCurrentColumnNumber(parser);
#if (defined(XML_LARGE_SIZE) && UVSIZE < 8)
	    /* XML_Size is unsigned long long but UV is 32-bit; use NV */
	    RETVAL = newSVnv((NV)col);
#else
	    RETVAL = newSVuv((UV)col);
#endif
	}
    OUTPUT:
	RETVAL

SV *
XML_GetCurrentByteIndex(parser)
	XML_Parser			parser
    CODE:
	{
	    XML_Index byte_index = XML_GetCurrentByteIndex(parser);
#if (defined(XML_LARGE_SIZE) && IVSIZE < 8)
	    /* XML_Index is long long but IV is 32-bit; use NV to avoid overflow */
	    RETVAL = newSVnv((NV)byte_index);
#else
	    RETVAL = newSViv((IV)byte_index);
#endif
	}
    OUTPUT:
	RETVAL

int
XML_GetCurrentByteCount(parser)
	XML_Parser			parser

int
XML_GetSpecifiedAttributeCount(parser)
	XML_Parser			parser

void
XML_ErrorString(code)
	int				code
    PPCODE:
	{
	  const char *ret = XML_ErrorString(code);
	  XPUSHs(sv_2mortal(newSVpv(ret, 0)));
	}

void
XML_ExpatVersion()
    PPCODE:
	const XML_LChar *ver = XML_ExpatVersion();
	XPUSHs(sv_2mortal(newSVpv((const char *)ver, 0)));

void
XML_ExpatVersionInfo()
    PPCODE:
	XML_Expat_Version info = XML_ExpatVersionInfo();
	EXTEND(SP, 6);
	PUSHs(sv_2mortal(newSVpv("major", 5)));
	PUSHs(sv_2mortal(newSViv(info.major)));
	PUSHs(sv_2mortal(newSVpv("minor", 5)));
	PUSHs(sv_2mortal(newSViv(info.minor)));
	PUSHs(sv_2mortal(newSVpv("micro", 5)));
	PUSHs(sv_2mortal(newSViv(info.micro)));

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::Parser::Encinfo", (void *) entry);
	  
	      if (! EncodingTable) {
		EncodingTable
		  = perl_get_hv("XML::Parser::Expat::Encoding_Table",
				FALSE);
		if (! EncodingTable)
		  croak("Can't find XML::Parser::Expat::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

SV *
XML_SetStartCdataHandler(parser, startcd_sv)
	XML_Parser			parser
	SV *				startcd_sv
    CODE:
	{
	  CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);
	  XML_StartCdataSectionHandler scdhndl =
	    (XML_StartCdataSectionHandler) 0;

	  XMLP_UPD(startcd_sv);
	  if (SvTRUE(startcd_sv))
	    scdhndl = startCdata;

	  XML_SetStartCdataSectionHandler(parser, scdhndl);
	  PUSHRET;
	}

SV *
XML_SetEndCdataHandler(parser, endcd_sv)
	XML_Parser			parser
	SV *				endcd_sv
    CODE:
	{
	  CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);
	  XML_EndCdataSectionHandler ecdhndl =
	    (XML_EndCdataSectionHandler) 0;

	  XMLP_UPD(endcd_sv);
	  if (SvTRUE(endcd_sv))
	    ecdhndl = endCdata;

	  XML_SetEndCdataSectionHandler(parser, ecdhndl);
	  PUSHRET;
	}

void
XML_UnsetAllHandlers(parser)
	XML_Parser			parser
    CODE:
	{
	  CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);
	  
	  suspend_callbacks(cbv);
	  if (cbv->ns) {
	    XML_SetNamespaceDeclHandler(cbv->p,
					(XML_StartNamespaceDeclHandler) 0,
					(XML_EndNamespaceDeclHandler) 0);
	  }

	  XML_SetElementHandler(parser,
				(XML_StartElementHandler) 0,
				(XML_EndElementHandler) 0);

	  XML_SetUnknownEncodingHandler(parser,
					(XML_UnknownEncodingHandler) 0,
					(void *) 0);
	}

int
XML_ElementIndex(parser)
        XML_Parser                      parser
    CODE:
        {
          CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);
          RETVAL = cbv->st_serial_stack[cbv->st_serial_stackptr];
        }
    OUTPUT:
        RETVAL

void
XML_SkipUntil(parser, index)
         XML_Parser			parser
         unsigned int			index
    CODE:
        {
          CallbackVector * cbv = (CallbackVector *) XML_GetUserData(parser);
	  if (index <= cbv->st_serial)
	    return;
	  cbv->skip_until = index;
	  suspend_callbacks(cbv);
	}

int
XML_Do_External_Parse(parser, result)
	XML_Parser			parser
	SV *				result
    CODE:
	{
	  RETVAL = 0;

	  if (SvROK(result) && SvOBJECT(SvRV(result))) {
	    RETVAL = parse_stream(parser, result);
	  }
	  else if (SvROK(result) && isGV(SvRV(result))) {
	    /* Lexical filehandle (open my $fh) - a reference to a glob */
	    RETVAL = parse_stream(parser,
				  sv_2mortal(newRV((SV*) GvIOp((GV*)SvRV(result)))));
	  }
	  else if (isGV(result)) {
	    RETVAL = parse_stream(parser,
				  sv_2mortal(newRV((SV*) GvIOp(result))));
	  }
	  else if (SvPOK(result)) {
	    STRLEN  eslen;
	    char *entstr = SvPV(result, eslen);

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

#if (defined(XML_DTD) || (defined(XML_GE) && XML_GE == 1)) \
    && defined(XML_MAJOR_VERSION) \
    && (XML_MAJOR_VERSION > 2 \
        || (XML_MAJOR_VERSION == 2 && XML_MINOR_VERSION >= 4))

int
XML_SetBillionLaughsAttackProtectionMaximumAmplification(parser, maxamp)
	XML_Parser			parser
	float				maxamp
    CODE:
	RETVAL = (int) XML_SetBillionLaughsAttackProtectionMaximumAmplification(
	    parser, maxamp);
    OUTPUT:
	RETVAL

int
XML_SetBillionLaughsAttackProtectionActivationThreshold(parser, threshold)
	XML_Parser			parser
	unsigned long			threshold
    CODE:
	RETVAL = (int) XML_SetBillionLaughsAttackProtectionActivationThreshold(
	    parser, (unsigned long long) threshold);
    OUTPUT:
	RETVAL

#endif

#ifdef HAVE_XML_SETALLOCTRACKER

int
XML_SetAllocTrackerMaximumAmplification(parser, maxamp)
	XML_Parser			parser
	float				maxamp
    CODE:
	RETVAL = (int) XML_SetAllocTrackerMaximumAmplification(
	    parser, maxamp);
    OUTPUT:
	RETVAL

int
XML_SetAllocTrackerActivationThreshold(parser, threshold)
	XML_Parser			parser
	unsigned long			threshold
    CODE:
	RETVAL = (int) XML_SetAllocTrackerActivationThreshold(
	    parser, (unsigned long long) threshold);
    OUTPUT:
	RETVAL

#endif

#if defined(XML_MAJOR_VERSION) \
    && (XML_MAJOR_VERSION > 2 \
        || (XML_MAJOR_VERSION == 2 \
            && (XML_MINOR_VERSION > 6 \
                || (XML_MINOR_VERSION == 6 && XML_MICRO_VERSION >= 0))))

int
XML_SetReparseDeferralEnabled(parser, enabled)
	XML_Parser			parser
	int				enabled
    CODE:
	RETVAL = (int) XML_SetReparseDeferralEnabled(parser,
	    (XML_Bool) enabled);
    OUTPUT:
	RETVAL

#endif