diff options
Diffstat (limited to 'source4/lib/appweb/ejs-2.0/ejs/classes/ejsXml.c')
-rw-r--r-- | source4/lib/appweb/ejs-2.0/ejs/classes/ejsXml.c | 1327 |
1 files changed, 1327 insertions, 0 deletions
diff --git a/source4/lib/appweb/ejs-2.0/ejs/classes/ejsXml.c b/source4/lib/appweb/ejs-2.0/ejs/classes/ejsXml.c new file mode 100644 index 0000000000..a2ef8d1390 --- /dev/null +++ b/source4/lib/appweb/ejs-2.0/ejs/classes/ejsXml.c @@ -0,0 +1,1327 @@ +/* + * @file ejsXml.c + * @brief E4X XML support + */ +/********************************* Copyright **********************************/ +/* + * @copy default + * + * Copyright (c) Mbedthis Software LLC, 2003-2006. All Rights Reserved. + * Copyright (c) Michael O'Brien, 1994-1995. All Rights Reserved. + * + * This software is distributed under commercial and open source licenses. + * You may use the GPL open source license described below or you may acquire + * a commercial license from Mbedthis Software. You agree to be fully bound + * by the terms of either license. Consult the LICENSE.TXT distributed with + * this software for full details. + * + * This software is open source; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See the GNU General Public License for more + * details at: http://www.mbedthis.com/downloads/gplLicense.html + * + * This program is distributed WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * This GPL license does NOT permit incorporating this software into + * proprietary programs. If you are unable to comply with the GPL, you must + * acquire a commercial license to use this software. Commercial licenses + * for this software and support services are available from Mbedthis + * Software at http://www.mbedthis.com + * + * @end + */ +/************************************ Doc *************************************/ +/* + * Javascript class definition + * + * class XML { + * public XML(); + * public XML(string xmlString); // "<tag... " + * public XML(string file); // "file" + * + * public void load(string file); + * public void save(string file); + * public Array children(); + * public Array attributes(); + * } + * + [[Internal Properties / Methods]] + - prototype - Ptr to class prototype (base class) + - class - Type of class + Object.prototype.toString + - Value - + - Get(name) - Returns the value + - Put(name, value) - Sets the value + - HasProperty(name) - Bool if property exists + - Delete(name) - Delete property + - DefaultValue(hint) - Return default primitive (not obj) value + toString, if result is obj, then call valueOf + if hint is number, then call valueOf, then toString + - Construct(arg list) - Constructor + - Call(arg list) - Function call + - HasInstance(value) - ?? + - Scope - Frame scope chain + - Match(string, index) - Regexp match + + - Example: + XML attribute @name + @* + * + var node = new XML("<order/>"); + Operators: + var prices = order..price; + var urgentItems = order.item(@level == "rush"); + var itemAttrs = order.item[0].@*; # @ for attributes + XML Literals + order.customer.address = + <address>..... + <zip>{zipCode}</zip> Where {var} is a JS var + <tag attribute={prefix}> ... Also for attributes + </address> + Omit namespaces + Example: + var html = <html/>; + html.head.title = "My title"; + head.body@bgcolor = "#e4e4e4"; +*/ + +/********************************** Includes **********************************/ + +#include "ejs.h" +#include "exml.h" + +/************************************ Data ************************************/ +#if BLD_FEATURE_EJS_E4X + +/* + * Per tag state + */ +typedef struct XmlTagState { + EjsVar *obj; + EjsVar *attributes; + EjsVar *comments; +} XmlTagState; + +/* + * Parser state + */ +typedef struct XmlState { + Ejs *ep; + EjsVar *xmlClass; + EjsVar *xmlListClass; + XmlTagState nodeStack[E4X_MAX_NODE_DEPTH]; + int topOfStack; + long inputSize; + long inputPos; + const char *inputBuf; + const char *fileName; +} XmlState; + +/****************************** Forward Declarations **************************/ +/* + * XML methods + */ +static int text(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int name(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int load(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int save(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int toString(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int valueOf(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); + +/* MOB -- temp */ +static int getList(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int setText(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); + +#if FUTURE +static int length(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int toXmlString(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); + +static int appendChild(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int attributes(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int child(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int children(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int comments(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int decendants(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int elements(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int insertChildAfter(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int insertChildBefore(Ejs *ep, EjsVar *thisObj, int argc, + EjsVar **argv); +static int replace(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int setName(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +static int text(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv); +#endif + +/* + * Internal methods + */ +static EjsVar *createXmlProperty(Ejs *ep, EjsVar *obj, const char *property); +static int deleteXmlProperty(Ejs *ep, EjsVar *obj, const char *property); +static EjsVar *getXmlProperty(Ejs *ep, EjsVar *obj, const char *property); +static EjsVar *setXmlProperty(Ejs *ep, EjsVar *obj, const char *property, + const EjsVar *value); +static int loadXmlString(Ejs *ep, EjsVar *thisObj, const char *xmlString); + +/* + * XMLList methods + */ +static EjsVar *createXmlListProperty(Ejs *ep, EjsVar *obj, + const char *property); +static int deleteXmlListProperty(Ejs *ep, EjsVar *obj, + const char *property); +static EjsVar *getXmlListProperty(Ejs *ep, EjsVar *obj, const char *property); +static EjsVar *setXmlListProperty(Ejs *ep, EjsVar *obj, const char *property, + const EjsVar *value); + +/* + * Misc + */ +static int readFileData(Exml *xp, void *data, char *buf, int size); +static int readStringData(Exml *xp, void *data, char *buf, int size); +static int parserHandler(Exml *xp, int state, const char *tagName, + const char *attName, const char *value); +static void termParser(Exml *xp); +static Exml *initParser(Ejs *ep, EjsVar *thisObj, const char *fileName); +static int getNumElements(EjsVar *obj); +static int getText(MprBuf *buf, EjsVar *obj); +static int xmlToString(Ejs *ep, MprBuf *buf, EjsVar *obj, int indentLevel); +static void indent(MprBuf *bp, int level); +static char *cleanTagName(char *name); + +/******************************************************************************/ +/* + * Define the E4X classes (XML, XMLList) + */ + +int ejsDefineXmlClasses(Ejs *ep) +{ + EjsMethods *methods; + EjsVar *xmlClass, *xmlListClass; + + /* + * Create the XML class + */ + xmlClass = ejsDefineClass(ep, "XML", "Object", ejsXmlConstructor); + if (xmlClass == 0) { + return MPR_ERR_CANT_INITIALIZE; + } + + /* + * Define the XML class methods + */ + ejsDefineCMethod(ep, xmlClass, "text", text, EJS_NO_LOCAL); + ejsDefineCMethod(ep, xmlClass, "name", name, EJS_NO_LOCAL); + ejsDefineCMethod(ep, xmlClass, "load", load, EJS_NO_LOCAL); + ejsDefineCMethod(ep, xmlClass, "save", save, EJS_NO_LOCAL); + ejsDefineCMethod(ep, xmlClass, "toString", toString, EJS_NO_LOCAL); + ejsDefineCMethod(ep, xmlClass, "valueOf", valueOf, EJS_NO_LOCAL); + +/* MOB -- temporary only */ + ejsDefineCMethod(ep, xmlClass, "getList", getList, EJS_NO_LOCAL); + ejsDefineCMethod(ep, xmlClass, "setText", setText, EJS_NO_LOCAL); + + /* + * Setup the XML internal methods. + */ + methods = mprAllocTypeZeroed(ep, EjsMethods); + xmlClass->objectState->methods = methods; + + methods->createProperty = createXmlProperty; + methods->deleteProperty = deleteXmlProperty; + methods->getProperty = getXmlProperty; + methods->setProperty = setXmlProperty; + + /* + * Create the XMLList class + */ + xmlListClass = ejsDefineClass(ep, "XMLList", "Array", + ejsXmlListConstructor); + + /* + * Define the XMLList class methods + */ + + /* + * Setup the XML internal methods. + */ + methods = mprAllocTypeZeroed(ep, EjsMethods); + xmlListClass->objectState->methods = methods; + + methods->createProperty = createXmlListProperty; + methods->deleteProperty = deleteXmlListProperty; + methods->getProperty = getXmlListProperty; + methods->setProperty = setXmlListProperty; + + /* MOB -- need to complete xmlListClass */ + + return (ejsObjHasErrors(xmlClass) || ejsObjHasErrors(xmlListClass)) + ? MPR_ERR_CANT_INITIALIZE : 0; + return 0; +} + +/******************************************************************************/ +/* + * Routine to create an XML object using a default constructor + */ + +EjsVar *ejsCreateXml(Ejs *ep) +{ + EjsVar *op; + + op = ejsCreateSimpleObj(ep, "XML"); + if (op == 0) { + mprAssert(op); + return op; + } + ejsSetVarName(ep, op, "xmlNode"); + + /* + * Invoke class constructors manually (for speed and space) + */ + if (ejsXmlConstructor(ep, op, 0, 0) < 0) { + mprFree(op); + mprAssert(0); + return 0; + } + return op; +} + +/******************************************************************************/ +/* + * XML constructor + */ + +int ejsXmlConstructor(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + EjsVar *vp; + const char *str; + + ejsSetVarFlags(thisObj, EJS_XML_FLAGS_ELEMENT); + + if (argc == 1) { + vp = argv[0]; + + if (ejsVarIsObject(vp)) { + /* Convert DOM to XML. Not implemented */; + + } else if (ejsVarIsString(vp)) { + str = vp->string; + if (str == 0) { + return 0; + } + if (*str == '<') { + /* XML Literal */ + return loadXmlString(ep, thisObj, str); + + } else { + /* Load from file */ + return load(ep, thisObj, argc, argv); + } + } else { + ejsError(ep, EJS_TYPE_ERROR, "Bad type passed to XML constructor"); + return -1; + } + } + return 0; +} + +/******************************************************************************/ +/* + * Routine to create an XMLList object + */ + +EjsVar *ejsCreateXmlList(Ejs *ep) +{ + EjsVar *op; + + /* Sanity limit for size of hash table */ + + op = ejsCreateSimpleObj(ep, "XMLList"); + if (op == 0) { + mprAssert(0); + return op; + } + if (ejsArrayConstructor(ep, op, 0, 0) < 0 || + ejsXmlConstructor(ep, op, 0, 0) < 0) { + mprFree(op); + mprAssert(0); + return 0; + } + return op; +} + +/******************************************************************************/ +/* + * XMLList constructor + */ + +int ejsXmlListConstructor(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + // ejsSetVarFlags(vp, EJS_XML_FLAGS_ELEMENT); + return 0; +} + +/******************************************************************************/ +/******************************** Internal Methods ****************************/ +/******************************************************************************/ + +static EjsVar *createXmlProperty(Ejs *ep, EjsVar *obj, const char *property) +{ + return ejsGetVarPtr(ejsCreateSimpleProperty(ep, obj, property)); +} + +/******************************************************************************/ + +static int deleteXmlProperty(Ejs *ep, EjsVar *obj, const char *property) +{ + return ejsDeleteProperty(ep, obj, property); +} + +/******************************************************************************/ +/* MOB -- need ep as an arg */ +static EjsVar *getXmlProperty(Ejs *ep, EjsVar *obj, const char *property) +{ +#if NEW + EjsVar *lp; + + lp = ejsCreateXmlList(ep); + if (isdigit(*property)) { + /* MOB -- where do we store these. Do we need them ? */ + lp->targetObject = obj + lp->targetProperty = property + return getXmlListProperty(lp, property); + } + + /* What about a simple elment. Should it not return the text */ + + if (*property == '@') { + ap = ejsGetFirstProperty(obj, EJS_ENUM_ALL); + while (ap) { + vp = ejsGetVarPtr(ap); + /* MOB -- are attributes unique ? */ + if (vp->flags & EJS_XML_FLAGS_ATTRIBUTE && + strcmp(property, ap->name) == 0) { + ejsAppendXml(lp, vp); + } + ap = ejsGetNexttProperty(ap, EJS_ENUM_ALL); + } + } else { + while (ap) { + vp = ejsGetVarPtr(ap); + /* MOB -- are attributes unique ? */ + if (vp->flags & EJS_XML_FLAGS_ELEMENT && + strcmp(property, ap->name) == 0) { + ejsAppendXml(lp, vp); + } + ap = ejsGetNexttProperty(ap, EJS_ENUM_ALL); + } + } + return l; + + // Must always return XML or XMLList event for comments and attributes +#endif + return ejsGetVarPtr(ejsGetSimpleProperty(ep, obj, property)); +} + +/******************************************************************************/ + +static EjsVar *setXmlProperty(Ejs *ep, EjsVar *obj, const char *property, + const EjsVar *value) +{ + EjsProperty *pp; + EjsVar *vp; + + pp = ejsCreateSimpleProperty(ep, obj, property); + if (pp == 0) { + /* Should never happen */ + mprAssert(pp); + return 0; + } + vp = ejsGetVarPtr(pp); + if (ejsWriteVar(ep, vp, value, EJS_SHALLOW_COPY) < 0) { + return 0; + } + return ejsGetVarPtr(pp); +} + +/******************************************************************************/ +/* + NEW + +static EjsVar *setXmlProperty(Ejs *ep, EjsVar *op, const char *property, + EjsVar *value) +{ + + if ((value->objectState->baseClass != XML && + value->objectState->baseClass != XMLList) || + value->string[0] != '<') { + ejsVarToString(luevalue.toString(); + ejsRunMethod(ep, value, "toString", 0); + value = ejsDupVar(ep->result); + + } else { + value = ejsDupVar(value); + } + + if (isdigit(*property)) { + // ERROR -- reserved for future versions + return 0; + } + + if (*property == '@') { + if (op->objectState->baseClass == XMLList) { + if (op->obj.LENGTH_PROPERTY == 0) { + c = ""; + } else { + // Catenate all result of toString on all elts in list + } + } else { + c = c.toString(); + } + // Replace existing attribute of same name or insert + return; + } + for (i = op->obj.LENGTH - 1; i >= 0; i--) { + // Delete item of same name + } + if (not Found) { + Append new Xml object + - set [[name]], [[class]] == "element" + } + + mprFree(value); +} + + */ +/******************************************************************************/ +/************************************ Methods *********************************/ +/******************************************************************************/ + +static int load(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + const char *fileName; + XmlState *parser; + Exml *xp; + MprFile *file; + + if (argc != 1 || !ejsVarIsString(argv[0])) { + ejsError(ep, EJS_ARG_ERROR, "Bad args. Usage: load(fileName);"); + return -1; + } + fileName = argv[0]->string; + + /* MOB -- not romable + Need rom code in MPR not MprServices + */ + file = mprOpen(ep, fileName, O_RDONLY, 0664); + if (file == 0) { + ejsError(ep, EJS_IO_ERROR, "Can't open: %s", fileName); + return -1; + } + + /* MOB -- should we empty thisObj of all existing properties ? */ + + xp = initParser(ep, thisObj, fileName); + parser = exmlGetParseArg(xp); + + exmlSetInputStream(xp, readFileData, (void*) file); + + if (exmlParse(xp) < 0) { + if (! ejsGotException(ep)) { + ejsError(ep, EJS_IO_ERROR, "Can't parse XML file: %s\nDetails %s", + fileName, exmlGetErrorMsg(xp)); + } + termParser(xp); + mprClose(file); + return -1; + } + + ejsSetReturnValue(ep, parser->nodeStack[0].obj); + + termParser(xp); + mprClose(file); + + return 0; +} + +/******************************************************************************/ + +static int loadXmlString(Ejs *ep, EjsVar *thisObj, const char *xmlString) +{ + XmlState *parser; + Exml *xp; + + xp = initParser(ep, thisObj, "string"); + parser = exmlGetParseArg(xp); + + parser->inputBuf = xmlString; + parser->inputSize = strlen(xmlString); + + exmlSetInputStream(xp, readStringData, (void*) 0); + + if (exmlParse(xp) < 0) { + if (! ejsGotException(ep)) { + ejsError(ep, EJS_IO_ERROR, "Can't parse XML string\nError %s", + exmlGetErrorMsg(xp)); + } + termParser(xp); + return -1; + } + + ejsSetReturnValue(ep, parser->nodeStack[0].obj); + + termParser(xp); + + return 0; +} + +/******************************************************************************/ + +static int text(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + EjsVar *vp; + + vp = ejsGetVarPtr(ejsGetSimpleProperty(ep, thisObj, E4X_TEXT_PROPERTY)); + if (vp == 0) { + ejsSetReturnValueToString(ep, ""); + return 0; + } + ejsSetReturnValue(ep, vp); + return 0; +} + +/******************************************************************************/ +/* + * Return the tag name + */ + +static int name(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + EjsVar *vp; + + vp = ejsGetVarPtr(ejsGetSimpleProperty(ep, thisObj, E4X_TAG_NAME_PROPERTY)); + if (vp == 0) { + ejsSetReturnValueToString(ep, ""); + return 0; + } + ejsSetReturnValue(ep, vp); +#if UNDEFINED + char *name; + /* MOB -- not ideal as we can't guarantee thisObj is a property */ + name = ejsGetPropertyPtr(thisObj)->name; + if (name == 0) { + name = ""; + } + ejsSetReturnValueToString(ep, name); +#endif + return 0; +} + +/******************************************************************************/ +/* MOB -- temporary only */ + +static int setText(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + if (argc != 1) { + ejsArgError(ep, "usage: setText(string)"); + } + + ejsSetProperty(ep, thisObj, E4X_TEXT_PROPERTY, argv[0]); + ejsSetReturnValue(ep, argv[0]); + return 0; +} + +/******************************************************************************/ + +static Exml *initParser(Ejs *ep, EjsVar *thisObj, const char *fileName) +{ + XmlState *parser; + Exml *xp; + + xp = exmlOpen(ep, 512, E4X_BUF_MAX); + mprAssert(xp); + + /* + * Create the parser stack + */ + parser = mprAllocTypeZeroed(ep, XmlState); + parser->ep = ep; + parser->nodeStack[0].obj = thisObj; + parser->xmlClass = ejsGetClass(ep, 0, "XML"); + parser->xmlListClass = ejsGetClass(ep, 0, "XMLList"); + parser->fileName = fileName; + + exmlSetParseArg(xp, parser); + exmlSetParserHandler(xp, parserHandler); + + return xp; +} + +/******************************************************************************/ + +static void termParser(Exml *xp) +{ + mprFree(exmlGetParseArg(xp)); + exmlClose(xp); +} + +/******************************************************************************/ +/* + * XML parsing callback. Called for each elt and attribute/value pair. + * For speed, we handcraft the object model here rather than calling + * putXmlProperty. + * + * "<!-- txt -->" parseHandler(efd, , EXML_COMMENT); + * "<elt" parseHandler(efd, , EXML_NEW_ELT); + * "...att=value" parseHandler(efd, , EXML_NEW_ATT); + * "<elt ...>" parseHandler(efd, , EXML_ELT_DEFINED); + * "<elt/>" parseHandler(efd, , EXML_SOLO_ELT_DEFINED); + * "<elt> ...<" parseHandler(efd, , EXML_ELT_DATA); + * "...</elt>" parseHandler(efd, , EXML_END_ELT); + * + * Note: we recurse on every new nested elt. + */ + +static int parserHandler(Exml *xp, int state, const char *tagName, + const char *attName, const char *value) +{ + XmlState *parser; + XmlTagState *tos; + EjsVar *currentNode, *vp, *tagNode, *parent, *vpx; + EjsProperty *pp; + Ejs *ep; + char *name; + + parser = (XmlState*) xp->parseArg; + ep = parser->ep; + tos = &parser->nodeStack[parser->topOfStack]; + currentNode = tos->obj; + + mprAssert(state >= 0); + mprAssert(tagName && *tagName); + + switch (state) { + case EXML_PI: + /* + * By using a property name with a leading space, we can store + * non-user-visible processing instructions as regular properties. + */ + pp = ejsCreateSimpleNonUniqueProperty(ep, currentNode, E4X_PI_PROPERTY); + ejsMakePropertyEnumerable(pp, 1); + vp = ejsGetVarPtr(pp); + ejsWriteVarAsString(ep, vp, value); + ejsSetVarFlags(vp, EJS_XML_FLAGS_PI); + break; + + case EXML_COMMENT: + /* + * By using a property name with a leading space, we can store + * non- user-visible comments as regular properties. + */ + pp = ejsCreateSimpleNonUniqueProperty(ep, currentNode, + E4X_COMMENT_PROPERTY); + ejsMakePropertyEnumerable(pp, 1); + vp = ejsGetVarPtr(pp); + ejsWriteVarAsString(ep, vp, value); + ejsSetVarFlags(vp, EJS_XML_FLAGS_COMMENT); + break; + + case EXML_NEW_ELT: + if (parser->topOfStack > E4X_MAX_NODE_DEPTH) { + ejsError(ep, EJS_IO_ERROR, + "XML nodes nested too deeply in %s at line %d", + parser->fileName, exmlGetLineNumber(xp)); + return MPR_ERR_BAD_SYNTAX; + } + + name = mprStrdup(xp, tagName); + if (name == 0) { + return MPR_ERR_MEMORY; + } + + if (cleanTagName(name) < 0) { + ejsError(ep, EJS_TYPE_ERROR, "Bad XML tag name in %s at %d", + parser->fileName, exmlGetLineNumber(xp)); + mprFree(name); + return MPR_ERR_BAD_SYNTAX; + } + + pp = ejsCreateSimpleNonUniqueProperty(ep, currentNode, name); + ejsMakePropertyEnumerable(pp, 1); + + tagNode = ejsGetVarPtr(pp); + + /* MOB -- OPT */ + vpx = ejsCreateXml(ep); + vp = ejsWriteVar(ep, tagNode, vpx, EJS_SHALLOW_COPY); + ejsMakeObjLive(vp, 1); + ejsFreeVar(ep, vpx); + + /* MOB -- return code */ + pp = ejsSetPropertyToString(ep, vp, E4X_TAG_NAME_PROPERTY, name); + ejsMakePropertyEnumerable(pp, 0); + + ejsSetVarFlags(vp, EJS_XML_FLAGS_ELEMENT); + ejsMakePropertyEnumerable(ejsGetPropertyPtr(vp), 1); + + tos = &parser->nodeStack[++(parser->topOfStack)]; + currentNode = tos->obj = vp; + tos->attributes = 0; + tos->comments = 0; + mprFree(name); + break; + + case EXML_NEW_ATT: + if (mprAllocSprintf(MPR_LOC_ARGS(xp), &name, 0, "@%s", attName) < 0) { + return MPR_ERR_MEMORY; + } + pp = ejsCreateProperty(ep, currentNode, name); + ejsMakePropertyEnumerable(pp, 1); + + vp = ejsGetVarPtr(pp); + ejsWriteVarAsString(ep, vp, value); + ejsSetVarFlags(vp, EJS_XML_FLAGS_ATTRIBUTE); + mprFree(name); + break; + + case EXML_SOLO_ELT_DEFINED: + parser->topOfStack--; + mprAssert(parser->topOfStack >= 0); + tos = &parser->nodeStack[parser->topOfStack]; + break; + + case EXML_ELT_DEFINED: + if (parser->topOfStack > 0) { + parent = parser->nodeStack[parser->topOfStack - 1].obj; + ejsSetProperty(ep, currentNode, E4X_PARENT_PROPERTY, parent); + } + break; + + case EXML_ELT_DATA: + case EXML_CDATA: + pp = ejsCreateSimpleNonUniqueProperty(ep, currentNode, + E4X_TEXT_PROPERTY); + ejsMakePropertyEnumerable(pp, 1); + vp = ejsGetVarPtr(pp); + ejsWriteVarAsString(ep, vp, value); + ejsSetVarFlags(vp, EJS_XML_FLAGS_TEXT); + break; + + case EXML_END_ELT: + /* + * This is the closing element in a pair "<x>...</x>". + * Pop the stack frame off the elt stack + */ + parser->topOfStack--; + mprAssert(parser->topOfStack >= 0); + tos = &parser->nodeStack[parser->topOfStack]; + break; + + default: + ejsError(ep, EJS_IO_ERROR, "XML error in %s at %d\nDetails %s", + parser->fileName, exmlGetLineNumber(xp), exmlGetErrorMsg(xp)); + mprAssert(0); + return MPR_ERR_BAD_SYNTAX; + } + return 0; +} + +/******************************************************************************/ + +static char *cleanTagName(char *name) +{ + char *cp; + + for (cp = name; *cp; cp++) { + if (*cp == ':') { + *cp = '_'; + } else if (!isalnum(*cp) && *cp != '_' && *cp != '$' && *cp != '@') { + return 0; + } + } + return name; +} + +/******************************************************************************/ + +static int readFileData(Exml *xp, void *data, char *buf, int size) +{ + mprAssert(xp); + mprAssert(data); + mprAssert(buf); + mprAssert(size > 0); + + return mprRead((MprFile*) data, buf, size); +} + +/******************************************************************************/ + +static int readStringData(Exml *xp, void *data, char *buf, int size) +{ + XmlState *parser; + int rc, len; + + mprAssert(xp); + mprAssert(buf); + mprAssert(size > 0); + + parser = (XmlState*) xp->parseArg; + + if (parser->inputPos < parser->inputSize) { + len = min(size, (parser->inputSize - parser->inputPos)); + rc = mprMemcpy(buf, size, &parser->inputBuf[parser->inputPos], len); + parser->inputPos += len; + return rc; + } + return 0; +} + +/******************************************************************************/ + +static int save(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + const char *fileName; + MprBuf *buf; + MprFile *file; + int bytes, len; + + if (argc != 1 || !ejsVarIsString(argv[0])) { + ejsError(ep, EJS_ARG_ERROR, "Bad args. Usage: save(fileName);"); + return -1; + } + fileName = argv[0]->string; + + /* MOB -- not romable + Need rom code in MPR not MprServices + */ + + /* + * Convert to a string + */ + buf = mprCreateBuf(ep, E4X_BUF_SIZE, E4X_BUF_MAX); + if (xmlToString(ep, buf, thisObj, -1) < 0) { + mprFree(buf); + return -1; + } + + file = mprOpen(ep, fileName, + O_CREAT | O_TRUNC | O_WRONLY | O_TEXT, 0664); + if (file == 0) { + ejsError(ep, EJS_IO_ERROR, "Can't open: %s, %d", fileName, + mprGetOsError()); + return -1; + } + + len = mprGetBufLength(buf); + bytes = mprWrite(file, buf->start, len); + if (bytes != len) { + ejsError(ep, EJS_IO_ERROR, "Can't write to: %s", fileName); + mprClose(file); + return -1; + } + mprWrite(file, "\n", 1); + mprFree(buf); + + mprClose(file); + + return 0; +} + +/******************************************************************************/ + +static int toString(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + MprBuf *buf; + + buf = mprCreateBuf(ep, E4X_BUF_SIZE, E4X_BUF_MAX); + + if (xmlToString(ep, buf, thisObj, -1) < 0) { + mprFree(buf); + return -1; + } + ejsWriteVarAsString(ep, ep->result, (char*) buf->start); + + mprFree(buf); + + return 0; +} + +/******************************************************************************/ +/* MOB -- need to support XMLList */ + +static int xmlToString(Ejs *ep, MprBuf *buf, EjsVar *obj, int indentLevel) +{ + EjsProperty *pp; + EjsVar *vp; + char *varBuf; + int endTag, sawElements; + + if (indentLevel < 0) { + mprPutStringToBuf(buf, "<?xml version=\"1.0\"?>"); + } + + switch (obj->type) { + case EJS_TYPE_STRING: + if (obj->flags & EJS_XML_FLAGS_ATTRIBUTE) { + mprPutFmtStringToBuf(buf, " %s=\"%s\"", + &ejsGetPropertyPtr(obj)->name[1], obj->string); + /* No new line */ + + } else if (obj->flags & EJS_XML_FLAGS_COMMENT) { + mprPutCharToBuf(buf, '\n'); + indent(buf, indentLevel); + mprPutFmtStringToBuf(buf, "<!-- %s -->", obj->string); + + } else if (obj->flags & EJS_XML_FLAGS_TEXT) { + mprPutStringToBuf(buf, obj->string); + + } else { +// indent(buf, indentLevel); + mprPutStringToBuf(buf, obj->string); +// mprPutCharToBuf(buf, '\n'); + } + break; + + default: + /* Primitive types come here */ + indent(buf, indentLevel); + /* MOB -- rc */ + varBuf = ejsVarToString(ep, obj); + mprPutStringToBuf(buf, varBuf); + break; + + case EJS_TYPE_OBJECT: + if (obj->objectState->baseClass == ejsGetClass(ep, 0, "XML")) { + if (!obj->objectState->visited) { + obj->objectState->visited = 1; + + /* MOB -- opt. Flags would be quicker */ + if (strcmp(ejsGetPropertyPtr(obj)->name, + E4X_PARENT_PROPERTY) == 0) { + return 0; + } + /* + * MOB -- short term fix for tags with no body but with + * attributes + */ + if (getNumElements(obj) == 0 && 0) { + /* + * XML element is simple with no elements, so return just + * the text. + */ + if (getText(buf, obj) < 0) { + ejsError(ep, EJS_IO_ERROR, + "XML is to big to convert to a string"); + obj->objectState->visited = 0; + return -1; + } + + } else if (obj->flags & (EJS_XML_FLAGS_ELEMENT)) { + /* + * XML object is complex (has elements) so return full XML + * content. + */ + mprPutCharToBuf(buf, '\n'); + indent(buf, indentLevel); + + /* + * When called from toString, obj is not a property + */ + if (indentLevel >= 0) { + mprPutFmtStringToBuf(buf, "<%s", + ejsGetPropertyPtr(obj)->name); + endTag = 0; + + } else { + endTag = 1; + } + + sawElements = 0; + pp = ejsGetFirstProperty(obj, 0); + while (pp) { + vp = ejsGetVarPtr(pp); + + if (! (vp->flags & EJS_XML_FLAGS_ATTRIBUTE)) { + if (endTag == 0) { + endTag++; + mprPutStringToBuf(buf, ">"); + } + } + if (vp->flags & EJS_XML_FLAGS_ELEMENT) { + if (strcmp(ejsGetPropertyPtr(vp)->name, + E4X_PARENT_PROPERTY) != 0) { + sawElements++; + } + } + + if (xmlToString(ep, buf, ejsGetVarPtr(pp), + indentLevel + 1) < 0){ + return -1; + } + + pp = ejsGetNextProperty(pp, 0); + } + if (indentLevel >= 0) { + if (sawElements) { + mprPutCharToBuf(buf, '\n'); + indent(buf, indentLevel); + } + mprPutFmtStringToBuf(buf, "</%s>", + ejsGetPropertyPtr(obj)->name); + } + } + obj->objectState->visited = 0; + } + return 0; + } + + if (obj->objectState->baseClass == ejsGetClass(ep, 0, "XMLList")) { + indent(buf, indentLevel); + /* MOB -- TBD */ + return 0; + } + + /* + * All other objects. Allow other objects to override toString + */ + if (ejsRunMethod(ep, obj->objectState->baseClass, "toString", + 0) < 0) { + return -1; + } + if (ejsVarIsString(ep->result)) { + indent(buf, indentLevel); + mprPutStringToBuf(buf, obj->string); + } + break; + } + return 0; +} + +/******************************************************************************/ + +static void indent(MprBuf *bp, int level) +{ + int i; + + for (i = 0; i < level; i++) { + mprPutCharToBuf(bp, '\t'); + } +} + +/******************************************************************************/ + +static int valueOf(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + if (argc != 0) { + mprAssert(0); + return -1; + } + + switch (thisObj->type) { + default: + case EJS_TYPE_UNDEFINED: + case EJS_TYPE_NULL: + case EJS_TYPE_CMETHOD: + case EJS_TYPE_OBJECT: + case EJS_TYPE_METHOD: + case EJS_TYPE_STRING_CMETHOD: + ejsWriteVar(ep, ep->result, thisObj, EJS_SHALLOW_COPY); + break; + + case EJS_TYPE_STRING: + ejsWriteVarAsInteger(ep, ep->result, atoi(thisObj->string)); + break; + + case EJS_TYPE_BOOL: + case EJS_TYPE_INT: +#if BLD_FEATURE_INT64 + case EJS_TYPE_INT64: +#endif +#if BLD_FEATURE_FLOATING_POINT + case EJS_TYPE_FLOAT: +#endif + ejsWriteVar(ep, ep->result, thisObj, EJS_SHALLOW_COPY); + break; + } + return 0; +} + +/******************************************************************************/ + +static int getList(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv) +{ + const char *nodeName; + EjsProperty *pp; + EjsVar *list, *vp; + + if (argc != 1) { + nodeName = 0; + } else { + nodeName = argv[0]->string; + } + + list = ejsCreateArray(ep, 0); + + pp = ejsGetFirstProperty(thisObj, EJS_ENUM_ALL); + while (pp) { + vp = ejsGetVarPtr(pp); + if (vp->type == EJS_TYPE_OBJECT) { + if (strcmp(ejsGetPropertyPtr(vp)->name, E4X_PARENT_PROPERTY) != 0) { + if (vp->flags & EJS_XML_FLAGS_ELEMENT && + (nodeName == 0 || strcmp(nodeName, pp->name) == 0)) { + ejsAddArrayElt(ep, list, vp, EJS_SHALLOW_COPY); + } + } + } + pp = ejsGetNextProperty(pp, EJS_ENUM_ALL); + } + + ejsSetReturnValueAndFree(ep, list); + return 0; +} + +/******************************************************************************/ + +static int getNumElements(EjsVar *obj) +{ + EjsProperty *pp; + int count; + + count = 0; + pp = ejsGetFirstProperty(obj, EJS_ENUM_ALL); + while (pp) { + if (ejsGetVarPtr(pp)->flags & EJS_XML_FLAGS_ELEMENT) { + count++; + } + pp = ejsGetNextProperty(pp, EJS_ENUM_ALL); + } + return count; +} + +/******************************************************************************/ +/* MOB - This needs to be a public method */ + +static int getText(MprBuf *buf, EjsVar *obj) +{ + EjsProperty *pp; + EjsVar *vp; + + pp = ejsGetFirstProperty(obj, EJS_ENUM_ALL); + while (pp) { + vp = ejsGetVarPtr(pp); + if (vp->flags & EJS_XML_FLAGS_TEXT) { + /* MOB -- should test for overflow */ + mprPutStringToBuf(buf, vp->string); + } + pp = ejsGetNextProperty(pp, EJS_ENUM_ALL); + } + return 0; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************** Internal Methods ****************************/ +/******************************************************************************/ + +static EjsVar *createXmlListProperty(Ejs *ep, EjsVar *obj, const char *property) +{ + return ejsGetVarPtr(ejsCreateProperty(ep, obj, property)); +} + +/******************************************************************************/ + +static int deleteXmlListProperty(Ejs *ep, EjsVar *obj, const char *property) +{ + return ejsDeleteProperty(ep, obj, property); +} + +/******************************************************************************/ + +static EjsVar *getXmlListProperty(Ejs *ep, EjsVar *obj, const char *property) +{ + // Must always return XML or XMLList event for comments and attributes + return ejsGetVarPtr(ejsGetSimpleProperty(ep, obj, property)); +} + +/******************************************************************************/ + +static EjsVar *setXmlListProperty(Ejs *ep, EjsVar *obj, const char *property, + const EjsVar *value) +{ + EjsProperty *pp; + EjsVar *vp; + + pp = ejsGetSimpleProperty(ep, obj, property); + if (pp == 0) { + mprAssert(pp); + return 0; + } + vp = ejsGetVarPtr(pp); + if (ejsWriteVar(ep, vp, value, EJS_SHALLOW_COPY) < 0){ + mprAssert(0); + return 0; + } + return ejsGetVarPtr(pp); +} + +/******************************************************************************/ +/* + NEW + +static EjsVar *putXmlListProperty(EjsVar *op, const char *property, + EjsVar *value) +{ + + if ((value->objectState->baseClass != XML && + value->objectState->baseClass != XMLList) || + value->string[0] != '<') { + c = value.toString(); + } else { + value = ejsDupVar(value); + ?? + } + if (isdigit(*property)) { + // ERROR + return 0; + } + if (*property == '@') { + if (op->objectState->baseClass == XMLList) { + if (op->obj.LENGTH_PROPERTY == 0) { + c = ""; + } else { + // Catenate all result of toString on all elts in list + } + } else { + c = c.toString(); + } + // Replace existing attribute of same name or insert + return; + } + for (i = op->obj.LENGTH - 1; i >= 0; i--) { + // Delete item of same name + } + if (not Found) { + Append new Xml object + - set [[name]], [[class]] == "element" + } +} + + */ + +/******************************************************************************/ +#else +void ejs4xDummy() {} + +/******************************************************************************/ +#endif /* BLD_FEATURE_EJS_E4X */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim:tw=78 + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ |