/*
 * e4xmlparser.cpp --
 *
 *	This file contains the implementation of the e4_XMLParser
 *	class defined in e4xml.h.
 *
 *	Authors: Jacob Levy and Jean-Claude Wippler.
 *		 jyl@best.com	jcw@equi4.com
 *
 *	Copyright: JYL Software, Inc., (c) 2000-2003.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE, EVEN IF
 * JYL SOFTWARE INC. IS MADE AWARE OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "e4xml.h"

/*
 ***************************************************************************
 *                                                                         *
 * Implementation of the e4_XMLParser class:                               *
 *                                                                         *
 ***************************************************************************
 */

/*
 * This procedure is called for the start of each XML element.
 */

void
e4_XMLParser::HandleStartElement(void *userData,
				 const char *name,
				 const char **attributes)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the element beginning.
     */

    (void) p->ProcessElementBegin(name, attributes);
}

/*
 * This procedure is called for the end of each XML element.
 */

void
e4_XMLParser::HandleEndElement(void *userData, const char *name)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    (void) p->ProcessElementEnd(name);
}

/*
 * This procedure is called to parse an XML comment.
 */

void
e4_XMLParser::HandleComment(void *userData, const char *comment)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the comment.
     */

    (void) p->ProcessComment(comment);
}

/*
 * This procedure handles the start of a CDATA section in the XML input.
 */

void
e4_XMLParser::HandleStartCDATA(void *userData)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    (void) p->ProcessCDATABegin();
}

/*
 * This procedure handles the end of a CDATA section.
 */

void
e4_XMLParser::HandleEndCDATA(void *userData)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    (void) p->ProcessCDATAEnd();
}

/*
 * This procedure handles a processing instruction XML input element.
 */

void
e4_XMLParser::HandleProcessingInstructions(void *userData,
					   const char *target,
					   const char *data)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;
    e4_Node n;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    (void) p->ProcessInstructions(target, data);
}

/*
 * This procedure is called to handle an XML declaration.
 */

void
e4_XMLParser::HandleXMLDeclaration(void *userData,
				   const char *version,
				   const char *encoding,
				   int standalone)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;
    e4_Node n;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    (void) p->ProcessXMLDeclaration(version, encoding, standalone);
}

/*
 * This procedure is called to handle the start of a document decl XML
 * input element.
 */

void
e4_XMLParser::HandleStartDocType(void *userData,
				 const char *doctypename,
				 const char *sysid,
				 const char *pubid,
				 int hasinternalsubset)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    (void) p->ProcessDTDBegin(doctypename, sysid, pubid, hasinternalsubset);
}

/*
 * This procedure is called to handle the end of a doctype declaration.
 */

void
e4_XMLParser::HandleEndDocType(void *userData)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    (void) p->ProcessDTDEnd();
}

/*
 * This procedure is called to handle default data (data that would otherwise
 * not be handled at all).
 */

void
e4_XMLParser::HandleDefaultData(void *userData, const char *data, int len)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the data.
     */

    (void) p->ProcessDefaultData(data, len);
}

/*
 * This procedure is called to handle character data contained within
 * XML elements.
 */

void
e4_XMLParser::HandleCharData(void *userData, const char *data, int len)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the actual data.
     */

    (void) p->ProcessCharData(data, len);
}

/*
 * This procedure is called to handle the start of a namespace scope.
 */

void
e4_XMLParser::HandleStartNamespaceDecl(void *userData,
				       const char *prefix,
				       const char *uri)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the actual data.
     */

    (void) p->ProcessStartNamespaceDecl(prefix, uri);
}

/*
 * This procedure is called to handle the start of a namespace scope.
 */

void
e4_XMLParser::HandleEndNamespaceDecl(void *userData,
				     const char *prefix)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the actual data.
     */

    (void) p->ProcessEndNamespaceDecl(prefix);
}

/*
 * This procedure is called to handle the declaration of an unparsed entity.
 */

void
e4_XMLParser::HandleUnparsedEntityDecl(void *userData,
				       const char *entityName,
				       const char *base,
				       const char *systemID,
				       const char *publicID,
				       const char *notationName)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the actual data.
     */

    (void) p->ProcessUnparsedEntityDecl(entityName, 
					base,
					systemID,
					publicID,
					notationName);
}

/*
 * This procedure is called to handle a notation declaration.
 */

void
e4_XMLParser::HandleNotationDecl(void *userData,
				 const char *notationName,
				 const char *base,
				 const char *systemID,
				 const char *publicID)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the declaration.
     */

    (void) p->ProcessNotationDecl(notationName, base, systemID, publicID);
}

#ifdef	NOTDEF
/*
 * This procedure is called to handle a skipped entity. A skipped entity is
 * an entity for which no declaration is given and which occurs in a situation
 * where its occurrence is not an error.
 */

void
e4_XMLParser::HandleSkippedEntity(void *userData,
				  const char *entityName,
				  int isParameterEntity)
{
    e4_XMLParser *p = (e4_XMLParser *) userData;

    /*
     * If there was an error before this call, do nothing more so that
     * the state will be at least partially correct!
     */

    if ((p == NULL) || (p->HasError() == true)) {
	return;
    }

    /*
     * Process the skipped entity.
     */

    (void) p->ProcessSkippedEntity(entityName, isParameterEntity);
}
#endif

/*
 * This constructor creates an empty XML parser.
 */

e4_XMLParser::e4_XMLParser()
    : ready(false),
      n(invalidNode),
      s(invalidStorage),
      inVertex(false),
      parser(NULL),
      depth(0),
      started(false),
      error(false),
      errorString(NULL),
      base64bytes(NULL),
      inputProcessor(&defaultXMLInputProcessor),
      nodeVertexCreator(&defaultNodeVertexCreator)
{
    inputProcessor->SetParser(this);
    nodeVertexCreator->SetParser(this);
}

/*
 * This constructor creates an XML parser from a given node and
 * stores all XML input in an e4Graph under the given node.
 */

e4_XMLParser::e4_XMLParser(e4_Node nn)
    : ready(true),
      n(nn),
      inVertex(false),
      parser(NULL),
      depth(0),
      started(false),
      error(false),
      errorString(NULL),
      base64bytes(NULL),
      inputProcessor(&defaultXMLInputProcessor),
      nodeVertexCreator(&defaultNodeVertexCreator)
{
    ConstructParser();
    (void) n.GetStorage(s);
    inputProcessor->SetParser(this);
    nodeVertexCreator->SetParser(this);
}

/*
 * The destructor deletes the instance parser if one was created, and
 * sets the instance node to invalidNode to ensure that the refcount is
 * decremented properly.
 */

e4_XMLParser::~e4_XMLParser()
{
    if (parser != NULL) {
	XML_ParserFree(parser);
    }
    n = invalidNode;
    s = invalidStorage;
    if (base64bytes != NULL) {
	free(base64bytes);
    }
}

/*
 * Is this parse finished?
 */

bool
e4_XMLParser::Finished()
{
    if ((started == true) && (depth <= 0)) {
	return true;
    }
    return false;
}

/*
 * Get the storage associated with this parser.
 */

bool
e4_XMLParser::GetStorage(e4_Storage &ss) const
{
    ss = s;
    return true;
}

/*
 * This operation assigns a node to the parser. This allows the parser
 * to switch where it is going to insert new elements.
 *
 * NOTE: Care must be taken to only switch nodes between parse operations
 * and not during a parse operation, or unpredictable behavior may result.
 */

void
e4_XMLParser::SetNode(e4_Node nn)
{
    if (inVertex == false) {
	n = nn;
	n.GetStorage(s);
    } else {
	FlagError("Can't set node while inside vertex!");
    }
}

/*
 * This operation returns the current node in nn.
 */

bool
e4_XMLParser::GetNode(e4_Node &nn) const
{
    nn = n;
    return true;
}

/*
 * This operation begins a vertex-add.
 */
bool
e4_XMLParser::EnterVertex()
{
    if (inVertex) {
	FlagError("Already inside a vertex!");
	return false;
    }
    inVertex = true;
    return true;
}

/*
 * This operation checks whether we're currently inside a vertex-add.
 */

