/*
 * e4xmlgenerator.cpp --
 *
 *	This file contains the implementation of the e4_XMLGenerator 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"

/*
 * This constructs an empty XML generator.
 */

e4_XMLGenerator::e4_XMLGenerator()
    : sn(invalidNode),
      se(NULL),
      nodesSeen(NULL),
      nodeCounter(0),
      ready(false),
      base64str(NULL),
      exportPureXML(false),
      firstGeneration(true),
      error(false),
      errorString(NULL),
      outputStream(&defaultXMLOutputStream),
      outputProcessor(&defaultXMLOutputProcessor)
{
    outputProcessor->SetGenerator(this);
    outputProcessor->SetOutputStream(outputStream);
    Reset();
}

/*
 * This constructor produces a fully initialized (ready to produce)
 * XML Generator.
 */

e4_XMLGenerator::e4_XMLGenerator(e4_Node nn, char *sen)
    : sn(invalidNode),
      se(NULL),
      nodeCounter(0),
      nodesSeen(NULL),
      ready(false),
      base64str(NULL),
      exportPureXML(false),
      firstGeneration(true),
      error(false),
      errorString(NULL),
      outputStream(&defaultXMLOutputStream),
      outputProcessor(&defaultXMLOutputProcessor)
{
    SetElementNameAndNode(sen, nn);
    outputProcessor->SetGenerator(this);
    outputProcessor->SetOutputStream(outputStream);
    Reset();
}

/*
 * This constructor produces a fully initialized (ready to produce)
 * XML Generator. It also sets the flag controlling whether pure
 * XML or slightly marked up XML is exported.
 */

e4_XMLGenerator::e4_XMLGenerator(e4_Node nn, char *sen, bool ex)
    : sn(invalidNode),
      se(NULL),
      nodeCounter(0),
      nodesSeen(NULL),
      ready(false),
      base64str(NULL),
      exportPureXML(ex),
      firstGeneration(true),
      error(false),
      errorString(NULL),
      outputStream(&defaultXMLOutputStream),
      outputProcessor(&defaultXMLOutputProcessor)
{
    SetElementNameAndNode(sen, nn);
    outputProcessor->SetGenerator(this);
    outputProcessor->SetOutputStream(outputStream);
    Reset();
}

/*
 * Destructor:
 */

e4_XMLGenerator::~e4_XMLGenerator()
{
    if (se != NULL) {
	free(se);
    }
    if (base64str != NULL) {
	free(base64str);
    }
    if (errorString != NULL) {
	delete [] errorString;
    }
    e4_DeleteHashTable(nodesSeen);
}

/*
 * Set the starting node.
 */

void
e4_XMLGenerator::SetNode(e4_Node nn)
{
    sn = nn;
    Reset();
}

/*
 * Set the element name.
 */

void
e4_XMLGenerator::SetElementName(char *sen)
{
    if (se != NULL) {
	free(se);
    }
    if (sen == NULL) {
	se = NULL;
    } else {
	se = (char *) malloc(strlen(sen) + 1);
	strcpy(se, sen);
    }
    Reset();
}

/*
 * Set both the element name and the node.
 */

void
e4_XMLGenerator::SetElementNameAndNode(char *sen, e4_Node nn)
{
    if (se != NULL) {
	free(se);
    }
    if (sen == NULL) {
	se = NULL;
    } else {
	se = (char *) malloc(strlen(sen) + 1);
	strcpy(se, sen);
    }
    sn = nn;
    Reset();
}

/*
 * Retrieve the associated node.
 */

void
e4_XMLGenerator::GetNode(e4_Node &nn) const
{
    nn = sn;
}

/*
 * Retrieve the wrapping XML tag name.
 */

char *
e4_XMLGenerator::GetElementName() const
{
    if (!ready) {
	return NULL;
    }
    return se;
}

/*
 * Reset the generator.
 */

void
e4_XMLGenerator::Reset()
{
    ready = ((sn == invalidNode) || (se == NULL)) ? false : true;
    firstGeneration = true;
    outputStream->Reset();
    if (nodesSeen != NULL) {
	e4_DeleteHashTable(nodesSeen);
    }
    nodesSeen = e4_NewHashTable(E4_ONE_WORD_KEY);
    if (error) {
	delete [] errorString;
	error = false;
    }
}
    
