/* * t4xml.cpp -- * * This file contains the implementation of the "tgraph::xml" command. * * Authors: Jacob Levy and Jean-Claude Wippler. * jyl@best.com jcw@equi4.com * * Copyright (c) 2000-2003, JYL Software Inc. * * 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 "t4xml_c.h" #include "e4xml.h" #include "t4graphrep.h" /* * The following code was contributed by David Gravereux. It deals with * making t4xml stubs compliant on Win32 systems automatically. */ #ifdef _MSC_VER /* * Only do this when MSVC++ is compiling us. */ # ifdef USE_TCL_STUBS /* * Mark this .obj as needing tcl's Stubs library. */ # pragma comment(lib, "tclstub" \ STRINGIFY(JOIN(TCL_MAJOR_VERSION,TCL_MINOR_VERSION)) ".lib") # if !defined(_MT) || !defined(_DLL) || defined(_DEBUG) /* * This fixes a bug with how the Stubs library was compiled. * The requirement for msvcrt.lib from tclstubXX.lib should * be removed. */ # pragma comment(linker, "-nodefaultlib:msvcrt.lib") # endif # else /* * Mark this .obj needing the import library */ # pragma comment(lib, "tcl" \ STRINGIFY(JOIN(TCL_MAJOR_VERSION,TCL_MINOR_VERSION)) ".lib") # endif #endif /* * Script to create the "txml" namespace: */ static const char *txmlNspcCreate = (const char *) "namespace eval ::txml {}"; /* * Selectors for input type: */ static char *inputSelectors[] = { (char *) "-channel", (char *) "-string", (char *) "-variable", NULL }; typedef enum XInputSelectors { XIChannel = 0, XIString, XIVar } XInputSelectors; /* * Selectors for output type: */ typedef enum XOutputSelectors { XOChannel = 0, XOVariable, XOAppend, XOString } XOutputSelectors; /* * Static functions declared in this file: */ static int T4XML_OutputToInterpResult(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *xmlName, bool ex); static int T4XML_OutputToChannel(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *xmlName, Tcl_Obj *channelName, bool ex); static int T4XML_OutputToVar(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *xmlName, Tcl_Obj *varName, int flags, bool ex); static int T4XML_ExportNode(Tcl_Interp *interp, Tcl_Obj *nodeName, e4_Node &nn); static int T4XML_InputFromChannel(Tcl_Interp *interp, Tcl_Obj *channelName, Tcl_Obj *nodeName); static int T4XML_InputFromString(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *inputString); static int T4XML_InputFromVar(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *varName); static int T4XML_OutputCmdProc(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]); static int T4XML_InputCmdProc(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]); /* * T4XML_ExportNode -- * * Given a command name in a given interpreter, obtain the * e4_Node structure associated with the command. * * Results: * Upon success, the interpreter result is blank. The e4_Node is * returned via an output parameter. * * Side effects: * None. */ static int T4XML_ExportNode(Tcl_Interp *interp, Tcl_Obj *nodeName, e4_Node &nn) { char *nname = Tcl_GetString(nodeName); GO_InternalRep *ptr2; T4InternalRep *tip; T4Node *tnp; Tcl_CmdInfo cmdInfo; if (Tcl_GetCommandInfo(interp, nname, &cmdInfo) == 0) { Tcl_ResetResult(interp); Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "there is no node named ", nname, NULL); return TCL_ERROR; } Tcl_ResetResult(interp); ptr2 = (GO_InternalRep *) cmdInfo.objClientData; if (ptr2 == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "internal error: partially defined node ", nname, NULL); return TCL_ERROR; } tip = (T4InternalRep *) ptr2->data; if (tip == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "internal error: partially defined node ", nname, NULL); return TCL_ERROR; } if (tip->KindIdentifier() != T4GRAPH_NODE) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), nname, " is not the name of a node", NULL); return TCL_ERROR; } tnp = (T4Node *) tip; tnp->ExternalizeNode(nn); return TCL_OK; } /* * T4XML_InputFromChannel -- * * This procedure reads in XML from a channel. * * Results: * Upon success, the interpreter result is empty. The given node will * contain new vertices representing the parsed XML input. * * Side effects: * May read input, may allocate new storage in a T4Storage object, and * may allocate new memory. */ static int T4XML_InputFromChannel(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *channelName) { e4_XMLParser *parser; e4_Node n; Tcl_Channel chan; char *input; int len, mode; bool done = false; bool error = false; Tcl_Obj *bufObj; if (T4XML_ExportNode(interp, nodeName, n) != TCL_OK) { return TCL_ERROR; } if (channelName == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "missing channel name for xml::input", NULL); return TCL_ERROR; } chan = Tcl_GetChannel(interp, Tcl_GetString(channelName), &mode); if (chan == NULL) { return TCL_ERROR; } Tcl_ResetResult(interp); if ((mode & TCL_READABLE) != TCL_READABLE) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "channel ", Tcl_GetString(channelName), " is not open for reading", NULL); return TCL_ERROR; } parser = new e4_XMLParser(n); if (parser == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "failed to construct parser", NULL); return TCL_ERROR; } bufObj = Tcl_NewStringObj("", 0); do { Tcl_SetStringObj(bufObj, "", 0); if ((Tcl_GetsObj(chan, bufObj) < 0) && (Tcl_Eof(chan))) { done = true; continue; } if (parser->Finished()) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "input following end of xml on ", "channel ", Tcl_GetString(channelName), NULL); error = true; done = true; continue; } input = Tcl_GetStringFromObj(bufObj, &len); if (input == NULL) { done = true; error = true; Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "error reading channel ", Tcl_GetString(channelName), NULL); continue; } if (!parser->Parse(input, len)) { done = true; error = true; if (parser->ErrorString() != NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), parser->ErrorString(), NULL); } else { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "unspecified parser error", NULL); } continue; } } while (!done); if (!parser->Finished()) { error = true; Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "premature termination of xml input on ", "channel ", Tcl_GetString(channelName), NULL); } if (parser->Finished() && parser->HasError()) { error = true; Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), parser->ErrorString(), NULL); } delete parser; if (error) { return TCL_ERROR; } return TCL_OK; } /* * T4XML_InputFromVar -- * * This procedure reads in XML from the value of a Tcl variable. * * Results: * A standard Tcl result. Upon success, the interpreter result * is empty. * * Side effects: * May read input, may allocate new storage in a T4Storage object, and * may allocate new memory. */ static int T4XML_InputFromVar(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *varName) { e4_XMLParser *parser; e4_Node n; char *input; if (T4XML_ExportNode(interp, nodeName, n) != TCL_OK) { return TCL_ERROR; } if (varName == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "missing input var to xml::input", NULL); return TCL_ERROR; } input = (char *) Tcl_GetVar(interp, Tcl_GetString(varName), 0); /* * If the value returned from Tcl_GetVar is NULL, then an error message * is already left in the interpreter result. */ if (input == NULL) { return TCL_ERROR; } parser = new e4_XMLParser(n); if (parser == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "failed to construct parser", NULL); return TCL_ERROR; } if (!parser->Parse(input, strlen(input))) { if (parser->HasError()) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), parser->ErrorString(), NULL); } else { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "unspecified parse error", NULL); } delete parser; return TCL_ERROR; } if (!parser->Finished()) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "parse error: Premature termination", NULL); delete parser; return TCL_ERROR; } if (parser->Finished() && parser->HasError()) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), parser->ErrorString(), NULL); delete parser; return TCL_ERROR; } delete parser; return TCL_OK; } /* * T4XML_InputFromString -- * * This procedure reads in XML from a given string. * * Results: * A standard Tcl result. Upon success, the interpreter result * is empty. * * Side effects: * May read input, may allocate new storage in a T4Storage object, and * may allocate new memory. */ static int T4XML_InputFromString(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *inputString) { e4_XMLParser *parser; e4_Node n; char *input; int len; if (T4XML_ExportNode(interp, nodeName, n) != TCL_OK) { return TCL_ERROR; } if (inputString == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "empty input string to xml::input", NULL); return TCL_ERROR; } input = Tcl_GetStringFromObj(inputString, &len); parser = new e4_XMLParser(n); if (parser == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "failed to construct parser", NULL); return TCL_ERROR; } if (!parser->Parse(input, len)) { if (parser->HasError()) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), parser->ErrorString(), NULL); } else { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "unspecified parse error", NULL); } delete parser; return TCL_ERROR; } if (!parser->Finished()) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "parse error: Premature termination", NULL); delete parser; return TCL_ERROR; } if (parser->Finished() && parser->HasError()) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), parser->ErrorString(), NULL); delete parser; return TCL_ERROR; } delete parser; return TCL_OK; } /* * T4XML_OutputXML -- * * This routine leaves in the interpreter result a string object * representing the XML output for the given node, wrapped with * the supplied tag. * * Results: * A standard Tcl result. Upon success, the interpreter result * holds a string object containing the XML output. * * Side effects: * None. */ static int T4XML_OutputXML(Tcl_Interp *interp, e4_Node n, char *tagName, bool ex) { e4_XMLGenerator *g; char *xmloutput; Tcl_ResetResult(interp); /* * Generate the XML output. The returned string is owned by the XML * generator, so it will be freed when the generator is deleted. */ g = new e4_XMLGenerator(n, tagName, ex); xmloutput = g->Generate(); /* * Check for errors. */ if (xmloutput == NULL) { delete g; Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "error generating XML", NULL); return TCL_ERROR; } /* * Put the XML output into the interpreter result. */ Tcl_SetStringObj(Tcl_GetObjResult(interp), xmloutput, -1); /* * Clean up. */ delete g; return TCL_OK; } /* * T4XML_OutputToInterpResult -- * * This procedure writes out XML for a given node, wrapped in an enclosing * XML tag with the given name, into the interpreter result. * * Results: * On success, the interpreter result contains the XML produced from the * given input. * * Side effects: * None. */ static int T4XML_OutputToInterpResult(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *XMLName, bool ex) { e4_Node n; if (T4XML_ExportNode(interp, nodeName, n) != TCL_OK) { return TCL_ERROR; } Tcl_ResetResult(interp); if (!n.IsValid()) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "invalid node ", Tcl_GetString(nodeName), NULL); return TCL_ERROR; } return T4XML_OutputXML(interp, n, Tcl_GetString(XMLName), ex); } /* * T4XML_OutputToChannel -- * * Produce XML representing the given node to a given channel. * * Results: * A standard Tcl result. Upon success, the interpreter result will be * empty and the given channel will contain the XML output. * * Side effects: * May write to a channel. */ static int T4XML_OutputToChannel(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *XMLName, Tcl_Obj *channelName, bool ex) { Tcl_Channel chan; int mode; int len; char *xml; if (channelName == NULL) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "missing channel name for xml::output", NULL); return TCL_ERROR; } chan = Tcl_GetChannel(interp, Tcl_GetString(channelName), &mode); if ((mode & TCL_WRITABLE) != TCL_WRITABLE) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "channel ", Tcl_GetString(channelName), " is not open for writing", NULL); return TCL_ERROR; } if (T4XML_OutputToInterpResult(interp, nodeName, XMLName, ex) != TCL_OK) { return TCL_ERROR; } xml = Tcl_GetStringFromObj(Tcl_GetObjResult(interp), &len); (void) Tcl_WriteChars(chan, xml, len); Tcl_ResetResult(interp); return TCL_OK; } /* * T4XML_OutputToVar -- * * This procedure stores the XML produced from a node in a Tcl variable. * If the "flags" argument contains the bit TCL_APPEND_VALUE set then the * XML is appended to the existing value instead of replacing it. * * Results: * A standard Tcl result. Upon successful completion the interpreter * result is empty. * * Side effects: * A Tcl variable may be created. */ static int T4XML_OutputToVar(Tcl_Interp *interp, Tcl_Obj *nodeName, Tcl_Obj *XMLName, Tcl_Obj *varName, int flags, bool ex) { Tcl_Obj *res; char *xml; int len; if (T4XML_OutputToInterpResult(interp, nodeName, XMLName, ex) != TCL_OK) { return TCL_ERROR; } res = Tcl_GetObjResult(interp); Tcl_IncrRefCount(res); xml = Tcl_GetStringFromObj(Tcl_GetObjResult(interp), &len); Tcl_ResetResult(interp); (void) Tcl_SetVar(interp, Tcl_GetString(varName), xml, flags); Tcl_DecrRefCount(res); Tcl_ResetResult(interp); return TCL_OK; } /* * T4XML_InputCmdProc -- * * This procedure reads in XML from a channel or from a given string. * * Results: * Upon success, the interpreter result is empty. The given node will * contain new vertices representing the parsed XML input. * * Side effects: * May read input, may allocate new storage in a T4Storage object, and * may allocate new memory. */ static int T4XML_InputCmdProc(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { XInputSelectors index; /* * Expecting exactly three arguments: * * txml::input node -channel chan * --OR-- * txml::input node -string str */ if (objc != 4) { Tcl_WrongNumArgs(interp, 0, NULL, "txml::input node sel sel-arg"); return TCL_ERROR; } /* * Figure out what type of input to read: */ if (Tcl_GetIndexFromObj(interp, objv[2], (CONST84 char **) inputSelectors, (char *) "inputSelector", 0, (int *) &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case XIChannel: return T4XML_InputFromChannel(interp, objv[1], objv[3]); case XIString: return T4XML_InputFromString(interp, objv[1], objv[3]); case XIVar: return T4XML_InputFromVar(interp, objv[1], objv[3]); default: Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "T4XML_InputCmdProc: unreachable code!", (char *) NULL); return TCL_ERROR; } } /* * T4XML_OutputCmdProc -- * * This procedure produces XML from a given node, wrapped in a tag * named according to the given XML name. * * Results: * Upon success, the XML representing the given node is produced. It is * written to various destinations depending on the output selector * given. * * Side effects: * May write output, may create a variable or append to a given variable. */ static int T4XML_OutputCmdProc(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { XOutputSelectors index = XOString; Tcl_Obj *chan; Tcl_Obj *var; int ex = 0; bool exv; char *str; int i; /* * Expecting a variable number of arguments: * * txml::output node name ?-pure ? ?-string? * txml::output node name ?-pure ? ?-channel chanName? * txml::output node name ?-pure ? ?-variable varName? * --OR-- * txml::output node name ?-pure ? ?-append varName? */ if (objc < 3) { Tcl_WrongNumArgs(interp, 0, NULL, "txml::output node name ?args ...?"); return TCL_ERROR; } for (i = 3; i < objc; i++) { str = Tcl_GetString(objv[i]); if (strncmp(str, "-pure", strlen("-pure")) == 0) { i++; if (i == objc) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "txml::output: expecting argument ", "to -pure", NULL); return TCL_ERROR; } if (Tcl_GetBooleanFromObj(interp, objv[i], &ex) != TCL_OK) { return TCL_ERROR; } } else if (strncmp(str, "-string", strlen("-string")) == 0) { index = XOString; } else if (strncmp(str, "-channel", strlen("-channel")) == 0) { i++; if (i == objc) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "txml::output: expecting argument ", "to -channel", NULL); return TCL_ERROR; } index = XOChannel; chan = objv[i]; } else if (strncmp(str, "-variable", strlen("-variable")) == 0) { i++; if (i == objc) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "txml::output: expecting argument ", "to -variable", NULL); return TCL_ERROR; } index = XOVariable; var = objv[i]; } else if (strncmp(str, "-append", strlen("-append")) == 0) { i++; if (i == objc) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "txml::output: expecting argument ", "to -append", NULL); return TCL_ERROR; } index = XOAppend; var = objv[i]; } else { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "txml::output: unrecognized option \"", Tcl_GetString(objv[i]), NULL); return TCL_ERROR; } } /* * After all arguments have been parsed, dispatch to the right operation: */ exv = (ex == 0 ? false : true); switch (index) { case XOString: return T4XML_OutputToInterpResult(interp, objv[1], objv[2], exv); case XOChannel: return T4XML_OutputToChannel(interp, objv[1], objv[2], chan, exv); case XOVariable: return T4XML_OutputToVar(interp, objv[1], objv[2], var, 0, exv); case XOAppend: return T4XML_OutputToVar(interp, objv[1], objv[2], var, TCL_APPEND_VALUE, exv); default: Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "T4XML_InputCmdProc: unreachable code!", (char *) NULL); return TCL_ERROR; } } /* * T4XML_RealInit -- * * This procedure initializes the given interpreter with the * T4XML command. * * Results: * Always succeeds and returns TCL_OK. * * Side effects: * Defines a new command, "tgraph::xml", in the given interpreter. */ extern "C" int T4XML_RealInit(Tcl_Interp *interp) { #ifdef USE_TCL_STUBS if (Tcl_InitStubs(interp, "8.1", 0) == NULL) { return TCL_ERROR; } #endif /* * Require the T4Graph package. */ if (Tcl_PkgRequire(interp, "tgraph", (char *) "1.0.0", 0) == NULL) { return TCL_ERROR; } /* * Create the "::txml" namespace. */ (void) Tcl_Eval(interp, (char *) txmlNspcCreate); (void) Tcl_CreateObjCommand(interp, (char *) "::txml::input", T4XML_InputCmdProc, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); (void) Tcl_CreateObjCommand(interp, (char *) "::txml::output", T4XML_OutputCmdProc, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); /* * All went well, so declare that we provide the T4XML package. */ return Tcl_PkgProvide(interp, (char *) "txml", (char *) "1.0.0"); }