bool
e4_XMLParser::InVertex() const
{
    return inVertex;
}

/*
 * This operation finishes a vertex-add.
 */

void
e4_XMLParser::ExitVertex()
{
    if (inVertex == false) {
	FlagError("Not in vertex-add!");
    }
    inVertex = false;
}

/*
 * This operation constructs a new parser from the file pointer and
 * node stored already in the instance. It returns true if the construction
 * succeeded.
 */

bool
e4_XMLParser::ConstructParser()
{
    error = false;
    errorString = NULL;
    if (!n.IsValid()) {
	ready = false;
    } else {
        if (parser != NULL) {
	    XML_ParserFree(parser);
	}
	parser = XML_ParserCreate(NULL);
	if (parser == NULL) {
	    ready = false;
	} else {
	    ready = true;
	    XML_SetUserData(parser, this);
	    XML_SetElementHandler(parser, 
				  HandleStartElement,
				  HandleEndElement);
	    XML_SetCommentHandler(parser, HandleComment);
	    XML_SetCdataSectionHandler(parser,
				       HandleStartCDATA,
				       HandleEndCDATA);
	    XML_SetProcessingInstructionHandler(parser,
						HandleProcessingInstructions);
	    XML_SetXmlDeclHandler(parser, HandleXMLDeclaration);
	    XML_SetCharacterDataHandler(parser, HandleCharData);
	    XML_SetDoctypeDeclHandler(parser,
				      HandleStartDocType,
				      HandleEndDocType);
	    XML_SetDefaultHandlerExpand(parser, HandleDefaultData);
	    XML_SetUnparsedEntityDeclHandler(parser,
					     HandleUnparsedEntityDecl);
	    XML_SetNotationDeclHandler(parser, HandleNotationDecl);
#ifdef	NOTDEF
	    XML_SetSkippedEntityHandler(parser, HandleSkippedEntity);
#endif

	    XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
	}
    }
    return ready;
}

/*
 * This operation parses a buffer of data.
 */

bool
e4_XMLParser::Parse(char *buf, size_t len)
{
    if (!ready) {
	return false;
    }
    error = false;
    started = true;

    /*
     * Parse and check for Expat errors as well as errors generated from
     * the e4xml routines.
     */

    if (!XML_Parse(parser, buf, len, false) || (error == true)) {
	error = true;
	if (errorString == NULL) {
	    FlagError("Input following XML expression");
	}
	return false;
    }
    return true;
}
    
/*
 * This operation flags an error and stores an error string into the
 * parser.
 */

void
e4_XMLParser::FlagError(const char *msg)
{
    char *format = "Error: %s (line %d, column %d, byte %d)";

    error = true;
    errorString = new char[50 /* strlen(format) */ + strlen(msg) + 64];
    sprintf(errorString,
	    format,
	    msg,
	    XML_GetCurrentLineNumber(parser),
	    XML_GetCurrentColumnNumber(parser),
	    XML_GetCurrentByteIndex(parser));
}

/*
 * This operation returns true if there was an error in a previous
 * operation that has not yet been cleared.
 */

bool
e4_XMLParser::HasError()
{
    return error;
}

/*
 * This operation retrieves the error string if there was an error.
 */

const char *
e4_XMLParser::ErrorString()
{
    if (errorString == NULL) {
	return "";
    }
    return (const char *) errorString;
}

/*
 * This operation clears an error state from the parser.
 */

void
e4_XMLParser::ClearError()
{
    error = false;
    errorString = NULL;
}

/*
 * This operation decodes a BASE64 encoded string into a binary value.
 */

byte *
e4_XMLParser::Base64_Decode(const char *base64str, int *len)
{
    if (base64bytes != NULL) {
	free(base64bytes);
    }
    base64bytes = base64_decode(base64str, len);
    return base64bytes;
}

/*
 * Process the declaration of an unparsed entity.
 */

bool
e4_XMLParser::ProcessUnparsedEntityDecl(const char *entityName,
					const char *base,
					const char *systemID,
					const char *publicID,
					const char *notationName)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Process the declaration:
     */

    return GetXMLProcessor()->ProcessUnparsedEntityDecl(entityName,
							base,
							systemID,
							publicID,
							notationName);
}

/*
 * Process a notation declaration.
 */

bool
e4_XMLParser::ProcessNotationDecl(const char *notationName,
				  const char *base,
				  const char *systemID,
				  const char *publicID)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Process the declaration:
     */

    return GetXMLProcessor()->ProcessNotationDecl(notationName,
						  base,
						  systemID,
						  publicID);
}

/*
 * Process a skipped entity.
 */

bool
e4_XMLParser::ProcessSkippedEntity(const char *entityName,
				   int isParameterEntity)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Process the skipped entity:
     */

    return GetXMLProcessor()->ProcessSkippedEntity(entityName,
						   isParameterEntity);
}

/*
 * Process default data.
 */

bool
e4_XMLParser::ProcessDefaultData(const char *data, int len)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Process the unclassified data.
     */

    return GetXMLProcessor()->ProcessUnclassifiedData(data, len);
}

/*
 * Process character data.
 */

bool
e4_XMLParser::ProcessCharData(const char *data, int len)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Process the character data.
     */

    return GetXMLProcessor()->ProcessCharData(data, len);
}

/*
 * Process the start of a namespace scope.
 */

bool
e4_XMLParser::ProcessStartNamespaceDecl(const char *prefix, const char *uri)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Process the start of the namespace scope.
     */

    return GetXMLProcessor()->ProcessStartNamespaceDecl(prefix, uri);
}

/*
 * Process the end of a namespace scope.
 */

bool
e4_XMLParser::ProcessEndNamespaceDecl(const char *prefix)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Process the start of the namespace scope.
     */

    return GetXMLProcessor()->ProcessEndNamespaceDecl(prefix);
}

/*
 * This operation starts the processing of an XML element.
 */

bool
e4_XMLParser::ProcessElementBegin(const char *name, const char **attributes)
{

    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Then process the element beginning.
     */

    return GetXMLProcessor()->ProcessElementBegin(name, attributes);
}

/*
 * Process an XML element end.
 */

bool
e4_XMLParser::ProcessElementEnd(const char *name)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Then process the element beginning.
     */

    return GetXMLProcessor()->ProcessElementEnd(name);
}

/*
 * Process an XML comment
 */

bool
e4_XMLParser::ProcessComment(const char *comment)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Then process the element beginning.
     */

    return GetXMLProcessor()->ProcessComment(comment);
}

/*
 * Process the beginning of a CDATA element.
 */

bool
e4_XMLParser::ProcessCDATABegin()
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * If we're in the process of adding a vertex, error out.
     */

    if (InVertex()) {
	FlagError("In vertex-add, cannot add CDATA section");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Start the CDATA section.
     */

    return GetXMLProcessor()->ProcessCDATABegin();
}

/*
 * Process the end of a CDATA section.
 */

bool
e4_XMLParser::ProcessCDATAEnd()
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * If we're in the process of adding a vertex, error out.
     */

    if (InVertex()) {
	FlagError("In vertex-add, cannot close CDATA section");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Process the end of the CDATA section.
     */

    return GetXMLProcessor()->ProcessCDATAEnd();
}

/*
 * Process XML processing instructions.
 */

bool
e4_XMLParser::ProcessInstructions(const char *target, const char *data)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * If we're in the process of adding a vertex, error out.
     */

    if (InVertex()) {
	FlagError("In vertex-add, cannot process XML processing instructions");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Process the XML instructions.
     */

    return GetXMLProcessor()->ProcessInstructions(target, data);
}

/*
 * Process XML declaration.
 */

bool
e4_XMLParser::ProcessXMLDeclaration(const char *version,
				    const char *encoding,
				    int standalone)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * If we're in the process of adding a vertex, error out.
     */

    if (InVertex()) {
	FlagError("In vertex-add, cannot process XML declaration");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Process the XML declaration.
     */

    return GetXMLProcessor()->ProcessXMLDeclaration(version, 
						    encoding,
						    standalone);
}

/*
 * Process the start of an XML DTD.
 */