/*
 * This operation flags an error and stores an error string into the generator.
 */

void
e4_XMLGenerator::FlagError(const char *msg)
{
    if (errorString != NULL) {
	delete [] errorString;
	errorString = NULL;
    }
    if (msg) {
	errorString = new char[strlen(msg) + 1];
	strcpy(errorString,msg);
    }
    error = true;
}

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

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

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

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

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

void
e4_XMLGenerator::ClearError()
{
    error = false;
    if (errorString) {
	delete [] errorString;
	errorString = NULL;
    }
}

/*
 * Generate XML.
 */

char *
e4_XMLGenerator::Generate()
{
    /*
     * If we're not ready, return NULL right away.
     */

    if (!ready) {
	return NULL;
    }

    /*
     * NOTE FOR GS: Please check this out!
     *
     * Prevent multi-generation use with this API, because Reset() is always
     * called, which will reset firstGeneration.
     */

    if (!firstGeneration) {
	FlagError("Cannot call Generate() after initial generation, "
		  "use Generate(const char *sen, e4_Node nn)");
	return NULL;
    }

    /*
     * Reset the generator to an empty state.
     */

    Reset();

    /*
     * Generate from the starting element and node.
     */

    outputProcessor->ProcessOutputBegin(se, sn, firstGeneration);
    if (GenerateNode(se, sn, 0)) {
	outputProcessor->ProcessOutputEnd(firstGeneration);
    }
    firstGeneration = false;

    /*
     * Finally return the accumulated XML output.
     */

    return outputStream->Get();
}

/*
 * Produce XML output.
 *
 * NOTE: The default output processor functionality will return a char pointer
 * to the generated output buffer. This buffer may not be complete if there is
 * an error state set.
 *
 * This version of the Generate interface allows for multiple generations into
 * a continuous output stream of disjoint paths in the storage. The first time
 * it is invoked internal state will be reset. Subsequent invocations will not
 * reset the output stream state.
 */

char *
e4_XMLGenerator::Generate(const char *sen, e4_Node nn)
{
    /*
     * Check for valid starting element name and starting node.
     */

    if ((sen == NULL) || (*sen == '\0')) {
	FlagError("Multi-invocation Generate requires a valid starting"
		  " element name");
	return (NULL);
    }
    if (!nn.IsValid()) {
	FlagError("Multi-invocation Generate requires a valid starting node");
	return (NULL);
    }

    /*
     * First time for this instance generating xml?
     */

    if (firstGeneration) {
	Reset();
    }

    /*
     * Remember starting element name.
     */

    if (se) {
	free(se);
    }
    se = (char *) malloc(strlen(sen) + 1);
    strcpy(se, sen);

    /*
     * Remember starting node.
     */

    sn = nn;

    /*
     * Generate from the starting element and node.
     */

    outputProcessor->ProcessOutputBegin(se,sn,firstGeneration);
    if (GenerateNode(se, sn, 0)) {
	outputProcessor->ProcessOutputEnd(firstGeneration);
    }
    firstGeneration = false;

    return outputStream->Get();
}

/*
 * Return the accumulated XML output.
 */

char *
e4_XMLGenerator::Get()
{
    if (!ready) {
	return NULL;
    }
    return outputStream->Get();
}

/*
 * Generate XML output from a node and a wrapping element.
 */

