/* Lookup a property by name. There are 7 kinds of lookups: prop, @att, [prop], *, @*, .name, .@name */ static EjsObj *getXmlPropertyByName(Ejs *ejs, EjsXML *xml, EjsName qname) { EjsXML *item, *result, *list; int next, nextList; result = 0; assert(xml->kind < 5); if (isdigit((uchar) qname.name->value[0]) && allDigitsForXml(qname.name)) { /* Consider xml as a list with only one entry == xml. Then return the 0'th entry */ return (EjsObj*) xml; } if (qname.name->value[0] == '@') { /* @ and @* */ result = ejsCreateXMLList(ejs, xml, qname); if (xml->attributes) { for (next = 0; (item = mprGetNextItem(xml->attributes, &next)) != 0; ) { assert(qname.name->value[0] == '@'); if (qname.name->value[1] == '*' || wcmp(item->qname.name->value, &qname.name->value[1]) == 0) { result = ejsAppendToXML(ejs, result, item); } } } } else if (qname.name->value[0] == '.') { /* Decenders (do ..@ also) */ result = ejsGetXMLDescendants(ejs, xml, qname); } else { /* name and * */ result = ejsCreateXMLList(ejs, xml, qname); if (xml->elements) { for (next = 0; (item = mprGetNextItem(xml->elements, &next)) != 0; ) { if (item->kind == EJS_XML_LIST) { list = item; for (nextList = 0; (item = mprGetNextItem(list->elements, &nextList)) != 0; ) { assert(item->qname.name); if (qname.name->value[0] == '*' || ejsCompareString(ejs, item->qname.name, qname.name) == 0) { result = ejsAppendToXML(ejs, result, item); } } } else if (item->qname.name) { assert(item->qname.name); if (qname.name->value[0] == '*' || ejsCompareString(ejs, item->qname.name, qname.name) == 0) { result = ejsAppendToXML(ejs, result, item); } } } } } return (EjsObj*) result; }
/* Create a value node. If the value is an XML node already, we are done. Otherwise, cast the value to a string and create a text child node containing the string value. */ static EjsXML *createValueNode(Ejs *ejs, EjsXML *elt, EjsObj *value) { EjsXML *text; EjsString *str; if (ejsIsXML(ejs, value)) { return (EjsXML*) value; } if ((str = (EjsString*) ejsCast(ejs, value, String)) == NULL) { return 0; } if (mprGetListLength(elt->elements) == 1) { /* Update an existing text element */ text = mprGetFirstItem(elt->elements); if (text->kind == EJS_XML_TEXT) { text->value = str; return elt; } } /* Create a new text element */ if (str->value[0] != '\0') { text = ejsCreateXML(ejs, EJS_XML_TEXT, N(NULL, NULL), elt, str); elt = ejsAppendToXML(ejs, elt, text); } return elt; }
/* Lookup a property by name. There are 7 kinds of lookups: prop, @att, [prop], *, @*, .name, .@name */ static EjsObj *getXmlListPropertyByName(Ejs *ejs, EjsXML *list, EjsName qname) { EjsXML *result, *subList, *item; int nextItem; /* Get the n'th item in the list */ if (isdigit((uchar) qname.name->value[0]) && allDigitsForXmlList(qname.name)) { return mprGetItem(list->elements, ejsAtoi(ejs, qname.name, 10)); } result = ejsCreateXMLList(ejs, list, qname); /* Build a list of all the elements that themselves have a property qname */ for (nextItem = 0; (item = mprGetNextItem(list->elements, &nextItem)) != 0; ) { if (item->kind == EJS_XML_ELEMENT) { subList = ejsGetPropertyByName(ejs, (EjsObj*) item, qname); assure(ejsIsXML(ejs, subList)); ejsAppendToXML(ejs, result, subList); } else { // TODO - do we ever get a list in a list? assure(0); } } return (EjsObj*) result; }
PUBLIC EjsXML *ejsGetXMLDescendants(Ejs *ejs, EjsXML *xml, EjsName qname) { EjsXML *item, *result; int next; result = ejsCreateXMLList(ejs, xml, qname); if (result == 0) { return 0; } if (qname.name->value[0] == '.' && qname.name->value[1] == '@') { if (xml->attributes) { for (next = 0; (item = mprGetNextItem(xml->attributes, &next)) != 0; ) { if (qname.name->value[2] == '*' || wcmp(item->qname.name->value, &qname.name->value[2]) == 0) { result = ejsAppendToXML(ejs, result, item); } } } if (xml->elements) { for (next = 0; (item = mprGetNextItem(xml->elements, &next)) != 0; ) { result = ejsAppendToXML(ejs, result, ejsGetXMLDescendants(ejs, item, qname)); } } } else { if (xml->elements) { for (next = 0; (item = mprGetNextItem(xml->elements, &next)) != 0; ) { if (qname.name->value[0] == '*' || wcmp(item->qname.name->value, &qname.name->value[1]) == 0) { result = ejsAppendToXML(ejs, result, item); } else { result = ejsAppendToXML(ejs, result, ejsGetXMLDescendants(ejs, item, qname)); } } } } return result; }
/* Set an alpha property by name. */ static int setAlphaPropertyByName(Ejs *ejs, EjsXML *list, EjsName qname, EjsObj *value) { EjsXML *elt, *targetObject; int count; targetObject = 0; count = ejsGetLength(ejs, (EjsObj*) list); if (count > 1) { // TODO - why no error in spec? assure(0); return 0; } if (count == 0) { /* Empty list so resolve the real target object and append it to the list. */ targetObject = resolve(ejs, list); if (targetObject == 0) { return 0; } if (ejsGetLength(ejs, (EjsObj*) targetObject) != 1) { return 0; } ejsAppendToXML(ejs, list, targetObject); } /* Update the element */ assure(ejsGetLength(ejs, (EjsObj*) list) == 1); elt = mprGetItem(list->elements, 0); // TODO OPT - GetFirstItem assure(elt); ejsSetPropertyByName(ejs, elt, qname, value); return 0; }
static int parserHandler(MprXml *xp, int state, cchar *tagName, cchar *attName, cchar *value) { Ejs *ejs; EjsXmlState *parser; EjsXmlTagState *tos; EjsName qname; EjsXML *xml, *node, *parent; parser = (EjsXmlState*) xp->parseArg; ejs = parser->ejs; tos = &parser->nodeStack[parser->topOfStack]; xml = tos->obj; mprAssert(xml); mprAssert(state >= 0); mprAssert(tagName && *tagName); switch (state) { case MPR_XML_PI: node = ejsCreateXML(ejs, EJS_XML_PROCESSING, NULL, xml, value); ejsAppendToXML(ejs, xml, node); break; case MPR_XML_COMMENT: node = ejsCreateXML(ejs, EJS_XML_COMMENT, NULL, xml, value); ejsAppendToXML(ejs, xml, node); break; case MPR_XML_NEW_ELT: if (parser->topOfStack > E4X_MAX_NODE_DEPTH) { ejsThrowSyntaxError(ejs, "XML nodes nested too deeply in %s at line %d", parser->filename, mprXmlGetLineNumber(xp)); return MPR_ERR_BAD_SYNTAX; } if (xml->kind <= 0) { ejsConfigureXML(ejs, xml, EJS_XML_ELEMENT, tagName, xml, NULL); } else { ejsName(&qname, 0, tagName); xml = ejsCreateXML(ejs, EJS_XML_ELEMENT, &qname, xml, NULL); tos = &parser->nodeStack[++(parser->topOfStack)]; tos->obj = (EjsXML*) xml; tos->attributes = 0; tos->comments = 0; } break; case MPR_XML_NEW_ATT: ejsName(&qname, 0, attName); node = ejsCreateXML(ejs, EJS_XML_ATTRIBUTE, &qname, xml, value); ejsAppendAttributeToXML(ejs, xml, node); break; case MPR_XML_SOLO_ELT_DEFINED: if (parser->topOfStack > 0) { parent = parser->nodeStack[parser->topOfStack - 1].obj; ejsAppendToXML(ejs, parent, xml); parser->topOfStack--; mprAssert(parser->topOfStack >= 0); tos = &parser->nodeStack[parser->topOfStack]; } break; case MPR_XML_ELT_DEFINED: if (parser->topOfStack > 0) { parent = parser->nodeStack[parser->topOfStack - 1].obj; ejsAppendToXML(ejs, parent, xml); } break; case MPR_XML_ELT_DATA: case MPR_XML_CDATA: ejsName(&qname, 0, attName); node = ejsCreateXML(ejs, EJS_XML_TEXT, &qname, xml, value); ejsAppendToXML(ejs, xml, node); break; case MPR_XML_END_ELT: /* * This is the closing element in a pair "<x>...</x>". * Pop the stack frame off the elt stack */ if (parser->topOfStack > 0) { parser->topOfStack--; mprAssert(parser->topOfStack >= 0); tos = &parser->nodeStack[parser->topOfStack]; } break; default: ejsThrowSyntaxError(ejs, "XML error in %s at %d\nDetails %s", parser->filename, mprXmlGetLineNumber(xp), mprXmlGetErrorMsg(xp)); mprAssert(0); return MPR_ERR_BAD_SYNTAX; } return 0; }
/* Set a property by name There are 7 kinds of qname's: prop, @att, [prop], *, @*, .name, .@name */ static int setXmlPropertyByName(Ejs *ejs, EjsXML *xml, EjsName qname, EjsObj *value) { EjsXML *elt, *xvalue, *rp, *lastElt; EjsObj *originalValue; int index, last; last = 0; lastElt = 0; if (isdigit((uchar) qname.name->value[0]) && allDigitsForXml(qname.name)) { ejsThrowTypeError(ejs, "Integer indicies for set are not allowed"); return EJS_ERR; } if (xml->kind != EJS_XML_ELEMENT) { // TODO spec requires this -- but why? -- surely throw? return 0; } /* Massage the value type. */ originalValue = value; xvalue = (EjsXML*) value; if (ejsIsXML(ejs, xvalue)) { if (xvalue->kind == EJS_XML_LIST) { value = cloneXml(ejs, xvalue, 1); } else if (xvalue->kind == EJS_XML_TEXT || xvalue->kind == EJS_XML_ATTRIBUTE) { value = ejsCast(ejs, originalValue, String); } else { value = cloneXml(ejs, xvalue, 1); } } else { value = ejsCast(ejs, value, String); } if (qname.name->value[0] == '@') { return setXmlPropertyAttributeByName(ejs, xml, qname, value); } /* Delete redundant elements by the same name. */ if (xml->elements) { for (last = -1, index = -1; (elt = mprGetPrevItem(xml->elements, &index)) != 0; ) { if (qname.name->value[0] == '*' || (elt->kind == EJS_XML_ELEMENT && elt->qname.name == qname.name)) { /* Must remove all redundant elements of the same name except the first one */ if (last >= 0) { rp = mprGetItem(xml->elements, last); rp->parent = 0; mprRemoveItemAtPos(xml->elements, last); } last = index; lastElt = elt; } } } if (xml->elements == 0) { // TODO - need routine to do this centrally so we can control the default number of elements in the list? xml->elements = mprCreateList(-1, 0); } elt = lastElt; index = last; if (qname.name->value[0] == '*') { /* Special case when called from XMLList to update the value of an element */ xml = createValueNode(ejs, xml, value); } else if (elt == 0) { /* Not found. New node required. */ elt = ejsCreateXML(ejs, EJS_XML_ELEMENT, qname, xml, NULL); if (elt == 0) { return 0; } index = mprGetListLength(xml->elements); xml = ejsAppendToXML(ejs, xml, createValueNode(ejs, elt, value)); } else { /* Update existing element. */ xml = ejsSetXMLElement(ejs, xml, index, createValueNode(ejs, elt, value)); } if (xml == 0) { return EJS_ERR; } return index; }