bool
e4_XMLParser::ProcessDTDBegin(const char *doctypename,
			      const char *sysid,
			      const char *pubid,
			      int hasinternalsubset)
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * If we're in the process of adding a vertex, error out.
     */

    if (InVertex()) {
	FlagError("In vertex-add, cannot process XML DTD");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Process the beginning of the DTD element.
     */

    return GetXMLProcessor()->ProcessDTDBegin(doctypename,
					      sysid,
					      pubid,
					      hasinternalsubset);
}

/*
 * Process the end of an XML DTD.
 */

bool
e4_XMLParser::ProcessDTDEnd()
{
    if (!n.IsValid()) {
	FlagError("Invalid node");
	return false;
    }

    /*
     * If we're in the process of adding a vertex, error out.
     */

    if (InVertex()) {
	FlagError("In vertex-add, cannot process end of XML DTD");
	return false;
    }

    /*
     * Finish any open unclassified data.
     */

    (void) GetXMLProcessor()->ProcessUnclassifiedData(NULL, 0);

    /*
     * Finish any open character data.
     */

    (void) GetXMLProcessor()->ProcessCharData(NULL, 0);

    /*
     * Start the end of the DTD element.
     */

    return GetXMLProcessor()->ProcessDTDEnd();
}

/*
 * Set the value of the new style vertex saved in the parser to the string
 * retrieved from the passed dynamic string.
 */

bool
e4_XMLParser::AssignVertex(e4_DString &ds)
{
    e4_Vertex v;
    const void *base64bytes;
    int len;

    if (savedvertex == invalidVertex) {
	return false;
    }
    v = savedvertex;
    savedvertex = invalidVertex;

    if (v.Type() == E4_VTSTRING) {
        v.Set(ds.Get());

	/*
	 * Finished adding the new vertex, fire the completion event.
	 */

	CauseVertexCompletionEvent(v, NULL);

	return true;
    }

    if (v.Type() == E4_VTBINARY) {
	base64bytes = (const void *) base64_decode(ds.Get(), &len);
	if (base64bytes == NULL) {
	    return false;
	}
	v.Set(base64bytes, len);
	free((char *) base64bytes);

	/*
	 * Finished adding the new vertex, fire the completion event.
	 */

	CauseVertexCompletionEvent(v, NULL);

	return true;
    }

    return false;
}

/*
 * Mechanism for generating callbacks on completion of vertex parsing.
 */

/*
 * This variable contains the event ID for the vertex completion event.
 */

static int vertexCompleteCB = -1;

bool
e4_XMLParser::DeclareVertexCompletionCallback(e4_CallbackFunction fn,
					      void *clientData)
{
    /*
     * Ensure the event code is defined.
     */

    if ((vertexCompleteCB == -1) ||
	!e4_Storage::IsEventCodeDefined(vertexCompleteCB)) {
	if (!e4_Storage::DefineEventCode(vertexCompleteCB)) {
	    return false;
	}
    }

    /*
     * Only install the callback if the storage is valid.
     */

    if (!s.IsValid() || !s.DeclareCallback(vertexCompleteCB, fn, clientData)) {
	return false;
    }

    return true;
}

bool
e4_XMLParser::DeleteVertexCompletionCallback(e4_CallbackFunction fn,
					     void *clientData)
{
    /*
     * Only do something if the event code is defined.
     */

    if ((vertexCompleteCB == -1) ||
	!e4_Storage::IsEventCodeDefined(vertexCompleteCB)) {
	return false;
    }

    /*
     * Only do something if the storage is valid.
     */

    if (!s.IsValid() || !s.DeleteCallback(vertexCompleteCB, fn, clientData)) {
	return false;
    }
    return true;
}

bool
e4_XMLParser::CauseVertexCompletionEvent(const e4_Vertex &v, void *csdata)
{
    /*
     * Only do something if the event code is defined.
     */

    if ((vertexCompleteCB == -1) ||
	!e4_Storage::IsEventCodeDefined(vertexCompleteCB)) {
	return false;
    }

    /*
     * Only do something if the storage is valid.
     */

    if (!s.IsValid() || !s.CauseEvent(vertexCompleteCB, v, csdata)) {
	return false;
    }
    return true;
}

					  