bool
e4_XMLGenerator::GenerateNode(char *elname, e4_Node nn, int vertexUserData)
{
    int id, isnew, nodeID = -1, vcount, n;
    bool attributesSeen = false;
    e4_HashEntry *ePtr;
    e4_NodeUniqueID nuid;
    e4_DString dsAttrs;
    e4_Node attributes;
    e4_Vertex v, vv;

    /*
     * First of all determine if this is a node we've already produced
     * XML output for. If so, generate a back reference.
     */

    if (!nn.GetUniqueID(nuid)) {
	FlagError("Could not obtain node unique ID, failing");
	return false;
    }
    id = nuid.GetUniqueID();
    ePtr = E4_CREATEHASHENTRY(nodesSeen, (char *) id, &isnew);

    /*
     * If it's not new, we've generated this node already. In that
     * case generate a back pointer to the entity containing this
     * node. That entity will be marked with a __multiparent__
     * attribute with the token that's the hash value of this entry.
     */

    if (!isnew) {
	return outputProcessor->ProcessBackRefNode(nn,
						   elname,
						   (int) E4_GETHASHVALUE(ePtr),
						   vertexUserData);
    }

    /*
     * Collect all the information needed to describe a normal node in this
     * storage, into a dynamic string. Once everything is collected, allow
     * the output processor to generate XML output from it.
     */

    /*
     * If this node has any parents, it's possible that we have to
     * generate XML for a cyclical graph. Tag this node with a 
     * __nodeid__ attribute so that when it gets deserialized, this
     * node can be associated with subsequent __nodebackref__ elements.
     *
     * If exportPureXML == true, then suppress this part of the generated
     * XML so that it corresponds exactly to XML input that could have
     * been used to create this node.
     */

    if (nn.ParentCount() > 0) {
	if (exportPureXML == false) {
	    E4_SETHASHVALUE(ePtr, nodeCounter);
	    nodeCounter++;
	    nodeID = (int) E4_GETHASHVALUE(ePtr);
	}
    } else {
	e4_DeleteHashEntry(ePtr);
    }

    /*
     * Get the number of vertices in this node.
     */

    vcount = nn.VertexCount();

#ifdef	NOTDEF
    /*
     * If exportPureXML == false, include the user data attached to this
     * node and to the vertex in the output.
     *
     * If exportPureXML == true, then suppress this part of the generated
     * XML so that it corresponds exactly to XML input that could have been
     * used to create this node.
     */

    if (exportPureXML == false) {
	dsptr->Append(" __nodeuserdata__=", -1);
	nn.GetUserData(ud);
	sprintf(intbuf, "\"%d\"", ud);
	dsptr->Append(intbuf, -1);

	dsptr->Append(" __vertexuserdata__=", -1);
	sprintf(intbuf, "\"%d\"", vertexUserData);
	dsptr->Append(intbuf, -1);
    }
#endif

    /*
     * If there are >= 1 vertices and the first one is named "__attributes__"
     * and has a node value, that sub-node contains attributes attached to the
     * node we're about to produce. If this is the case, procedure the
     * attributes into the XML output.
     */

    if ((vcount > 0) &&
	(strcmp(nn.VertexName(1), "__attributes__") == 0) &&
	(nn.VertexTypeByRank(1) == E4_VTNODE)) {
	nn.GetVertexByRank(1, attributes);
	if (GenerateAttributes(attributes, dsAttrs)) {
	    vcount--;
	    attributesSeen = true;
	}
    }

    /*
     * Call the output processor the generate a node-begin bracket.
     */

    if (!outputProcessor->ProcessNodeBegin(nn,
					   elname,
					   nodeID,
					   vertexUserData,
					   dsAttrs,
					   (vcount > 0))) {
	return false;
    }

    /*
     * Now iterate over each vertex in turn.
     */

    if (vcount > 0) {
	(void) nn.GetVertexRefByRank((attributesSeen ? 2 : 1), v);
	for (n = vcount; n > 0; n--) {
	    if (!GenerateVertex(v)) {
		return false;
	    }
	    (void) v.Next(1, vv);
	    v = vv;
	}
    }

    /*
     * Geneate a node-end bracket.
     */

    if (!outputProcessor->ProcessNodeEnd(nn, elname, (vcount > 0))) {
	return false;
    }

    /*
     * Everything worked out fine.
     */

    return true;
}

/*
 * Generate XML output for a given vertex.
 */

bool
e4_XMLGenerator::GenerateVertex(e4_Vertex v)
{
    e4_Node nn;
    const char *s = NULL,
	       *s1 = NULL,
	       *s2 = NULL,
	       *s3 = NULL,
	       *s4 = NULL,
	       *s5 = NULL;
    int vi = -1, ud = -1;

    /*
     * Special case for vertex with name "__data__" and type "string". This
     * generates character data in the XML output.
     */

    if ((v.Type() == E4_VTSTRING) &&
	(strcmp(v.Name(), "__data__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	(void) v.Get(s);
	return outputProcessor->ProcessCharData(s);
    }

    /*
     * Special case for vertex with name "__comment__" and type "string". This
     * generates an XML comment in the output.
     */

    if ((v.Type() == E4_VTSTRING) && (strcmp(v.Name(), "__comment__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	(void) v.Get(s);
	return outputProcessor->ProcessComment(s);
    }

    /*
     * Special case for vertex with name "__cdata__" and type "node". This
     * generates an XML CDATA section in the output, if that node contains
     * a vertex named "__data__" with a string value.
     */

    if ((v.Type() == E4_VTNODE) &&
	(strcmp(v.Name(), "__cdata__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	(void) v.Get(nn);
	if (nn.GetVertex("__data__", s)) {
	    return outputProcessor->ProcessCDATA(s);
	}
	FlagError("Incorrect CDATA node");
	return false;
    }

    /*
     * Special case for vertex with name "__processinginstruction__" and
     * type "node". This generates an XML processing instruction element
     * in the output, if that node has two vertices with names "__target__"
     * and "__data__" with string values.
     */

    if ((v.Type() == E4_VTNODE) &&
	(strcmp(v.Name(), "__processinginstruction__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	if (v.Get(nn) &&
	    nn.IsValid() &&
	    nn.GetVertex("__target__", s) &&
	    nn.GetVertex("__data__", s2)) {
	    return outputProcessor->ProcessInstructions(s, s2);
	}
	FlagError("Incorrect processing instruction node");
	return false;
    }

    /*
     * Special case for a vertex named "__xml__" with a node value. This
     * generates an XML declaration element in the output, if that node has
     * three vertices with names "__version__", "__encoding__" and
     * "__standalone__" with string, string and int values.
     */

    if ((v.Type() == E4_VTNODE) && (strcmp(v.Name(), "__xml__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	if (v.Get(nn) &&
	    nn.IsValid() &&
	    nn.GetVertex("__version__", s) &&
	    nn.GetVertex("__encoding__", s2) &&
	    nn.GetVertex("__standalone__", vi)) {
	    return outputProcessor->ProcessXMLDeclaration(s, s2, vi);
	}
	FlagError("Incorrect XML declaration node");
	return false;
    }

    /*
     * Special case for a vertex with a node value and named
     * "__doctypedecl__". This node produces a DOCTYPE section in
     * the XML output, with special handling for vertices named
     * "__doctypename__", "__sysid__", "__pubid__" and
     * "__hasinternalsubset__".
     */

    if ((v.Type() == E4_VTNODE) && 
	(strcmp(v.Name(), "__doctypedecl__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	if (v.Get(nn) &&
	    nn.IsValid() &&
	    nn.GetVertex("__doctypename__", s)) {
	    (void) nn.GetVertex("__sysid__", s2);
	    (void) nn.GetVertex("__pubid__", s3);
	    (void) nn.GetVertex("__hasinternalsubset__", vi);

	    /*
	     * Produce a start-dtd bracket in the output.
	     */

	    if (!outputProcessor->ProcessDTDBegin(s, s2, s3, vi)) {
		return false;
	    }

	    /*
	     * Produce output for element declarations.
	     */

	    if (!GenerateElementDecls(nn)) {
		return false;
	    }

	    /*
	     * All done, close the DOCTYPE declaration.
	     */

	    return outputProcessor->ProcessDTDEnd();
	}
	FlagError("Incorrect DOCTYPEDECL node");
	return false;
    }

    /*
     * Special case for a vertex with name "__unparsedentity__" and
     * a node value.
     */

    if ((v.Type() == E4_VTNODE) &&
	(strcmp(v.Name(), "__unparsedentity__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	if (v.Get(nn) &&
	    nn.IsValid() &&
	    nn.GetVertex("__entityname__", s)) {
	    (void) nn.GetVertex("__base__", s2);
	    (void) nn.GetVertex("__systemid__", s3);
	    (void) nn.GetVertex("__publicid__", s4);
	    (void) nn.GetVertex("__notationname__", s5);
	    return outputProcessor->ProcessUnparsedEntity(s, s2, s3, s4, s5);
	}
	FlagError("Incorrect UNPARSEDENTITY node");
	return false;
    }

    /*
     * Special case for a vertex named __skippedentity__ with a node value.
     */

    if ((v.Type() == E4_VTNODE) &&
	(strcmp(v.Name(), "__skippedentity__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	if (v.Get(nn) &&
	    nn.IsValid() &&
	    nn.GetVertex("__entityname__", s) &&
	    nn.GetVertex("__isparameterentity__", vi)) {
	    return outputProcessor->ProcessSkippedEntity(s, vi);
	}
	FlagError("Incorrect SKIPPEDENTITY node");
	return false;
    }

    /*
     * Special case for a vertex named "__notation__" with a node value.
     */

    if ((v.Type() == E4_VTNODE) &&
	(strcmp(v.Name(), "__notation__") == 0)) {

	/*
	 * NOTE: There is no way to represent the user data attached to
	 * this vertex, in the output.
	 */

	if (v.Get(nn) &&
	    nn.IsValid() &&
	    nn.GetVertex("__notationname__", s)) {
	    (void) nn.GetVertex("__base__", s1);
	    (void) nn.GetVertex("__systemid__", s2);
	    (void) nn.GetVertex("__publicid__", s3);
	    return outputProcessor->ProcessNotationDecl(s, s1, s2, s3);
	}
	FlagError("Incorrect NOTATIONDECL node");
	return false;
    }

    /*
     * Special case for a vertex with a node value and none of the
     * preceding special case names.
     */

    if (v.Type() == E4_VTNODE) {
	(void) v.Get(nn);
	(void) v.GetUserData(ud);
	return GenerateNode((char *) v.Name(), nn, ud);
    }

    /*
     * Otherwise it's a regular vertex, so generate that.
     */

    return outputProcessor->ProcessVertex(v);
}

/*
 * Generate XML output for a list of attributes.
 */

bool
e4_XMLGenerator::GenerateAttributes(e4_Node attrs, e4_DString &dsAttrs) const
{
    e4_Vertex v, vv;
    int len;
    const char *s;
    bool first;

    /*
     * First of all a sanity check: the node must contain only
     * vertices of type E4_VTSTRING. If not, we generate a regular
     * node for it.
     */

    for (len = attrs.VertexCount(),
	   first = true, 
	   (void) attrs.GetVertexRefByRank(1, v);
	 len > 0;
	 len--,
	   v.Next(1, vv), 
	   first = false) {
	if (first == false) {
	    v = vv;
	}
	if (v.Type() != E4_VTSTRING) {
	    return false;
	}
    }

    /*
     * Now generate an attribute for each vertex.
     */

    for (len = attrs.VertexCount(), first = true,
	   (void) attrs.GetVertexRefByRank(1, v);
	 len > 0;
	 len--, v.Next(1, vv), first = false) {
	if (first == false) {
	    v = vv;
	}
	(void) v.Get(s);
	dsAttrs.Append(" ", -1);
	dsAttrs.Append(v.Name(), -1);
	dsAttrs.Append("=\"", -1);
	dsAttrs.Append(s, -1);
	dsAttrs.Append("\"", -1);
    }

    /*
     * Ensure it is double NULL terminated.
     */

    dsAttrs.Append("",1);

    return true;
}

/*
 * This operation generates output for a series of DOCTYPE element
 * declarations.
 *
 * NOTE: For now this does nothing because I don't understand what
 * it all means.. :(
 */

bool
e4_XMLGenerator::GenerateElementDecls(e4_Node doctype) const
{
   return true;
}

/*
 * This operation encodes a binary value of a given length into a BASE64
 * encoded string.
 */

char *
e4_XMLGenerator::Base64_Encode(byte *bytes, int len)
{
    if (base64str != NULL) {
	free(base64str);
    }
    base64str = base64_encode(bytes, len);
    return base64str;
}
