mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-11 02:10:17 +01:00
ba7f53c261
mozilla-573873-1.8.patch mozilla-liveconnect-1.9.2.13.patch mozilla-nsTArray.patch
8403 lines
248 KiB
C
8403 lines
248 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=4 sw=4 et tw=78:
|
|
*
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is SpiderMonkey E4X code, released August, 2004.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#include "jsstddef.h"
|
|
#include "jsconfig.h"
|
|
|
|
#if JS_HAS_XML_SUPPORT
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "jstypes.h"
|
|
#include "jsbit.h"
|
|
#include "jsprf.h"
|
|
#include "jsutil.h"
|
|
#include "jsapi.h"
|
|
#include "jsarray.h"
|
|
#include "jsatom.h"
|
|
#include "jsbool.h"
|
|
#include "jscntxt.h"
|
|
#include "jsfun.h"
|
|
#include "jsgc.h"
|
|
#include "jsinterp.h"
|
|
#include "jslock.h"
|
|
#include "jsnum.h"
|
|
#include "jsobj.h"
|
|
#include "jsopcode.h"
|
|
#include "jsparse.h"
|
|
#include "jsscan.h"
|
|
#include "jsscope.h"
|
|
#include "jsscript.h"
|
|
#include "jsstr.h"
|
|
#include "jsxml.h"
|
|
|
|
#ifdef DEBUG
|
|
#include <string.h> /* for #ifdef DEBUG memset calls */
|
|
#endif
|
|
|
|
/*
|
|
* NOTES
|
|
* - in the js shell, you must use the -x command line option, or call
|
|
* options('xml') before compiling anything that uses XML literals
|
|
*
|
|
* TODO
|
|
* - XXXbe patrol
|
|
* - Fuse objects and their JSXML* private data into single GC-things
|
|
* - fix function::foo vs. x.(foo == 42) collision using proper namespacing
|
|
* - fix the !TCF_HAS_DEFXMLNS optimization in js_FoldConstants
|
|
* - JSCLASS_DOCUMENT_OBSERVER support -- live two-way binding to Gecko's DOM!
|
|
* - JS_TypeOfValue sure could use a cleaner interface to "types"
|
|
*/
|
|
|
|
#ifdef DEBUG_brendan
|
|
#define METERING 1
|
|
#endif
|
|
|
|
#ifdef METERING
|
|
static struct {
|
|
jsrefcount qname;
|
|
jsrefcount qnameobj;
|
|
jsrefcount liveqname;
|
|
jsrefcount liveqnameobj;
|
|
jsrefcount namespace;
|
|
jsrefcount namespaceobj;
|
|
jsrefcount livenamespace;
|
|
jsrefcount livenamespaceobj;
|
|
jsrefcount xml;
|
|
jsrefcount xmlobj;
|
|
jsrefcount livexml;
|
|
jsrefcount livexmlobj;
|
|
} xml_stats;
|
|
|
|
#define METER(x) JS_ATOMIC_INCREMENT(&(x))
|
|
#define UNMETER(x) JS_ATOMIC_DECREMENT(&(x))
|
|
#else
|
|
#define METER(x) /* nothing */
|
|
#define UNMETER(x) /* nothing */
|
|
#endif
|
|
|
|
/*
|
|
* Random utilities and global functions.
|
|
*/
|
|
const char js_isXMLName_str[] = "isXMLName";
|
|
const char js_XMLList_str[] = "XMLList";
|
|
const char js_localName_str[] = "localName";
|
|
const char js_xml_parent_str[] = "parent";
|
|
const char js_prefix_str[] = "prefix";
|
|
const char js_toXMLString_str[] = "toXMLString";
|
|
const char js_uri_str[] = "uri";
|
|
|
|
const char js_amp_entity_str[] = "&";
|
|
const char js_gt_entity_str[] = ">";
|
|
const char js_lt_entity_str[] = "<";
|
|
const char js_quot_entity_str[] = """;
|
|
|
|
#define IS_EMPTY(str) (JSSTRING_LENGTH(str) == 0)
|
|
#define IS_STAR(str) (JSSTRING_LENGTH(str) == 1 && *JSSTRING_CHARS(str) == '*')
|
|
|
|
static JSBool
|
|
xml_isXMLName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
*rval = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argv[0]));
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Namespace class and library functions.
|
|
*/
|
|
enum namespace_tinyid {
|
|
NAMESPACE_PREFIX = -1,
|
|
NAMESPACE_URI = -2
|
|
};
|
|
|
|
static JSBool
|
|
namespace_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSXMLNamespace *ns;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
ns = (JSXMLNamespace *)
|
|
JS_GetInstancePrivate(cx, obj, &js_NamespaceClass.base, NULL);
|
|
if (!ns)
|
|
return JS_TRUE;
|
|
|
|
switch (JSVAL_TO_INT(id)) {
|
|
case NAMESPACE_PREFIX:
|
|
*vp = ns->prefix ? STRING_TO_JSVAL(ns->prefix) : JSVAL_VOID;
|
|
break;
|
|
case NAMESPACE_URI:
|
|
*vp = STRING_TO_JSVAL(ns->uri);
|
|
break;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static void
|
|
namespace_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSXMLNamespace *ns;
|
|
JSRuntime *rt;
|
|
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
|
|
if (!ns)
|
|
return;
|
|
JS_ASSERT(ns->object == obj);
|
|
ns->object = NULL;
|
|
UNMETER(xml_stats.livenamespaceobj);
|
|
|
|
rt = cx->runtime;
|
|
if (rt->functionNamespaceObject == obj)
|
|
rt->functionNamespaceObject = NULL;
|
|
}
|
|
|
|
static void
|
|
namespace_mark_vector(JSContext *cx, JSXMLNamespace **vec, uint32 len)
|
|
{
|
|
uint32 i;
|
|
JSXMLNamespace *ns;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
ns = vec[i];
|
|
{
|
|
#ifdef GC_MARK_DEBUG
|
|
char buf[100];
|
|
|
|
JS_snprintf(buf, sizeof buf, "%s=%s",
|
|
ns->prefix ? JS_GetStringBytes(ns->prefix) : "",
|
|
JS_GetStringBytes(ns->uri));
|
|
#endif
|
|
GC_MARK(cx, ns, buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32
|
|
namespace_mark(JSContext *cx, JSObject *obj, void *arg)
|
|
{
|
|
JSXMLNamespace *ns;
|
|
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
|
|
GC_MARK(cx, ns, "private");
|
|
return 0;
|
|
}
|
|
|
|
static JSBool
|
|
namespace_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
|
|
{
|
|
JSXMLNamespace *ns, *ns2;
|
|
JSObject *obj2;
|
|
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
|
|
JS_ASSERT(JSVAL_IS_OBJECT(v));
|
|
obj2 = JSVAL_TO_OBJECT(v);
|
|
if (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_NamespaceClass.base) {
|
|
*bp = JS_FALSE;
|
|
} else {
|
|
ns2 = (JSXMLNamespace *) JS_GetPrivate(cx, obj2);
|
|
*bp = js_EqualStrings(ns->uri, ns2->uri);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JS_FRIEND_DATA(JSExtendedClass) js_NamespaceClass = {
|
|
{ "Namespace",
|
|
JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED |
|
|
JSCLASS_HAS_CACHED_PROTO(JSProto_Namespace),
|
|
JS_PropertyStub, JS_PropertyStub, namespace_getProperty, NULL,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, namespace_finalize,
|
|
NULL, NULL, NULL, NULL,
|
|
NULL, NULL, namespace_mark, NULL },
|
|
namespace_equality,NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL
|
|
};
|
|
|
|
#define NAMESPACE_ATTRS \
|
|
(JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
|
|
|
|
static JSPropertySpec namespace_props[] = {
|
|
{js_prefix_str, NAMESPACE_PREFIX, NAMESPACE_ATTRS, 0, 0},
|
|
{js_uri_str, NAMESPACE_URI, NAMESPACE_ATTRS, 0, 0},
|
|
{0,0,0,0,0}
|
|
};
|
|
|
|
static JSBool
|
|
namespace_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXMLNamespace *ns;
|
|
|
|
ns = (JSXMLNamespace *)
|
|
JS_GetInstancePrivate(cx, obj, &js_NamespaceClass.base, argv);
|
|
if (!ns)
|
|
return JS_FALSE;
|
|
|
|
*rval = STRING_TO_JSVAL(ns->uri);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSFunctionSpec namespace_methods[] = {
|
|
{js_toString_str, namespace_toString, 0,0,0},
|
|
{0,0,0,0,0}
|
|
};
|
|
|
|
JSXMLNamespace *
|
|
js_NewXMLNamespace(JSContext *cx, JSString *prefix, JSString *uri,
|
|
JSBool declared)
|
|
{
|
|
JSXMLNamespace *ns;
|
|
|
|
ns = (JSXMLNamespace *)
|
|
js_NewGCThing(cx, GCX_NAMESPACE, sizeof(JSXMLNamespace));
|
|
if (!ns)
|
|
return NULL;
|
|
ns->object = NULL;
|
|
ns->prefix = prefix;
|
|
ns->uri = uri;
|
|
ns->declared = declared;
|
|
METER(xml_stats.namespace);
|
|
METER(xml_stats.livenamespace);
|
|
return ns;
|
|
}
|
|
|
|
void
|
|
js_MarkXMLNamespace(JSContext *cx, JSXMLNamespace *ns)
|
|
{
|
|
GC_MARK(cx, ns->object, "object");
|
|
GC_MARK(cx, ns->prefix, "prefix");
|
|
GC_MARK(cx, ns->uri, "uri");
|
|
}
|
|
|
|
void
|
|
js_FinalizeXMLNamespace(JSContext *cx, JSXMLNamespace *ns)
|
|
{
|
|
UNMETER(xml_stats.livenamespace);
|
|
}
|
|
|
|
JSObject *
|
|
js_NewXMLNamespaceObject(JSContext *cx, JSString *prefix, JSString *uri,
|
|
JSBool declared)
|
|
{
|
|
JSXMLNamespace *ns;
|
|
JSTempValueRooter tvr;
|
|
JSObject *obj;
|
|
|
|
ns = js_NewXMLNamespace(cx, prefix, uri, declared);
|
|
if (!ns)
|
|
return NULL;
|
|
|
|
JS_PUSH_TEMP_ROOT_NAMESPACE(cx, ns, &tvr);
|
|
obj = js_GetXMLNamespaceObject(cx, ns);
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
return obj;
|
|
}
|
|
|
|
JSObject *
|
|
js_GetXMLNamespaceObject(JSContext *cx, JSXMLNamespace *ns)
|
|
{
|
|
JSObject *obj;
|
|
|
|
obj = ns->object;
|
|
if (obj) {
|
|
JS_ASSERT(JS_GetPrivate(cx, obj) == ns);
|
|
return obj;
|
|
}
|
|
obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
|
|
if (!obj || !JS_SetPrivate(cx, obj, ns)) {
|
|
cx->weakRoots.newborn[GCX_OBJECT] = NULL;
|
|
return NULL;
|
|
}
|
|
ns->object = obj;
|
|
METER(xml_stats.namespaceobj);
|
|
METER(xml_stats.livenamespaceobj);
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
* QName class and library functions.
|
|
*/
|
|
enum qname_tinyid {
|
|
QNAME_URI = -1,
|
|
QNAME_LOCALNAME = -2
|
|
};
|
|
|
|
static JSBool
|
|
qname_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSXMLQName *qn;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
qn = (JSXMLQName *)
|
|
JS_GetInstancePrivate(cx, obj, &js_QNameClass.base, NULL);
|
|
if (!qn)
|
|
return JS_TRUE;
|
|
|
|
switch (JSVAL_TO_INT(id)) {
|
|
case QNAME_URI:
|
|
*vp = qn->uri ? STRING_TO_JSVAL(qn->uri) : JSVAL_NULL;
|
|
break;
|
|
case QNAME_LOCALNAME:
|
|
*vp = STRING_TO_JSVAL(qn->localName);
|
|
break;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static void
|
|
qname_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSXMLQName *qn;
|
|
|
|
qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
|
|
if (!qn)
|
|
return;
|
|
JS_ASSERT(qn->object == obj);
|
|
qn->object = NULL;
|
|
UNMETER(xml_stats.liveqnameobj);
|
|
}
|
|
|
|
static void
|
|
anyname_finalize(JSContext* cx, JSObject* obj)
|
|
{
|
|
JSRuntime *rt;
|
|
|
|
/* Make sure the next call to js_GetAnyName doesn't try to use obj. */
|
|
rt = cx->runtime;
|
|
if (rt->anynameObject == obj)
|
|
rt->anynameObject = NULL;
|
|
|
|
qname_finalize(cx, obj);
|
|
}
|
|
|
|
static uint32
|
|
qname_mark(JSContext *cx, JSObject *obj, void *arg)
|
|
{
|
|
JSXMLQName *qn;
|
|
|
|
qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
|
|
GC_MARK(cx, qn, "private");
|
|
return 0;
|
|
}
|
|
|
|
static JSBool
|
|
qname_identity(JSXMLQName *qna, JSXMLQName *qnb)
|
|
{
|
|
if (!qna->uri ^ !qnb->uri)
|
|
return JS_FALSE;
|
|
if (qna->uri && !js_EqualStrings(qna->uri, qnb->uri))
|
|
return JS_FALSE;
|
|
return js_EqualStrings(qna->localName, qnb->localName);
|
|
}
|
|
|
|
static JSBool
|
|
qname_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
|
|
{
|
|
JSXMLQName *qn, *qn2;
|
|
JSObject *obj2;
|
|
|
|
qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
|
|
JS_ASSERT(JSVAL_IS_OBJECT(v));
|
|
obj2 = JSVAL_TO_OBJECT(v);
|
|
if (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_QNameClass.base) {
|
|
*bp = JS_FALSE;
|
|
} else {
|
|
qn2 = (JSXMLQName *) JS_GetPrivate(cx, obj2);
|
|
*bp = qname_identity(qn, qn2);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JS_FRIEND_DATA(JSExtendedClass) js_QNameClass = {
|
|
{ "QName",
|
|
JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED |
|
|
JSCLASS_HAS_CACHED_PROTO(JSProto_QName),
|
|
JS_PropertyStub, JS_PropertyStub, qname_getProperty, NULL,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, qname_finalize,
|
|
NULL, NULL, NULL, NULL,
|
|
NULL, NULL, qname_mark, NULL },
|
|
qname_equality, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL
|
|
};
|
|
|
|
/*
|
|
* Classes for the ECMA-357-internal types AttributeName and AnyName, which
|
|
* are like QName, except that they have no property getters. They share the
|
|
* qname_toString method, and therefore are exposed as constructable objects
|
|
* in this implementation.
|
|
*/
|
|
JS_FRIEND_DATA(JSClass) js_AttributeNameClass = {
|
|
js_AttributeName_str,
|
|
JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE |
|
|
JSCLASS_HAS_CACHED_PROTO(JSProto_AttributeName),
|
|
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, qname_finalize,
|
|
NULL, NULL, NULL, NULL,
|
|
NULL, NULL, qname_mark, NULL
|
|
};
|
|
|
|
JS_FRIEND_DATA(JSClass) js_AnyNameClass = {
|
|
js_AnyName_str,
|
|
JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE |
|
|
JSCLASS_HAS_CACHED_PROTO(JSProto_AnyName),
|
|
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, anyname_finalize,
|
|
NULL, NULL, NULL, NULL,
|
|
NULL, NULL, qname_mark, NULL
|
|
};
|
|
|
|
#define QNAME_ATTRS \
|
|
(JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
|
|
|
|
static JSPropertySpec qname_props[] = {
|
|
{js_uri_str, QNAME_URI, QNAME_ATTRS, 0, 0},
|
|
{js_localName_str, QNAME_LOCALNAME, QNAME_ATTRS, 0, 0},
|
|
{0,0,0,0,0}
|
|
};
|
|
|
|
static JSBool
|
|
qname_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSClass *clasp;
|
|
JSXMLQName *qn;
|
|
JSString *str, *qualstr;
|
|
size_t length;
|
|
jschar *chars;
|
|
|
|
clasp = OBJ_GET_CLASS(cx, obj);
|
|
if (clasp == &js_AttributeNameClass || clasp == &js_AnyNameClass) {
|
|
qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
|
|
} else {
|
|
qn = (JSXMLQName *)
|
|
JS_GetInstancePrivate(cx, obj, &js_QNameClass.base, argv);
|
|
if (!qn)
|
|
return JS_FALSE;
|
|
}
|
|
|
|
if (!qn->uri) {
|
|
/* No uri means wildcard qualifier. */
|
|
str = ATOM_TO_STRING(cx->runtime->atomState.starQualifierAtom);
|
|
} else if (IS_EMPTY(qn->uri)) {
|
|
/* Empty string for uri means localName is in no namespace. */
|
|
str = cx->runtime->emptyString;
|
|
} else {
|
|
qualstr = ATOM_TO_STRING(cx->runtime->atomState.qualifierAtom);
|
|
str = js_ConcatStrings(cx, qn->uri, qualstr);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
}
|
|
str = js_ConcatStrings(cx, str, qn->localName);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
|
|
if (str && clasp == &js_AttributeNameClass) {
|
|
length = JSSTRING_LENGTH(str);
|
|
chars = (jschar *) JS_malloc(cx, (length + 2) * sizeof(jschar));
|
|
if (!chars)
|
|
return JS_FALSE;
|
|
*chars = '@';
|
|
js_strncpy(chars + 1, JSSTRING_CHARS(str), length);
|
|
chars[++length] = 0;
|
|
str = js_NewString(cx, chars, length, 0);
|
|
if (!str) {
|
|
JS_free(cx, chars);
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
|
|
*rval = STRING_TO_JSVAL(str);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSFunctionSpec qname_methods[] = {
|
|
{js_toString_str, qname_toString, 0,0,0},
|
|
{0,0,0,0,0}
|
|
};
|
|
|
|
JSXMLQName *
|
|
js_NewXMLQName(JSContext *cx, JSString *uri, JSString *prefix,
|
|
JSString *localName)
|
|
{
|
|
JSXMLQName *qn;
|
|
|
|
qn = (JSXMLQName *) js_NewGCThing(cx, GCX_QNAME, sizeof(JSXMLQName));
|
|
if (!qn)
|
|
return NULL;
|
|
qn->object = NULL;
|
|
qn->uri = uri;
|
|
qn->prefix = prefix;
|
|
qn->localName = localName;
|
|
METER(xml_stats.qname);
|
|
METER(xml_stats.liveqname);
|
|
return qn;
|
|
}
|
|
|
|
void
|
|
js_MarkXMLQName(JSContext *cx, JSXMLQName *qn)
|
|
{
|
|
GC_MARK(cx, qn->object, "object");
|
|
GC_MARK(cx, qn->uri, "uri");
|
|
GC_MARK(cx, qn->prefix, "prefix");
|
|
GC_MARK(cx, qn->localName, "localName");
|
|
}
|
|
|
|
void
|
|
js_FinalizeXMLQName(JSContext *cx, JSXMLQName *qn)
|
|
{
|
|
UNMETER(xml_stats.liveqname);
|
|
}
|
|
|
|
JSObject *
|
|
js_NewXMLQNameObject(JSContext *cx, JSString *uri, JSString *prefix,
|
|
JSString *localName)
|
|
{
|
|
JSXMLQName *qn;
|
|
JSTempValueRooter tvr;
|
|
JSObject *obj;
|
|
|
|
qn = js_NewXMLQName(cx, uri, prefix, localName);
|
|
if (!qn)
|
|
return NULL;
|
|
JS_PUSH_TEMP_ROOT_QNAME(cx, qn, &tvr);
|
|
obj = js_GetXMLQNameObject(cx, qn);
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
return obj;
|
|
}
|
|
|
|
JSObject *
|
|
js_GetXMLQNameObject(JSContext *cx, JSXMLQName *qn)
|
|
{
|
|
JSObject *obj;
|
|
|
|
obj = qn->object;
|
|
if (obj) {
|
|
JS_ASSERT(JS_GetPrivate(cx, obj) == qn);
|
|
return obj;
|
|
}
|
|
obj = js_NewObject(cx, &js_QNameClass.base, NULL, NULL);
|
|
if (!obj || !JS_SetPrivate(cx, obj, qn)) {
|
|
cx->weakRoots.newborn[GCX_OBJECT] = NULL;
|
|
return NULL;
|
|
}
|
|
qn->object = obj;
|
|
METER(xml_stats.qnameobj);
|
|
METER(xml_stats.liveqnameobj);
|
|
return obj;
|
|
}
|
|
|
|
JSObject *
|
|
js_GetAttributeNameObject(JSContext *cx, JSXMLQName *qn)
|
|
{
|
|
JSObject *obj;
|
|
|
|
obj = qn->object;
|
|
if (obj) {
|
|
if (OBJ_GET_CLASS(cx, obj) == &js_AttributeNameClass)
|
|
return obj;
|
|
qn = js_NewXMLQName(cx, qn->uri, qn->prefix, qn->localName);
|
|
if (!qn)
|
|
return NULL;
|
|
}
|
|
|
|
obj = js_NewObject(cx, &js_AttributeNameClass, NULL, NULL);
|
|
if (!obj || !JS_SetPrivate(cx, obj, qn)) {
|
|
cx->weakRoots.newborn[GCX_OBJECT] = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
qn->object = obj;
|
|
METER(xml_stats.qnameobj);
|
|
METER(xml_stats.liveqnameobj);
|
|
return obj;
|
|
}
|
|
|
|
JSObject *
|
|
js_ConstructXMLQNameObject(JSContext *cx, jsval nsval, jsval lnval)
|
|
{
|
|
jsval argv[2];
|
|
|
|
/*
|
|
* ECMA-357 11.1.2,
|
|
* The _QualifiedIdentifier : PropertySelector :: PropertySelector_
|
|
* production, step 2.
|
|
*/
|
|
if (!JSVAL_IS_PRIMITIVE(nsval) &&
|
|
OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nsval)) == &js_AnyNameClass) {
|
|
nsval = JSVAL_NULL;
|
|
}
|
|
|
|
argv[0] = nsval;
|
|
argv[1] = lnval;
|
|
return js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, argv);
|
|
}
|
|
|
|
static JSBool
|
|
IsXMLName(const jschar *cp, size_t n)
|
|
{
|
|
JSBool rv;
|
|
jschar c;
|
|
|
|
rv = JS_FALSE;
|
|
if (n != 0 && JS_ISXMLNSSTART(*cp)) {
|
|
while (--n != 0) {
|
|
c = *++cp;
|
|
if (!JS_ISXMLNS(c))
|
|
return rv;
|
|
}
|
|
rv = JS_TRUE;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
JSBool
|
|
js_IsXMLName(JSContext *cx, jsval v)
|
|
{
|
|
JSClass *clasp;
|
|
JSXMLQName *qn;
|
|
JSString *name;
|
|
JSErrorReporter older;
|
|
|
|
/*
|
|
* Inline specialization of the QName constructor called with v passed as
|
|
* the only argument, to compute the localName for the constructed qname,
|
|
* without actually allocating the object or computing its uri and prefix.
|
|
* See ECMA-357 13.1.2.1 step 1 and 13.3.2.
|
|
*/
|
|
if (!JSVAL_IS_PRIMITIVE(v) &&
|
|
(clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)),
|
|
clasp == &js_QNameClass.base ||
|
|
clasp == &js_AttributeNameClass ||
|
|
clasp == &js_AnyNameClass)) {
|
|
qn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
|
|
name = qn->localName;
|
|
} else {
|
|
older = JS_SetErrorReporter(cx, NULL);
|
|
name = js_ValueToString(cx, v);
|
|
JS_SetErrorReporter(cx, older);
|
|
if (!name) {
|
|
JS_ClearPendingException(cx);
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
|
|
return IsXMLName(JSSTRING_CHARS(name), JSSTRING_LENGTH(name));
|
|
}
|
|
|
|
static JSBool
|
|
NamespaceHelper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
jsval urival, prefixval;
|
|
JSObject *uriobj;
|
|
JSBool isNamespace, isQName;
|
|
JSClass *clasp;
|
|
JSString *empty, *prefix;
|
|
JSXMLNamespace *ns, *ns2;
|
|
JSXMLQName *qn;
|
|
|
|
urival = argv[argc > 1];
|
|
isNamespace = isQName = JS_FALSE;
|
|
if (!JSVAL_IS_PRIMITIVE(urival)) {
|
|
uriobj = JSVAL_TO_OBJECT(urival);
|
|
clasp = OBJ_GET_CLASS(cx, uriobj);
|
|
isNamespace = (clasp == &js_NamespaceClass.base);
|
|
isQName = (clasp == &js_QNameClass.base);
|
|
}
|
|
#ifdef __GNUC__ /* suppress bogus gcc warnings */
|
|
else uriobj = NULL;
|
|
#endif
|
|
|
|
if (!obj) {
|
|
/* Namespace called as function. */
|
|
if (argc == 1 && isNamespace) {
|
|
/* Namespace called with one Namespace argument is identity. */
|
|
*rval = urival;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* Create and return a new QName object exactly as if constructed. */
|
|
obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
|
|
if (!obj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
}
|
|
METER(xml_stats.namespaceobj);
|
|
METER(xml_stats.livenamespaceobj);
|
|
|
|
/*
|
|
* Create and connect private data to rooted obj early, so we don't have
|
|
* to worry about rooting string newborns hanging off of the private data
|
|
* further below.
|
|
*/
|
|
empty = cx->runtime->emptyString;
|
|
ns = js_NewXMLNamespace(cx, empty, empty, JS_FALSE);
|
|
if (!ns)
|
|
return JS_FALSE;
|
|
if (!JS_SetPrivate(cx, obj, ns))
|
|
return JS_FALSE;
|
|
ns->object = obj;
|
|
|
|
if (argc == 1) {
|
|
if (isNamespace) {
|
|
ns2 = (JSXMLNamespace *) JS_GetPrivate(cx, uriobj);
|
|
ns->uri = ns2->uri;
|
|
ns->prefix = ns2->prefix;
|
|
} else if (isQName &&
|
|
(qn = (JSXMLQName *) JS_GetPrivate(cx, uriobj))->uri) {
|
|
ns->uri = qn->uri;
|
|
ns->prefix = qn->prefix;
|
|
} else {
|
|
ns->uri = js_ValueToString(cx, urival);
|
|
if (!ns->uri)
|
|
return JS_FALSE;
|
|
|
|
/* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
|
|
if (!IS_EMPTY(ns->uri))
|
|
ns->prefix = NULL;
|
|
}
|
|
} else if (argc == 2) {
|
|
if (isQName &&
|
|
(qn = (JSXMLQName *) JS_GetPrivate(cx, uriobj))->uri) {
|
|
ns->uri = qn->uri;
|
|
} else {
|
|
ns->uri = js_ValueToString(cx, urival);
|
|
if (!ns->uri)
|
|
return JS_FALSE;
|
|
}
|
|
|
|
prefixval = argv[0];
|
|
if (IS_EMPTY(ns->uri)) {
|
|
if (!JSVAL_IS_VOID(prefixval)) {
|
|
prefix = js_ValueToString(cx, prefixval);
|
|
if (!prefix)
|
|
return JS_FALSE;
|
|
if (!IS_EMPTY(prefix)) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XML_NAMESPACE,
|
|
js_ValueToPrintableString(cx,
|
|
STRING_TO_JSVAL(prefix)));
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
} else if (JSVAL_IS_VOID(prefixval) || !js_IsXMLName(cx, prefixval)) {
|
|
/* NULL here represents *undefined* in ECMA-357 13.2.2 4(d) etc. */
|
|
ns->prefix = NULL;
|
|
} else {
|
|
prefix = js_ValueToString(cx, prefixval);
|
|
if (!prefix)
|
|
return JS_FALSE;
|
|
ns->prefix = prefix;
|
|
}
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
Namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
return NamespaceHelper(cx,
|
|
(cx->fp->flags & JSFRAME_CONSTRUCTING) ? obj : NULL,
|
|
argc, argv, rval);
|
|
}
|
|
|
|
static JSBool
|
|
QNameHelper(JSContext *cx, JSObject *obj, JSClass *clasp, uintN argc,
|
|
jsval *argv, jsval *rval)
|
|
{
|
|
jsval nameval, nsval;
|
|
JSBool isQName, isNamespace;
|
|
JSXMLQName *qn;
|
|
JSString *uri, *prefix, *name;
|
|
JSObject *nsobj;
|
|
JSXMLNamespace *ns;
|
|
|
|
JS_ASSERT(clasp == &js_QNameClass.base ||
|
|
clasp == &js_AttributeNameClass);
|
|
nameval = argv[argc > 1];
|
|
isQName =
|
|
!JSVAL_IS_PRIMITIVE(nameval) &&
|
|
OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nameval)) == &js_QNameClass.base;
|
|
|
|
if (!obj) {
|
|
/* QName called as function. */
|
|
if (argc == 1 && isQName) {
|
|
/* QName called with one QName argument is identity. */
|
|
*rval = nameval;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Create and return a new QName or AttributeName object exactly as if
|
|
* constructed.
|
|
*/
|
|
obj = js_NewObject(cx, clasp, NULL, NULL);
|
|
if (!obj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
}
|
|
METER(xml_stats.qnameobj);
|
|
METER(xml_stats.liveqnameobj);
|
|
|
|
if (isQName) {
|
|
/* If namespace is not specified and name is a QName, clone it. */
|
|
qn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nameval));
|
|
if (argc == 1) {
|
|
uri = qn->uri;
|
|
prefix = qn->prefix;
|
|
name = qn->localName;
|
|
goto out;
|
|
}
|
|
|
|
/* Namespace and qname were passed -- use the qname's localName. */
|
|
nameval = STRING_TO_JSVAL(qn->localName);
|
|
}
|
|
|
|
if (argc == 0) {
|
|
name = cx->runtime->emptyString;
|
|
} else {
|
|
name = js_ValueToString(cx, nameval);
|
|
if (!name)
|
|
return JS_FALSE;
|
|
|
|
/* Use argv[1] as a local root for name, even if it was not passed. */
|
|
argv[1] = STRING_TO_JSVAL(name);
|
|
}
|
|
|
|
nsval = argv[0];
|
|
if (argc == 1 || JSVAL_IS_VOID(nsval)) {
|
|
if (IS_STAR(name)) {
|
|
nsval = JSVAL_NULL;
|
|
} else {
|
|
if (!js_GetDefaultXMLNamespace(cx, &nsval))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
|
|
if (JSVAL_IS_NULL(nsval)) {
|
|
/* NULL prefix represents *undefined* in ECMA-357 13.3.2 5(a). */
|
|
uri = prefix = NULL;
|
|
} else {
|
|
/*
|
|
* Inline specialization of the Namespace constructor called with
|
|
* nsval passed as the only argument, to compute the uri and prefix
|
|
* for the constructed namespace, without actually allocating the
|
|
* object or computing other members. See ECMA-357 13.3.2 6(a) and
|
|
* 13.2.2.
|
|
*/
|
|
isNamespace = isQName = JS_FALSE;
|
|
if (!JSVAL_IS_PRIMITIVE(nsval)) {
|
|
nsobj = JSVAL_TO_OBJECT(nsval);
|
|
clasp = OBJ_GET_CLASS(cx, nsobj);
|
|
isNamespace = (clasp == &js_NamespaceClass.base);
|
|
isQName = (clasp == &js_QNameClass.base);
|
|
}
|
|
#ifdef __GNUC__ /* suppress bogus gcc warnings */
|
|
else nsobj = NULL;
|
|
#endif
|
|
|
|
if (isNamespace) {
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
|
|
uri = ns->uri;
|
|
prefix = ns->prefix;
|
|
} else if (isQName &&
|
|
(qn = (JSXMLQName *) JS_GetPrivate(cx, nsobj))->uri) {
|
|
uri = qn->uri;
|
|
prefix = qn->prefix;
|
|
} else {
|
|
uri = js_ValueToString(cx, nsval);
|
|
if (!uri)
|
|
return JS_FALSE;
|
|
argv[0] = STRING_TO_JSVAL(uri); /* local root */
|
|
|
|
/* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
|
|
prefix = IS_EMPTY(uri) ? cx->runtime->emptyString : NULL;
|
|
}
|
|
}
|
|
|
|
out:
|
|
qn = js_NewXMLQName(cx, uri, prefix, name);
|
|
if (!qn)
|
|
return JS_FALSE;
|
|
if (!JS_SetPrivate(cx, obj, qn))
|
|
return JS_FALSE;
|
|
qn->object = obj;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
QName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
return QNameHelper(cx, (cx->fp->flags & JSFRAME_CONSTRUCTING) ? obj : NULL,
|
|
&js_QNameClass.base, argc, argv, rval);
|
|
}
|
|
|
|
static JSBool
|
|
AttributeName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
return QNameHelper(cx, (cx->fp->flags & JSFRAME_CONSTRUCTING) ? obj : NULL,
|
|
&js_AttributeNameClass, argc, argv, rval);
|
|
}
|
|
|
|
/*
|
|
* XMLArray library functions.
|
|
*/
|
|
static JSBool
|
|
namespace_identity(const void *a, const void *b)
|
|
{
|
|
const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
|
|
const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
|
|
|
|
if (nsa->prefix && nsb->prefix) {
|
|
if (!js_EqualStrings(nsa->prefix, nsb->prefix))
|
|
return JS_FALSE;
|
|
} else {
|
|
if (nsa->prefix || nsb->prefix)
|
|
return JS_FALSE;
|
|
}
|
|
return js_EqualStrings(nsa->uri, nsb->uri);
|
|
}
|
|
|
|
static JSBool
|
|
attr_identity(const void *a, const void *b)
|
|
{
|
|
const JSXML *xmla = (const JSXML *) a;
|
|
const JSXML *xmlb = (const JSXML *) b;
|
|
|
|
return qname_identity(xmla->name, xmlb->name);
|
|
}
|
|
|
|
static void
|
|
XMLArrayCursorInit(JSXMLArrayCursor *cursor, JSXMLArray *array)
|
|
{
|
|
JSXMLArrayCursor *next;
|
|
|
|
cursor->array = array;
|
|
cursor->index = 0;
|
|
next = cursor->next = array->cursors;
|
|
if (next)
|
|
next->prevp = &cursor->next;
|
|
cursor->prevp = &array->cursors;
|
|
array->cursors = cursor;
|
|
cursor->root = NULL;
|
|
}
|
|
|
|
static void
|
|
XMLArrayCursorFinish(JSXMLArrayCursor *cursor)
|
|
{
|
|
JSXMLArrayCursor *next;
|
|
|
|
if (!cursor->array)
|
|
return;
|
|
next = cursor->next;
|
|
if (next)
|
|
next->prevp = cursor->prevp;
|
|
*cursor->prevp = next;
|
|
cursor->array = NULL;
|
|
}
|
|
|
|
static void *
|
|
XMLArrayCursorNext(JSXMLArrayCursor *cursor)
|
|
{
|
|
JSXMLArray *array;
|
|
|
|
array = cursor->array;
|
|
if (!array || cursor->index >= array->length)
|
|
return NULL;
|
|
return cursor->root = array->vector[cursor->index++];
|
|
}
|
|
|
|
static void *
|
|
XMLArrayCursorItem(JSXMLArrayCursor *cursor)
|
|
{
|
|
JSXMLArray *array;
|
|
|
|
array = cursor->array;
|
|
if (!array || cursor->index >= array->length)
|
|
return NULL;
|
|
return cursor->root = array->vector[cursor->index];
|
|
}
|
|
|
|
static void
|
|
XMLArrayCursorMark(JSContext *cx, JSXMLArrayCursor *cursor)
|
|
{
|
|
while (cursor) {
|
|
GC_MARK(cx, cursor->root, "cursor->root");
|
|
cursor = cursor->next;
|
|
}
|
|
}
|
|
|
|
/* NB: called with null cx from the GC, via xml_mark => XMLArrayTrim. */
|
|
static JSBool
|
|
XMLArraySetCapacity(JSContext *cx, JSXMLArray *array, uint32 capacity)
|
|
{
|
|
void **vector;
|
|
|
|
if (capacity == 0) {
|
|
/* We could let realloc(p, 0) free this, but purify gets confused. */
|
|
if (array->vector)
|
|
free(array->vector);
|
|
vector = NULL;
|
|
} else {
|
|
if ((size_t)capacity > ~(size_t)0 / sizeof(void *) ||
|
|
!(vector = (void **)
|
|
realloc(array->vector, capacity * sizeof(void *)))) {
|
|
if (cx)
|
|
JS_ReportOutOfMemory(cx);
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
array->capacity = JSXML_PRESET_CAPACITY | capacity;
|
|
array->vector = vector;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static void
|
|
XMLArrayTrim(JSXMLArray *array)
|
|
{
|
|
if (array->capacity & JSXML_PRESET_CAPACITY)
|
|
return;
|
|
if (array->length < array->capacity)
|
|
XMLArraySetCapacity(NULL, array, array->length);
|
|
}
|
|
|
|
static JSBool
|
|
XMLArrayInit(JSContext *cx, JSXMLArray *array, uint32 capacity)
|
|
{
|
|
array->length = array->capacity = 0;
|
|
array->vector = NULL;
|
|
array->cursors = NULL;
|
|
return capacity == 0 || XMLArraySetCapacity(cx, array, capacity);
|
|
}
|
|
|
|
static void
|
|
XMLArrayFinish(JSContext *cx, JSXMLArray *array)
|
|
{
|
|
JSXMLArrayCursor *cursor;
|
|
|
|
JS_free(cx, array->vector);
|
|
|
|
while ((cursor = array->cursors) != NULL)
|
|
XMLArrayCursorFinish(cursor);
|
|
|
|
#ifdef DEBUG
|
|
memset(array, 0xd5, sizeof *array);
|
|
#endif
|
|
}
|
|
|
|
#define XML_NOT_FOUND ((uint32) -1)
|
|
|
|
static uint32
|
|
XMLArrayFindMember(const JSXMLArray *array, void *elt, JSIdentityOp identity)
|
|
{
|
|
void **vector;
|
|
uint32 i, n;
|
|
|
|
/* The identity op must not reallocate array->vector. */
|
|
vector = array->vector;
|
|
if (identity) {
|
|
for (i = 0, n = array->length; i < n; i++) {
|
|
if (identity(vector[i], elt))
|
|
return i;
|
|
}
|
|
} else {
|
|
for (i = 0, n = array->length; i < n; i++) {
|
|
if (vector[i] == elt)
|
|
return i;
|
|
}
|
|
}
|
|
return XML_NOT_FOUND;
|
|
}
|
|
|
|
/*
|
|
* Grow array vector capacity by powers of two to LINEAR_THRESHOLD, and after
|
|
* that, grow by LINEAR_INCREMENT. Both must be powers of two, and threshold
|
|
* should be greater than increment.
|
|
*/
|
|
#define LINEAR_THRESHOLD 256
|
|
#define LINEAR_INCREMENT 32
|
|
|
|
static JSBool
|
|
XMLArrayAddMember(JSContext *cx, JSXMLArray *array, uint32 index, void *elt)
|
|
{
|
|
uint32 capacity, i;
|
|
int log2;
|
|
void **vector;
|
|
|
|
if (index >= array->length) {
|
|
if (index >= JSXML_CAPACITY(array)) {
|
|
/* Arrange to clear JSXML_PRESET_CAPACITY from array->capacity. */
|
|
capacity = index + 1;
|
|
if (index >= LINEAR_THRESHOLD) {
|
|
capacity = JS_ROUNDUP(capacity, LINEAR_INCREMENT);
|
|
} else {
|
|
JS_CEILING_LOG2(log2, capacity);
|
|
capacity = JS_BIT(log2);
|
|
}
|
|
if ((size_t)capacity > ~(size_t)0 / sizeof(void *) ||
|
|
!(vector = (void **)
|
|
realloc(array->vector, capacity * sizeof(void *)))) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return JS_FALSE;
|
|
}
|
|
array->capacity = capacity;
|
|
array->vector = vector;
|
|
for (i = array->length; i < index; i++)
|
|
vector[i] = NULL;
|
|
}
|
|
array->length = index + 1;
|
|
}
|
|
|
|
array->vector[index] = elt;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
XMLArrayInsert(JSContext *cx, JSXMLArray *array, uint32 i, uint32 n)
|
|
{
|
|
uint32 j;
|
|
JSXMLArrayCursor *cursor;
|
|
|
|
j = array->length;
|
|
JS_ASSERT(i <= j);
|
|
if (!XMLArraySetCapacity(cx, array, j + n))
|
|
return JS_FALSE;
|
|
|
|
array->length = j + n;
|
|
JS_ASSERT(n != (uint32)-1);
|
|
while (j != i) {
|
|
--j;
|
|
array->vector[j + n] = array->vector[j];
|
|
}
|
|
|
|
for (cursor = array->cursors; cursor; cursor = cursor->next) {
|
|
if (cursor->index > i)
|
|
cursor->index += n;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static void *
|
|
XMLArrayDelete(JSContext *cx, JSXMLArray *array, uint32 index, JSBool compress)
|
|
{
|
|
uint32 length;
|
|
void **vector, *elt;
|
|
JSXMLArrayCursor *cursor;
|
|
|
|
length = array->length;
|
|
if (index >= length)
|
|
return NULL;
|
|
|
|
vector = array->vector;
|
|
elt = vector[index];
|
|
if (compress) {
|
|
while (++index < length)
|
|
vector[index-1] = vector[index];
|
|
array->length = length - 1;
|
|
array->capacity = JSXML_CAPACITY(array);
|
|
} else {
|
|
vector[index] = NULL;
|
|
}
|
|
|
|
for (cursor = array->cursors; cursor; cursor = cursor->next) {
|
|
if (cursor->index > index)
|
|
--cursor->index;
|
|
}
|
|
return elt;
|
|
}
|
|
|
|
static void
|
|
XMLArrayTruncate(JSContext *cx, JSXMLArray *array, uint32 length)
|
|
{
|
|
void **vector;
|
|
|
|
JS_ASSERT(!array->cursors);
|
|
if (length >= array->length)
|
|
return;
|
|
|
|
if (length == 0) {
|
|
if (array->vector)
|
|
free(array->vector);
|
|
vector = NULL;
|
|
} else {
|
|
vector = realloc(array->vector, length * sizeof(void *));
|
|
if (!vector)
|
|
return;
|
|
}
|
|
|
|
if (array->length > length)
|
|
array->length = length;
|
|
array->capacity = length;
|
|
array->vector = vector;
|
|
}
|
|
|
|
#define XMLARRAY_FIND_MEMBER(a,e,f) XMLArrayFindMember(a, (void *)(e), f)
|
|
#define XMLARRAY_HAS_MEMBER(a,e,f) (XMLArrayFindMember(a, (void *)(e), f) != \
|
|
XML_NOT_FOUND)
|
|
#define XMLARRAY_MEMBER(a,i,t) (((i) < (a)->length) \
|
|
? (t *) (a)->vector[i] \
|
|
: NULL)
|
|
#define XMLARRAY_SET_MEMBER(a,i,e) JS_BEGIN_MACRO \
|
|
if ((a)->length <= (i)) \
|
|
(a)->length = (i) + 1; \
|
|
((a)->vector[i] = (void *)(e)); \
|
|
JS_END_MACRO
|
|
#define XMLARRAY_ADD_MEMBER(x,a,i,e)XMLArrayAddMember(x, a, i, (void *)(e))
|
|
#define XMLARRAY_INSERT(x,a,i,n) XMLArrayInsert(x, a, i, n)
|
|
#define XMLARRAY_APPEND(x,a,e) XMLARRAY_ADD_MEMBER(x, a, (a)->length, (e))
|
|
#define XMLARRAY_DELETE(x,a,i,c,t) ((t *) XMLArrayDelete(x, a, i, c))
|
|
#define XMLARRAY_TRUNCATE(x,a,n) XMLArrayTruncate(x, a, n)
|
|
|
|
/*
|
|
* Define XML setting property strings and constants early, so everyone can
|
|
* use the same names and their magic numbers (tinyids, flags).
|
|
*/
|
|
static const char js_ignoreComments_str[] = "ignoreComments";
|
|
static const char js_ignoreProcessingInstructions_str[]
|
|
= "ignoreProcessingInstructions";
|
|
static const char js_ignoreWhitespace_str[] = "ignoreWhitespace";
|
|
static const char js_prettyPrinting_str[] = "prettyPrinting";
|
|
static const char js_prettyIndent_str[] = "prettyIndent";
|
|
|
|
/*
|
|
* NB: These XML static property tinyids must
|
|
* (a) not collide with the generic negative tinyids at the top of jsfun.c;
|
|
* (b) index their corresponding xml_static_props array elements.
|
|
* Don't change 'em!
|
|
*/
|
|
enum xml_static_tinyid {
|
|
XML_IGNORE_COMMENTS,
|
|
XML_IGNORE_PROCESSING_INSTRUCTIONS,
|
|
XML_IGNORE_WHITESPACE,
|
|
XML_PRETTY_PRINTING,
|
|
XML_PRETTY_INDENT
|
|
};
|
|
|
|
static JSBool
|
|
xml_setting_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_setting_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSBool b;
|
|
uint8 flag;
|
|
|
|
JS_ASSERT(JSVAL_IS_INT(id));
|
|
if (!js_ValueToBoolean(cx, *vp, &b))
|
|
return JS_FALSE;
|
|
|
|
flag = JS_BIT(JSVAL_TO_INT(id));
|
|
if (b)
|
|
cx->xmlSettingFlags |= flag;
|
|
else
|
|
cx->xmlSettingFlags &= ~flag;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSPropertySpec xml_static_props[] = {
|
|
{js_ignoreComments_str, XML_IGNORE_COMMENTS, JSPROP_PERMANENT,
|
|
xml_setting_getter, xml_setting_setter},
|
|
{js_ignoreProcessingInstructions_str,
|
|
XML_IGNORE_PROCESSING_INSTRUCTIONS, JSPROP_PERMANENT,
|
|
xml_setting_getter, xml_setting_setter},
|
|
{js_ignoreWhitespace_str, XML_IGNORE_WHITESPACE, JSPROP_PERMANENT,
|
|
xml_setting_getter, xml_setting_setter},
|
|
{js_prettyPrinting_str, XML_PRETTY_PRINTING, JSPROP_PERMANENT,
|
|
xml_setting_getter, xml_setting_setter},
|
|
{js_prettyIndent_str, XML_PRETTY_INDENT, JSPROP_PERMANENT,
|
|
xml_setting_getter, NULL},
|
|
{0,0,0,0,0}
|
|
};
|
|
|
|
/* Derive cx->xmlSettingFlags bits from xml_static_props tinyids. */
|
|
#define XSF_IGNORE_COMMENTS JS_BIT(XML_IGNORE_COMMENTS)
|
|
#define XSF_IGNORE_PROCESSING_INSTRUCTIONS \
|
|
JS_BIT(XML_IGNORE_PROCESSING_INSTRUCTIONS)
|
|
#define XSF_IGNORE_WHITESPACE JS_BIT(XML_IGNORE_WHITESPACE)
|
|
#define XSF_PRETTY_PRINTING JS_BIT(XML_PRETTY_PRINTING)
|
|
#define XSF_CACHE_VALID JS_BIT(XML_PRETTY_INDENT)
|
|
|
|
/*
|
|
* Extra, unrelated but necessarily disjoint flag used by ParseNodeToXML.
|
|
* This flag means a couple of things:
|
|
*
|
|
* - The top JSXML created for a parse tree must have an object owning it.
|
|
*
|
|
* - That the default namespace normally inherited from the temporary
|
|
* <parent xmlns='...'> tag that wraps a runtime-concatenated XML source
|
|
* string must, in the case of a precompiled XML object tree, inherit via
|
|
* ad-hoc code in ParseNodeToXML.
|
|
*
|
|
* Because of the second purpose, we name this flag XSF_PRECOMPILED_ROOT.
|
|
*/
|
|
#define XSF_PRECOMPILED_ROOT (XSF_CACHE_VALID << 1)
|
|
|
|
/* Macros for special-casing xml:, xmlns= and xmlns:foo= in ParseNodeToQName. */
|
|
#define IS_XML(str) \
|
|
(JSSTRING_LENGTH(str) == 3 && IS_XML_CHARS(JSSTRING_CHARS(str)))
|
|
|
|
#define IS_XMLNS(str) \
|
|
(JSSTRING_LENGTH(str) == 5 && IS_XMLNS_CHARS(JSSTRING_CHARS(str)))
|
|
|
|
#define IS_XML_CHARS(chars) \
|
|
(JS_TOLOWER((chars)[0]) == 'x' && \
|
|
JS_TOLOWER((chars)[1]) == 'm' && \
|
|
JS_TOLOWER((chars)[2]) == 'l')
|
|
|
|
#define HAS_NS_AFTER_XML(chars) \
|
|
(JS_TOLOWER((chars)[3]) == 'n' && \
|
|
JS_TOLOWER((chars)[4]) == 's')
|
|
|
|
#define IS_XMLNS_CHARS(chars) \
|
|
(IS_XML_CHARS(chars) && HAS_NS_AFTER_XML(chars))
|
|
|
|
#define STARTS_WITH_XML(chars,length) \
|
|
(length >= 3 && IS_XML_CHARS(chars))
|
|
|
|
static const char xml_namespace_str[] = "http://www.w3.org/XML/1998/namespace";
|
|
static const char xmlns_namespace_str[] = "http://www.w3.org/2000/xmlns/";
|
|
|
|
static JSXMLQName *
|
|
ParseNodeToQName(JSContext *cx, JSParseNode *pn, JSXMLArray *inScopeNSes,
|
|
JSBool isAttributeName)
|
|
{
|
|
JSString *str, *uri, *prefix, *localName;
|
|
size_t length, offset;
|
|
const jschar *start, *limit, *colon;
|
|
uint32 n;
|
|
JSXMLNamespace *ns;
|
|
|
|
JS_ASSERT(pn->pn_arity == PN_NULLARY);
|
|
str = ATOM_TO_STRING(pn->pn_atom);
|
|
length = JSSTRING_LENGTH(str);
|
|
start = JSSTRING_CHARS(str);
|
|
JS_ASSERT(length != 0 && *start != '@');
|
|
JS_ASSERT(length != 1 || *start != '*');
|
|
|
|
uri = cx->runtime->emptyString;
|
|
limit = start + length;
|
|
colon = js_strchr_limit(start, ':', limit);
|
|
if (colon) {
|
|
offset = PTRDIFF(colon, start, jschar);
|
|
prefix = js_NewDependentString(cx, str, 0, offset, 0);
|
|
if (!prefix)
|
|
return NULL;
|
|
|
|
if (STARTS_WITH_XML(start, offset)) {
|
|
if (offset == 3) {
|
|
uri = JS_InternString(cx, xml_namespace_str);
|
|
if (!uri)
|
|
return NULL;
|
|
} else if (offset == 5 && HAS_NS_AFTER_XML(start)) {
|
|
uri = JS_InternString(cx, xmlns_namespace_str);
|
|
if (!uri)
|
|
return NULL;
|
|
} else {
|
|
uri = NULL;
|
|
}
|
|
} else {
|
|
uri = NULL;
|
|
n = inScopeNSes->length;
|
|
while (n != 0) {
|
|
--n;
|
|
ns = XMLARRAY_MEMBER(inScopeNSes, n, JSXMLNamespace);
|
|
if (ns->prefix && js_EqualStrings(ns->prefix, prefix)) {
|
|
uri = ns->uri;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!uri) {
|
|
js_ReportCompileErrorNumber(cx, pn,
|
|
JSREPORT_PN | JSREPORT_ERROR,
|
|
JSMSG_BAD_XML_NAMESPACE,
|
|
js_ValueToPrintableString(cx,
|
|
STRING_TO_JSVAL(prefix)));
|
|
return NULL;
|
|
}
|
|
|
|
localName = js_NewStringCopyN(cx, colon + 1, length - (offset + 1), 0);
|
|
if (!localName)
|
|
return NULL;
|
|
} else {
|
|
if (isAttributeName) {
|
|
/*
|
|
* An unprefixed attribute is not in any namespace, so set prefix
|
|
* as well as uri to the empty string.
|
|
*/
|
|
prefix = uri;
|
|
} else {
|
|
/*
|
|
* Loop from back to front looking for the closest declared default
|
|
* namespace.
|
|
*/
|
|
n = inScopeNSes->length;
|
|
while (n != 0) {
|
|
--n;
|
|
ns = XMLARRAY_MEMBER(inScopeNSes, n, JSXMLNamespace);
|
|
if (!ns->prefix || IS_EMPTY(ns->prefix)) {
|
|
uri = ns->uri;
|
|
break;
|
|
}
|
|
}
|
|
prefix = IS_EMPTY(uri) ? cx->runtime->emptyString : NULL;
|
|
}
|
|
localName = str;
|
|
}
|
|
|
|
return js_NewXMLQName(cx, uri, prefix, localName);
|
|
}
|
|
|
|
static JSString *
|
|
ChompXMLWhitespace(JSContext *cx, JSString *str)
|
|
{
|
|
size_t length, newlength, offset;
|
|
const jschar *cp, *start, *end;
|
|
jschar c;
|
|
|
|
length = JSSTRING_LENGTH(str);
|
|
for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
|
|
c = *cp;
|
|
if (!JS_ISXMLSPACE(c))
|
|
break;
|
|
}
|
|
while (end > cp) {
|
|
c = end[-1];
|
|
if (!JS_ISXMLSPACE(c))
|
|
break;
|
|
--end;
|
|
}
|
|
newlength = PTRDIFF(end, cp, jschar);
|
|
if (newlength == length)
|
|
return str;
|
|
offset = PTRDIFF(cp, start, jschar);
|
|
return js_NewDependentString(cx, str, offset, newlength, 0);
|
|
}
|
|
|
|
static JSXML *
|
|
ParseNodeToXML(JSContext *cx, JSParseNode *pn, JSXMLArray *inScopeNSes,
|
|
uintN flags)
|
|
{
|
|
JSXML *xml, *kid, *attr, *attrj;
|
|
JSString *str;
|
|
uint32 length, n, i, j;
|
|
JSParseNode *pn2, *pn3, *head, **pnp;
|
|
JSXMLNamespace *ns;
|
|
JSXMLQName *qn, *attrjqn;
|
|
JSXMLClass xml_class;
|
|
int stackDummy;
|
|
|
|
if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
|
|
js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR,
|
|
JSMSG_OVER_RECURSED);
|
|
return NULL;
|
|
}
|
|
|
|
#define PN2X_SKIP_CHILD ((JSXML *) 1)
|
|
|
|
/*
|
|
* Cases return early to avoid common code that gets an outermost xml's
|
|
* object, which protects GC-things owned by xml and its descendants from
|
|
* garbage collection.
|
|
*/
|
|
xml = NULL;
|
|
if (!js_EnterLocalRootScope(cx))
|
|
return NULL;
|
|
switch (pn->pn_type) {
|
|
case TOK_XMLELEM:
|
|
length = inScopeNSes->length;
|
|
pn2 = pn->pn_head;
|
|
xml = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
|
|
if (!xml)
|
|
goto fail;
|
|
|
|
flags &= ~XSF_PRECOMPILED_ROOT;
|
|
n = pn->pn_count;
|
|
JS_ASSERT(n >= 2);
|
|
n -= 2;
|
|
if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
|
|
goto fail;
|
|
|
|
i = 0;
|
|
while ((pn2 = pn2->pn_next) != NULL) {
|
|
if (!pn2->pn_next) {
|
|
/* Don't append the end tag! */
|
|
JS_ASSERT(pn2->pn_type == TOK_XMLETAGO);
|
|
break;
|
|
}
|
|
|
|
if ((flags & XSF_IGNORE_WHITESPACE) &&
|
|
n > 1 && pn2->pn_type == TOK_XMLSPACE) {
|
|
--n;
|
|
continue;
|
|
}
|
|
|
|
kid = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
|
|
if (kid == PN2X_SKIP_CHILD) {
|
|
--n;
|
|
continue;
|
|
}
|
|
|
|
if (!kid)
|
|
goto fail;
|
|
|
|
/* Store kid in xml right away, to protect it from GC. */
|
|
XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
|
|
kid->parent = xml;
|
|
++i;
|
|
|
|
/* XXX where is this documented in an XML spec, or in E4X? */
|
|
if ((flags & XSF_IGNORE_WHITESPACE) &&
|
|
n > 1 && kid->xml_class == JSXML_CLASS_TEXT) {
|
|
str = ChompXMLWhitespace(cx, kid->xml_value);
|
|
if (!str)
|
|
goto fail;
|
|
kid->xml_value = str;
|
|
}
|
|
}
|
|
|
|
JS_ASSERT(i == n);
|
|
if (n < pn->pn_count - 2)
|
|
XMLArrayTrim(&xml->xml_kids);
|
|
XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
|
|
break;
|
|
|
|
case TOK_XMLLIST:
|
|
xml = js_NewXML(cx, JSXML_CLASS_LIST);
|
|
if (!xml)
|
|
goto fail;
|
|
|
|
n = pn->pn_count;
|
|
if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
|
|
goto fail;
|
|
|
|
i = 0;
|
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
|
/*
|
|
* Always ignore insignificant whitespace in lists -- we shouldn't
|
|
* condition this on an XML.ignoreWhitespace setting when the list
|
|
* constructor is XMLList (note XML/XMLList unification hazard).
|
|
*/
|
|
if (pn2->pn_type == TOK_XMLSPACE) {
|
|
--n;
|
|
continue;
|
|
}
|
|
|
|
kid = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
|
|
if (kid == PN2X_SKIP_CHILD) {
|
|
--n;
|
|
continue;
|
|
}
|
|
|
|
if (!kid)
|
|
goto fail;
|
|
|
|
XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
|
|
++i;
|
|
}
|
|
|
|
if (n < pn->pn_count)
|
|
XMLArrayTrim(&xml->xml_kids);
|
|
break;
|
|
|
|
case TOK_XMLSTAGO:
|
|
case TOK_XMLPTAGC:
|
|
length = inScopeNSes->length;
|
|
pn2 = pn->pn_head;
|
|
JS_ASSERT(pn2->pn_type == TOK_XMLNAME);
|
|
if (pn2->pn_arity == PN_LIST)
|
|
goto syntax;
|
|
|
|
xml = js_NewXML(cx, JSXML_CLASS_ELEMENT);
|
|
if (!xml)
|
|
goto fail;
|
|
|
|
/* First pass: check syntax and process namespace declarations. */
|
|
JS_ASSERT(pn->pn_count >= 1);
|
|
n = pn->pn_count - 1;
|
|
pnp = &pn2->pn_next;
|
|
head = *pnp;
|
|
while ((pn2 = *pnp) != NULL) {
|
|
size_t length;
|
|
const jschar *chars;
|
|
|
|
if (pn2->pn_type != TOK_XMLNAME || pn2->pn_arity != PN_NULLARY)
|
|
goto syntax;
|
|
|
|
/* Enforce "Well-formedness constraint: Unique Att Spec". */
|
|
for (pn3 = head; pn3 != pn2; pn3 = pn3->pn_next->pn_next) {
|
|
if (pn3->pn_atom == pn2->pn_atom) {
|
|
js_ReportCompileErrorNumber(cx, pn2,
|
|
JSREPORT_PN | JSREPORT_ERROR,
|
|
JSMSG_DUPLICATE_XML_ATTR,
|
|
js_ValueToPrintableString(cx,
|
|
ATOM_KEY(pn2->pn_atom)));
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
str = ATOM_TO_STRING(pn2->pn_atom);
|
|
pn2 = pn2->pn_next;
|
|
JS_ASSERT(pn2);
|
|
if (pn2->pn_type != TOK_XMLATTR)
|
|
goto syntax;
|
|
|
|
length = JSSTRING_LENGTH(str);
|
|
chars = JSSTRING_CHARS(str);
|
|
if (length >= 5 &&
|
|
IS_XMLNS_CHARS(chars) &&
|
|
(length == 5 || chars[5] == ':')) {
|
|
JSString *uri, *prefix;
|
|
|
|
uri = ATOM_TO_STRING(pn2->pn_atom);
|
|
if (length == 5) {
|
|
/* 10.3.2.1. Step 6(h)(i)(1)(a). */
|
|
prefix = cx->runtime->emptyString;
|
|
} else {
|
|
prefix = js_NewStringCopyN(cx, chars + 6, length - 6, 0);
|
|
if (!prefix)
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Once the new ns is appended to xml->xml_namespaces, it is
|
|
* protected from GC by the object that owns xml -- which is
|
|
* either xml->object if outermost, or the object owning xml's
|
|
* oldest ancestor if !outermost.
|
|
*/
|
|
ns = js_NewXMLNamespace(cx, prefix, uri, JS_TRUE);
|
|
if (!ns)
|
|
goto fail;
|
|
|
|
/*
|
|
* Don't add a namespace that's already in scope. If someone
|
|
* extracts a child property from its parent via [[Get]], then
|
|
* we enforce the invariant, noted many times in ECMA-357, that
|
|
* the child's namespaces form a possibly-improper superset of
|
|
* its ancestors' namespaces.
|
|
*/
|
|
if (!XMLARRAY_HAS_MEMBER(inScopeNSes, ns, namespace_identity)) {
|
|
if (!XMLARRAY_APPEND(cx, inScopeNSes, ns) ||
|
|
!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns)) {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
JS_ASSERT(n >= 2);
|
|
n -= 2;
|
|
*pnp = pn2->pn_next;
|
|
/* XXXbe recycle pn2 */
|
|
continue;
|
|
}
|
|
|
|
pnp = &pn2->pn_next;
|
|
}
|
|
|
|
/*
|
|
* If called from js_ParseNodeToXMLObject, emulate the effect of the
|
|
* <parent xmlns='%s'>...</parent> wrapping done by "ToXML Applied to
|
|
* the String Type" (ECMA-357 10.3.1).
|
|
*/
|
|
if (flags & XSF_PRECOMPILED_ROOT) {
|
|
JS_ASSERT(length >= 1);
|
|
ns = XMLARRAY_MEMBER(inScopeNSes, 0, JSXMLNamespace);
|
|
JS_ASSERT(!XMLARRAY_HAS_MEMBER(&xml->xml_namespaces, ns,
|
|
namespace_identity));
|
|
ns = js_NewXMLNamespace(cx, ns->prefix, ns->uri, JS_FALSE);
|
|
if (!ns)
|
|
goto fail;
|
|
if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
|
|
goto fail;
|
|
}
|
|
XMLArrayTrim(&xml->xml_namespaces);
|
|
|
|
/* Second pass: process tag name and attributes, using namespaces. */
|
|
pn2 = pn->pn_head;
|
|
qn = ParseNodeToQName(cx, pn2, inScopeNSes, JS_FALSE);
|
|
if (!qn)
|
|
goto fail;
|
|
xml->name = qn;
|
|
|
|
JS_ASSERT((n & 1) == 0);
|
|
n >>= 1;
|
|
if (!XMLArraySetCapacity(cx, &xml->xml_attrs, n))
|
|
goto fail;
|
|
|
|
for (i = 0; (pn2 = pn2->pn_next) != NULL; i++) {
|
|
qn = ParseNodeToQName(cx, pn2, inScopeNSes, JS_TRUE);
|
|
if (!qn) {
|
|
xml->xml_attrs.length = i;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Enforce "Well-formedness constraint: Unique Att Spec", part 2:
|
|
* this time checking local name and namespace URI.
|
|
*/
|
|
for (j = 0; j < i; j++) {
|
|
attrj = XMLARRAY_MEMBER(&xml->xml_attrs, j, JSXML);
|
|
attrjqn = attrj->name;
|
|
if (js_EqualStrings(attrjqn->uri, qn->uri) &&
|
|
js_EqualStrings(attrjqn->localName, qn->localName)) {
|
|
js_ReportCompileErrorNumber(cx, pn2,
|
|
JSREPORT_PN | JSREPORT_ERROR,
|
|
JSMSG_DUPLICATE_XML_ATTR,
|
|
js_ValueToPrintableString(cx,
|
|
ATOM_KEY(pn2->pn_atom)));
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
pn2 = pn2->pn_next;
|
|
JS_ASSERT(pn2);
|
|
JS_ASSERT(pn2->pn_type == TOK_XMLATTR);
|
|
|
|
attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
|
|
if (!attr)
|
|
goto fail;
|
|
|
|
XMLARRAY_SET_MEMBER(&xml->xml_attrs, i, attr);
|
|
attr->parent = xml;
|
|
attr->name = qn;
|
|
attr->xml_value = ATOM_TO_STRING(pn2->pn_atom);
|
|
}
|
|
|
|
/* Point tag closes its own namespace scope. */
|
|
if (pn->pn_type == TOK_XMLPTAGC)
|
|
XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
|
|
break;
|
|
|
|
case TOK_XMLSPACE:
|
|
case TOK_XMLTEXT:
|
|
case TOK_XMLCDATA:
|
|
case TOK_XMLCOMMENT:
|
|
case TOK_XMLPI:
|
|
str = ATOM_TO_STRING(pn->pn_atom);
|
|
qn = NULL;
|
|
if (pn->pn_type == TOK_XMLCOMMENT) {
|
|
if (flags & XSF_IGNORE_COMMENTS)
|
|
goto skip_child;
|
|
xml_class = JSXML_CLASS_COMMENT;
|
|
} else if (pn->pn_type == TOK_XMLPI) {
|
|
if (IS_XML(str)) {
|
|
js_ReportCompileErrorNumber(cx, pn,
|
|
JSREPORT_PN | JSREPORT_ERROR,
|
|
JSMSG_RESERVED_ID,
|
|
js_ValueToPrintableString(cx,
|
|
STRING_TO_JSVAL(str)));
|
|
goto fail;
|
|
}
|
|
|
|
if (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS)
|
|
goto skip_child;
|
|
|
|
qn = ParseNodeToQName(cx, pn, inScopeNSes, JS_FALSE);
|
|
if (!qn)
|
|
goto fail;
|
|
|
|
str = pn->pn_atom2
|
|
? ATOM_TO_STRING(pn->pn_atom2)
|
|
: cx->runtime->emptyString;
|
|
xml_class = JSXML_CLASS_PROCESSING_INSTRUCTION;
|
|
} else {
|
|
/* CDATA section content, or element text. */
|
|
xml_class = JSXML_CLASS_TEXT;
|
|
}
|
|
|
|
xml = js_NewXML(cx, xml_class);
|
|
if (!xml)
|
|
goto fail;
|
|
xml->name = qn;
|
|
if (pn->pn_type == TOK_XMLSPACE)
|
|
xml->xml_flags |= XMLF_WHITESPACE_TEXT;
|
|
xml->xml_value = str;
|
|
break;
|
|
|
|
default:
|
|
goto syntax;
|
|
}
|
|
|
|
js_LeaveLocalRootScopeWithResult(cx, (jsval) xml);
|
|
if ((flags & XSF_PRECOMPILED_ROOT) && !js_GetXMLObject(cx, xml))
|
|
return NULL;
|
|
return xml;
|
|
|
|
skip_child:
|
|
js_LeaveLocalRootScope(cx);
|
|
return PN2X_SKIP_CHILD;
|
|
|
|
#undef PN2X_SKIP_CHILD
|
|
|
|
syntax:
|
|
js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR,
|
|
JSMSG_BAD_XML_MARKUP);
|
|
fail:
|
|
js_LeaveLocalRootScope(cx);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* XML helper, object-ops, and library functions. We start with the helpers,
|
|
* in ECMA-357 order, but merging XML (9.1) and XMLList (9.2) helpers.
|
|
*/
|
|
static JSBool
|
|
GetXMLSetting(JSContext *cx, const char *name, jsval *vp)
|
|
{
|
|
jsval v;
|
|
|
|
if (!js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_XML), &v))
|
|
return JS_FALSE;
|
|
if (!VALUE_IS_FUNCTION(cx, v)) {
|
|
*vp = JSVAL_VOID;
|
|
return JS_TRUE;
|
|
}
|
|
return JS_GetProperty(cx, JSVAL_TO_OBJECT(v), name, vp);
|
|
}
|
|
|
|
static JSBool
|
|
FillSettingsCache(JSContext *cx)
|
|
{
|
|
int i;
|
|
const char *name;
|
|
jsval v;
|
|
JSBool isSet;
|
|
|
|
/* Note: XML_PRETTY_INDENT is not a boolean setting. */
|
|
for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
|
|
name = xml_static_props[i].name;
|
|
if (!GetXMLSetting(cx, name, &v) || !js_ValueToBoolean(cx, v, &isSet))
|
|
return JS_FALSE;
|
|
if (isSet)
|
|
cx->xmlSettingFlags |= JS_BIT(i);
|
|
else
|
|
cx->xmlSettingFlags &= ~JS_BIT(i);
|
|
}
|
|
|
|
cx->xmlSettingFlags |= XSF_CACHE_VALID;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
GetBooleanXMLSetting(JSContext *cx, const char *name, JSBool *bp)
|
|
{
|
|
int i;
|
|
|
|
if (!(cx->xmlSettingFlags & XSF_CACHE_VALID) && !FillSettingsCache(cx))
|
|
return JS_FALSE;
|
|
|
|
for (i = 0; xml_static_props[i].name; i++) {
|
|
if (!strcmp(xml_static_props[i].name, name)) {
|
|
*bp = (cx->xmlSettingFlags & JS_BIT(i)) != 0;
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
*bp = JS_FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
GetUint32XMLSetting(JSContext *cx, const char *name, uint32 *uip)
|
|
{
|
|
jsval v;
|
|
|
|
return GetXMLSetting(cx, name, &v) && js_ValueToECMAUint32(cx, v, uip);
|
|
}
|
|
|
|
static JSBool
|
|
GetXMLSettingFlags(JSContext *cx, uintN *flagsp)
|
|
{
|
|
JSBool flag;
|
|
|
|
/* Just get the first flag to validate the setting flags cache. */
|
|
if (!GetBooleanXMLSetting(cx, js_ignoreComments_str, &flag))
|
|
return JS_FALSE;
|
|
*flagsp = cx->xmlSettingFlags;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSXML *
|
|
ParseXMLSource(JSContext *cx, JSString *src)
|
|
{
|
|
jsval nsval;
|
|
JSXMLNamespace *ns;
|
|
JSString *uri;
|
|
size_t urilen, srclen, length, offset, dstlen;
|
|
jschar *chars;
|
|
const jschar *srcp, *endp;
|
|
void *mark;
|
|
JSTokenStream *ts;
|
|
uintN lineno;
|
|
JSStackFrame *fp;
|
|
JSOp op;
|
|
JSParseNode *pn;
|
|
JSXML *xml;
|
|
JSXMLArray nsarray;
|
|
uintN flags;
|
|
|
|
static const char prefix[] = "<parent xmlns=\"";
|
|
static const char middle[] = "\">";
|
|
static const char suffix[] = "</parent>";
|
|
|
|
#define constrlen(constr) (sizeof(constr) - 1)
|
|
|
|
if (!js_GetDefaultXMLNamespace(cx, &nsval))
|
|
return NULL;
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
|
|
uri = js_EscapeAttributeValue(cx, ns->uri, JS_FALSE);
|
|
|
|
urilen = JSSTRING_LENGTH(uri);
|
|
srclen = JSSTRING_LENGTH(src);
|
|
length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
|
|
constrlen(suffix);
|
|
|
|
chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
|
|
if (!chars)
|
|
return NULL;
|
|
|
|
dstlen = length;
|
|
js_InflateStringToBuffer(cx, prefix, constrlen(prefix), chars, &dstlen);
|
|
offset = dstlen;
|
|
js_strncpy(chars + offset, JSSTRING_CHARS(uri), urilen);
|
|
offset += urilen;
|
|
dstlen = length - offset + 1;
|
|
js_InflateStringToBuffer(cx, middle, constrlen(middle), chars + offset,
|
|
&dstlen);
|
|
offset += dstlen;
|
|
srcp = JSSTRING_CHARS(src);
|
|
js_strncpy(chars + offset, srcp, srclen);
|
|
offset += srclen;
|
|
dstlen = length - offset + 1;
|
|
js_InflateStringToBuffer(cx, suffix, constrlen(suffix), chars + offset,
|
|
&dstlen);
|
|
chars [offset + dstlen] = 0;
|
|
|
|
mark = JS_ARENA_MARK(&cx->tempPool);
|
|
ts = js_NewBufferTokenStream(cx, chars, length);
|
|
if (!ts)
|
|
return NULL;
|
|
for (fp = cx->fp; fp && !fp->pc; fp = fp->down)
|
|
continue;
|
|
if (fp) {
|
|
op = (JSOp) *fp->pc;
|
|
if (op == JSOP_TOXML || op == JSOP_TOXMLLIST) {
|
|
ts->filename = fp->script->filename;
|
|
lineno = js_PCToLineNumber(cx, fp->script, fp->pc);
|
|
for (endp = srcp + srclen; srcp < endp; srcp++)
|
|
if (*srcp == '\n')
|
|
--lineno;
|
|
ts->lineno = lineno;
|
|
}
|
|
}
|
|
|
|
JS_KEEP_ATOMS(cx->runtime);
|
|
pn = js_ParseXMLTokenStream(cx, cx->fp->scopeChain, ts, JS_FALSE);
|
|
xml = NULL;
|
|
if (pn && XMLArrayInit(cx, &nsarray, 1)) {
|
|
if (GetXMLSettingFlags(cx, &flags))
|
|
xml = ParseNodeToXML(cx, pn, &nsarray, flags);
|
|
|
|
XMLArrayFinish(cx, &nsarray);
|
|
}
|
|
JS_UNKEEP_ATOMS(cx->runtime);
|
|
|
|
JS_ARENA_RELEASE(&cx->tempPool, mark);
|
|
JS_free(cx, chars);
|
|
return xml;
|
|
|
|
#undef constrlen
|
|
}
|
|
|
|
/*
|
|
* Errata in 10.3.1, 10.4.1, and 13.4.4.24 (at least).
|
|
*
|
|
* 10.3.1 Step 6(a) fails to NOTE that implementations that do not enforce
|
|
* the constraint:
|
|
*
|
|
* for all x belonging to XML:
|
|
* x.[[InScopeNamespaces]] >= x.[[Parent]].[[InScopeNamespaces]]
|
|
*
|
|
* must union x.[[InScopeNamespaces]] into x[0].[[InScopeNamespaces]] here
|
|
* (in new sub-step 6(a), renumbering the others to (b) and (c)).
|
|
*
|
|
* Same goes for 10.4.1 Step 7(a).
|
|
*
|
|
* In order for XML.prototype.namespaceDeclarations() to work correctly, the
|
|
* default namespace thereby unioned into x[0].[[InScopeNamespaces]] must be
|
|
* flagged as not declared, so that 13.4.4.24 Step 8(a) can exclude all such
|
|
* undeclared namespaces associated with x not belonging to ancestorNS.
|
|
*/
|
|
static JSXML *
|
|
OrphanXMLChild(JSContext *cx, JSXML *xml, uint32 i)
|
|
{
|
|
JSXMLNamespace *ns;
|
|
|
|
ns = XMLARRAY_MEMBER(&xml->xml_namespaces, 0, JSXMLNamespace);
|
|
xml = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (!ns || !xml)
|
|
return xml;
|
|
if (xml->xml_class == JSXML_CLASS_ELEMENT) {
|
|
if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
|
|
return NULL;
|
|
ns->declared = JS_FALSE;
|
|
}
|
|
xml->parent = NULL;
|
|
return xml;
|
|
}
|
|
|
|
static JSObject *
|
|
ToXML(JSContext *cx, jsval v)
|
|
{
|
|
JSObject *obj;
|
|
JSXML *xml;
|
|
JSClass *clasp;
|
|
JSString *str;
|
|
uint32 length;
|
|
|
|
if (JSVAL_IS_PRIMITIVE(v)) {
|
|
if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
|
|
goto bad;
|
|
} else {
|
|
obj = JSVAL_TO_OBJECT(v);
|
|
if (OBJECT_IS_XML(cx, obj)) {
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
if (xml->xml_kids.length != 1)
|
|
goto bad;
|
|
xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
|
|
if (xml) {
|
|
JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
|
|
return js_GetXMLObject(cx, xml);
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
clasp = OBJ_GET_CLASS(cx, obj);
|
|
if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
|
|
JS_ASSERT(0);
|
|
}
|
|
|
|
if (clasp != &js_StringClass &&
|
|
clasp != &js_NumberClass &&
|
|
clasp != &js_BooleanClass) {
|
|
goto bad;
|
|
}
|
|
}
|
|
|
|
str = js_ValueToString(cx, v);
|
|
if (!str)
|
|
return NULL;
|
|
if (IS_EMPTY(str)) {
|
|
length = 0;
|
|
#ifdef __GNUC__ /* suppress bogus gcc warnings */
|
|
xml = NULL;
|
|
#endif
|
|
} else {
|
|
xml = ParseXMLSource(cx, str);
|
|
if (!xml)
|
|
return NULL;
|
|
length = JSXML_LENGTH(xml);
|
|
}
|
|
|
|
if (length == 0) {
|
|
obj = js_NewXMLObject(cx, JSXML_CLASS_TEXT);
|
|
if (!obj)
|
|
return NULL;
|
|
} else if (length == 1) {
|
|
xml = OrphanXMLChild(cx, xml, 0);
|
|
if (!xml)
|
|
return NULL;
|
|
obj = js_GetXMLObject(cx, xml);
|
|
if (!obj)
|
|
return NULL;
|
|
} else {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SYNTAX_ERROR);
|
|
return NULL;
|
|
}
|
|
return obj;
|
|
|
|
bad:
|
|
str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
|
|
if (str) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XML_CONVERSION,
|
|
JS_GetStringBytes(str));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static JSBool
|
|
Append(JSContext *cx, JSXML *list, JSXML *kid);
|
|
|
|
static JSObject *
|
|
ToXMLList(JSContext *cx, jsval v)
|
|
{
|
|
JSObject *obj, *listobj;
|
|
JSXML *xml, *list, *kid;
|
|
JSClass *clasp;
|
|
JSString *str;
|
|
uint32 i, length;
|
|
|
|
if (JSVAL_IS_PRIMITIVE(v)) {
|
|
if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
|
|
goto bad;
|
|
} else {
|
|
obj = JSVAL_TO_OBJECT(v);
|
|
if (OBJECT_IS_XML(cx, obj)) {
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
if (xml->xml_class != JSXML_CLASS_LIST) {
|
|
listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (!listobj)
|
|
return NULL;
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
if (!Append(cx, list, xml))
|
|
return NULL;
|
|
return listobj;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
clasp = OBJ_GET_CLASS(cx, obj);
|
|
if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
|
|
JS_ASSERT(0);
|
|
}
|
|
|
|
if (clasp != &js_StringClass &&
|
|
clasp != &js_NumberClass &&
|
|
clasp != &js_BooleanClass) {
|
|
goto bad;
|
|
}
|
|
}
|
|
|
|
str = js_ValueToString(cx, v);
|
|
if (!str)
|
|
return NULL;
|
|
if (IS_EMPTY(str)) {
|
|
xml = NULL;
|
|
length = 0;
|
|
} else {
|
|
if (!js_EnterLocalRootScope(cx))
|
|
return NULL;
|
|
xml = ParseXMLSource(cx, str);
|
|
if (!xml) {
|
|
js_LeaveLocalRootScope(cx);
|
|
return NULL;
|
|
}
|
|
length = JSXML_LENGTH(xml);
|
|
}
|
|
|
|
listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (listobj) {
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
for (i = 0; i < length; i++) {
|
|
kid = OrphanXMLChild(cx, xml, i);
|
|
if (!kid || !Append(cx, list, kid)) {
|
|
listobj = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (xml)
|
|
js_LeaveLocalRootScopeWithResult(cx, (jsval) listobj);
|
|
return listobj;
|
|
|
|
bad:
|
|
str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
|
|
if (str) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XMLLIST_CONVERSION,
|
|
JS_GetStringBytes(str));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* ECMA-357 10.2.1 Steps 5-7 pulled out as common subroutines of XMLToXMLString
|
|
* and their library-public js_* counterparts. The guts of MakeXMLCDataString,
|
|
* MakeXMLCommentString, and MakeXMLPIString are further factored into a common
|
|
* MakeXMLSpecialString subroutine.
|
|
*
|
|
* These functions take ownership of sb->base, if sb is non-null, in all cases
|
|
* of success or failure.
|
|
*/
|
|
static JSString *
|
|
MakeXMLSpecialString(JSContext *cx, JSStringBuffer *sb,
|
|
JSString *str, JSString *str2,
|
|
const jschar *prefix, size_t prefixlength,
|
|
const jschar *suffix, size_t suffixlength)
|
|
{
|
|
JSStringBuffer localSB;
|
|
size_t length, length2, newlength;
|
|
jschar *bp, *base;
|
|
|
|
if (!sb) {
|
|
sb = &localSB;
|
|
js_InitStringBuffer(sb);
|
|
}
|
|
|
|
length = JSSTRING_LENGTH(str);
|
|
length2 = str2 ? JSSTRING_LENGTH(str2) : 0;
|
|
newlength = STRING_BUFFER_OFFSET(sb) +
|
|
prefixlength + length + ((length2 != 0) ? 1 + length2 : 0) +
|
|
suffixlength;
|
|
bp = base = (jschar *)
|
|
JS_realloc(cx, sb->base, (newlength + 1) * sizeof(jschar));
|
|
if (!bp) {
|
|
js_FinishStringBuffer(sb);
|
|
return NULL;
|
|
}
|
|
|
|
bp += STRING_BUFFER_OFFSET(sb);
|
|
js_strncpy(bp, prefix, prefixlength);
|
|
bp += prefixlength;
|
|
js_strncpy(bp, JSSTRING_CHARS(str), length);
|
|
bp += length;
|
|
if (length2 != 0) {
|
|
*bp++ = (jschar) ' ';
|
|
js_strncpy(bp, JSSTRING_CHARS(str2), length2);
|
|
bp += length2;
|
|
}
|
|
js_strncpy(bp, suffix, suffixlength);
|
|
bp[suffixlength] = 0;
|
|
|
|
str = js_NewString(cx, base, newlength, 0);
|
|
if (!str)
|
|
free(base);
|
|
return str;
|
|
}
|
|
|
|
static JSString *
|
|
MakeXMLCDATAString(JSContext *cx, JSStringBuffer *sb, JSString *str)
|
|
{
|
|
static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
|
|
'C', 'D', 'A', 'T', 'A',
|
|
'['};
|
|
static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};
|
|
|
|
return MakeXMLSpecialString(cx, sb, str, NULL,
|
|
cdata_prefix_ucNstr, 9,
|
|
cdata_suffix_ucNstr, 3);
|
|
}
|
|
|
|
static JSString *
|
|
MakeXMLCommentString(JSContext *cx, JSStringBuffer *sb, JSString *str)
|
|
{
|
|
static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
|
|
static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};
|
|
|
|
return MakeXMLSpecialString(cx, sb, str, NULL,
|
|
comment_prefix_ucNstr, 4,
|
|
comment_suffix_ucNstr, 3);
|
|
}
|
|
|
|
static JSString *
|
|
MakeXMLPIString(JSContext *cx, JSStringBuffer *sb, JSString *name,
|
|
JSString *value)
|
|
{
|
|
static const jschar pi_prefix_ucNstr[] = {'<', '?'};
|
|
static const jschar pi_suffix_ucNstr[] = {'?', '>'};
|
|
|
|
return MakeXMLSpecialString(cx, sb, name, value,
|
|
pi_prefix_ucNstr, 2,
|
|
pi_suffix_ucNstr, 2);
|
|
}
|
|
|
|
/*
|
|
* ECMA-357 10.2.1 17(d-g) pulled out into a common subroutine that appends
|
|
* equals, a double quote, an attribute value, and a closing double quote.
|
|
*/
|
|
static void
|
|
AppendAttributeValue(JSContext *cx, JSStringBuffer *sb, JSString *valstr)
|
|
{
|
|
js_AppendChar(sb, '=');
|
|
valstr = js_EscapeAttributeValue(cx, valstr, JS_TRUE);
|
|
if (!valstr) {
|
|
free(sb->base);
|
|
sb->base = STRING_BUFFER_ERROR_BASE;
|
|
return;
|
|
}
|
|
js_AppendJSString(sb, valstr);
|
|
}
|
|
|
|
/*
|
|
* ECMA-357 10.2.1.1 EscapeElementValue helper method.
|
|
*
|
|
* This function takes ownership of sb->base, if sb is non-null, in all cases
|
|
* of success or failure.
|
|
*/
|
|
static JSString *
|
|
EscapeElementValue(JSContext *cx, JSStringBuffer *sb, JSString *str)
|
|
{
|
|
size_t length, newlength;
|
|
const jschar *cp, *start, *end;
|
|
jschar c;
|
|
|
|
length = newlength = JSSTRING_LENGTH(str);
|
|
for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
|
|
c = *cp;
|
|
if (c == '<' || c == '>')
|
|
newlength += 3;
|
|
else if (c == '&')
|
|
newlength += 4;
|
|
|
|
if (newlength < length) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return NULL;
|
|
}
|
|
}
|
|
if ((sb && STRING_BUFFER_OFFSET(sb) != 0) || newlength > length) {
|
|
JSStringBuffer localSB;
|
|
if (!sb) {
|
|
sb = &localSB;
|
|
js_InitStringBuffer(sb);
|
|
}
|
|
if (!sb->grow(sb, newlength)) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return NULL;
|
|
}
|
|
for (cp = start; cp < end; cp++) {
|
|
c = *cp;
|
|
if (c == '<')
|
|
js_AppendCString(sb, js_lt_entity_str);
|
|
else if (c == '>')
|
|
js_AppendCString(sb, js_gt_entity_str);
|
|
else if (c == '&')
|
|
js_AppendCString(sb, js_amp_entity_str);
|
|
else
|
|
js_AppendChar(sb, c);
|
|
}
|
|
JS_ASSERT(STRING_BUFFER_OK(sb));
|
|
str = js_NewString(cx, sb->base, STRING_BUFFER_OFFSET(sb), 0);
|
|
if (!str)
|
|
js_FinishStringBuffer(sb);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
|
|
* This function takes ownership of sb->base, if sb is non-null, in all cases.
|
|
*/
|
|
static JSString *
|
|
EscapeAttributeValue(JSContext *cx, JSStringBuffer *sb, JSString *str,
|
|
JSBool quote)
|
|
{
|
|
size_t length, newlength;
|
|
const jschar *cp, *start, *end;
|
|
jschar c;
|
|
|
|
length = JSSTRING_LENGTH(str);
|
|
newlength = length + (quote ? 2 : 0);
|
|
for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
|
|
c = *cp;
|
|
if (c == '"')
|
|
newlength += 5;
|
|
else if (c == '<')
|
|
newlength += 3;
|
|
else if (c == '&' || c == '\n' || c == '\r' || c == '\t')
|
|
newlength += 4;
|
|
|
|
if (newlength < length) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return NULL;
|
|
}
|
|
}
|
|
if ((sb && STRING_BUFFER_OFFSET(sb) != 0) || newlength > length) {
|
|
JSStringBuffer localSB;
|
|
if (!sb) {
|
|
sb = &localSB;
|
|
js_InitStringBuffer(sb);
|
|
}
|
|
if (!sb->grow(sb, newlength)) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return NULL;
|
|
}
|
|
if (quote)
|
|
js_AppendChar(sb, '"');
|
|
for (cp = start; cp < end; cp++) {
|
|
c = *cp;
|
|
if (c == '"')
|
|
js_AppendCString(sb, js_quot_entity_str);
|
|
else if (c == '<')
|
|
js_AppendCString(sb, js_lt_entity_str);
|
|
else if (c == '&')
|
|
js_AppendCString(sb, js_amp_entity_str);
|
|
else if (c == '\n')
|
|
js_AppendCString(sb, "
");
|
|
else if (c == '\r')
|
|
js_AppendCString(sb, "
");
|
|
else if (c == '\t')
|
|
js_AppendCString(sb, "	");
|
|
else
|
|
js_AppendChar(sb, c);
|
|
}
|
|
if (quote)
|
|
js_AppendChar(sb, '"');
|
|
JS_ASSERT(STRING_BUFFER_OK(sb));
|
|
str = js_NewString(cx, sb->base, STRING_BUFFER_OFFSET(sb), 0);
|
|
if (!str)
|
|
js_FinishStringBuffer(sb);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
|
|
static JSXMLNamespace *
|
|
GetNamespace(JSContext *cx, JSXMLQName *qn, const JSXMLArray *inScopeNSes)
|
|
{
|
|
JSXMLNamespace *match, *ns;
|
|
uint32 i, n;
|
|
jsval argv[2];
|
|
JSObject *nsobj;
|
|
|
|
JS_ASSERT(qn->uri);
|
|
if (!qn->uri) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XML_NAMESPACE,
|
|
qn->prefix
|
|
? js_ValueToPrintableString(cx,
|
|
STRING_TO_JSVAL(qn->prefix))
|
|
: js_type_strs[JSTYPE_VOID]);
|
|
return NULL;
|
|
}
|
|
|
|
/* Look for a matching namespace in inScopeNSes, if provided. */
|
|
match = NULL;
|
|
if (inScopeNSes) {
|
|
for (i = 0, n = inScopeNSes->length; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(inScopeNSes, i, JSXMLNamespace);
|
|
if (!ns)
|
|
continue;
|
|
|
|
/*
|
|
* Erratum, very tricky, and not specified in ECMA-357 13.3.5.4:
|
|
* If we preserve prefixes, we must match null qn->prefix against
|
|
* an empty ns->prefix, in order to avoid generating redundant
|
|
* prefixed and default namespaces for cases such as:
|
|
*
|
|
* x = <t xmlns="http://foo.com"/>
|
|
* print(x.toXMLString());
|
|
*
|
|
* Per 10.3.2.1, the namespace attribute in t has an empty string
|
|
* prefix (*not* a null prefix), per 10.3.2.1 Step 6(h)(i)(1):
|
|
*
|
|
* 1. If the [local name] property of a is "xmlns"
|
|
* a. Map ns.prefix to the empty string
|
|
*
|
|
* But t's name has a null prefix in this implementation, meaning
|
|
* *undefined*, per 10.3.2.1 Step 6(c)'s NOTE (which refers to
|
|
* the http://www.w3.org/TR/xml-infoset/ spec, item 2.2.3, without
|
|
* saying how "no value" maps to an ECMA-357 value -- but it must
|
|
* map to the *undefined* prefix value).
|
|
*
|
|
* Since "" != undefined (or null, in the current implementation)
|
|
* the ECMA-357 spec will fail to match in [[GetNamespace]] called
|
|
* on t with argument {} U {(prefix="", uri="http://foo.com")}.
|
|
* This spec bug leads to ToXMLString results that duplicate the
|
|
* declared namespace.
|
|
*/
|
|
if (js_EqualStrings(ns->uri, qn->uri) &&
|
|
(ns->prefix == qn->prefix ||
|
|
((ns->prefix && qn->prefix)
|
|
? js_EqualStrings(ns->prefix, qn->prefix)
|
|
: IS_EMPTY(ns->prefix ? ns->prefix : qn->prefix)))) {
|
|
match = ns;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we didn't match, make a new namespace from qn. */
|
|
if (!match) {
|
|
argv[0] = qn->prefix ? STRING_TO_JSVAL(qn->prefix) : JSVAL_VOID;
|
|
argv[1] = STRING_TO_JSVAL(qn->uri);
|
|
nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL,
|
|
2, argv);
|
|
if (!nsobj)
|
|
return NULL;
|
|
match = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
static JSString *
|
|
GeneratePrefix(JSContext *cx, JSString *uri, JSXMLArray *decls)
|
|
{
|
|
const jschar *cp, *start, *end;
|
|
size_t length, newlength, offset;
|
|
uint32 i, n, m, serial;
|
|
jschar *bp, *dp;
|
|
JSBool done;
|
|
JSXMLNamespace *ns;
|
|
JSString *prefix;
|
|
|
|
JS_ASSERT(!IS_EMPTY(uri));
|
|
|
|
/*
|
|
* If there are no *declared* namespaces, skip all collision detection and
|
|
* return a short prefix quickly; an example of such a situation:
|
|
*
|
|
* var x = <f/>;
|
|
* var n = new Namespace("http://example.com/");
|
|
* x.@n::att = "val";
|
|
* x.toXMLString();
|
|
*
|
|
* This is necessary for various log10 uses below to be valid.
|
|
*/
|
|
if (decls->length == 0)
|
|
return JS_NewStringCopyZ(cx, "a");
|
|
|
|
/*
|
|
* Try peeling off the last filename suffix or pathname component till
|
|
* we have a valid XML name. This heuristic will prefer "xul" given
|
|
* ".../there.is.only.xul", "xbl" given ".../xbl", and "xbl2" given any
|
|
* likely URI of the form ".../xbl2/2005".
|
|
*/
|
|
start = JSSTRING_CHARS(uri);
|
|
cp = end = start + JSSTRING_LENGTH(uri);
|
|
while (--cp > start) {
|
|
if (*cp == '.' || *cp == '/' || *cp == ':') {
|
|
++cp;
|
|
length = PTRDIFF(end, cp, jschar);
|
|
if (IsXMLName(cp, length) && !STARTS_WITH_XML(cp, length))
|
|
break;
|
|
end = --cp;
|
|
}
|
|
}
|
|
length = PTRDIFF(end, cp, jschar);
|
|
|
|
/*
|
|
* If the namespace consisted only of non-XML names or names that begin
|
|
* case-insensitively with "xml", arbitrarily create a prefix consisting
|
|
* of 'a's of size length (allowing dp-calculating code to work with or
|
|
* without this branch executing) plus the space for storing a hyphen and
|
|
* the serial number (avoiding reallocation if a collision happens).
|
|
*/
|
|
bp = (jschar *) cp;
|
|
newlength = length;
|
|
if (STARTS_WITH_XML(cp, length) || !IsXMLName(cp, length)) {
|
|
newlength = length + 2 + (size_t) log10(decls->length);
|
|
bp = (jschar *)
|
|
JS_malloc(cx, (newlength + 1) * sizeof(jschar));
|
|
if (!bp)
|
|
return NULL;
|
|
|
|
bp[newlength] = 0;
|
|
for (i = 0; i < newlength; i++)
|
|
bp[i] = 'a';
|
|
}
|
|
|
|
/*
|
|
* Now search through decls looking for a collision. If we collide with
|
|
* an existing prefix, start tacking on a hyphen and a serial number.
|
|
*/
|
|
serial = 0;
|
|
do {
|
|
done = JS_TRUE;
|
|
for (i = 0, n = decls->length; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(decls, i, JSXMLNamespace);
|
|
if (ns && ns->prefix &&
|
|
JSSTRING_LENGTH(ns->prefix) == newlength &&
|
|
!memcmp(JSSTRING_CHARS(ns->prefix), bp,
|
|
newlength * sizeof(jschar))) {
|
|
if (bp == cp) {
|
|
newlength = length + 2 + (size_t) log10(n);
|
|
bp = (jschar *)
|
|
JS_malloc(cx, (newlength + 1) * sizeof(jschar));
|
|
if (!bp)
|
|
return NULL;
|
|
js_strncpy(bp, cp, length);
|
|
}
|
|
|
|
++serial;
|
|
JS_ASSERT(serial <= n);
|
|
dp = bp + length + 2 + (size_t) log10(serial);
|
|
*dp = 0;
|
|
for (m = serial; m != 0; m /= 10)
|
|
*--dp = (jschar)('0' + m % 10);
|
|
*--dp = '-';
|
|
JS_ASSERT(dp == bp + length);
|
|
|
|
done = JS_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
} while (!done);
|
|
|
|
if (bp == cp) {
|
|
offset = PTRDIFF(cp, start, jschar);
|
|
prefix = js_NewDependentString(cx, uri, offset, length, 0);
|
|
} else {
|
|
prefix = js_NewString(cx, bp, newlength, 0);
|
|
if (!prefix)
|
|
JS_free(cx, bp);
|
|
}
|
|
return prefix;
|
|
}
|
|
|
|
static JSBool
|
|
namespace_match(const void *a, const void *b)
|
|
{
|
|
const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
|
|
const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
|
|
|
|
if (nsb->prefix)
|
|
return nsa->prefix && js_EqualStrings(nsa->prefix, nsb->prefix);
|
|
return js_EqualStrings(nsa->uri, nsb->uri);
|
|
}
|
|
|
|
/* ECMA-357 10.2.1 and 10.2.2 */
|
|
#define TO_SOURCE_FLAG 0x80000000
|
|
|
|
static JSString *
|
|
XMLToXMLString(JSContext *cx, JSXML *xml, const JSXMLArray *ancestorNSes,
|
|
uintN indentLevel)
|
|
{
|
|
JSBool pretty, indentKids;
|
|
JSStringBuffer sb;
|
|
JSString *str, *prefix, *kidstr;
|
|
JSXMLArrayCursor cursor;
|
|
uint32 i, n, nextIndentLevel;
|
|
JSXMLArray empty, decls, ancdecls;
|
|
JSXMLNamespace *ns, *ns2;
|
|
JSXML *attr, *kid;
|
|
|
|
if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
|
|
return NULL;
|
|
|
|
js_InitStringBuffer(&sb);
|
|
if (pretty)
|
|
js_RepeatChar(&sb, ' ', indentLevel & ~TO_SOURCE_FLAG);
|
|
str = NULL;
|
|
|
|
switch (xml->xml_class) {
|
|
case JSXML_CLASS_TEXT:
|
|
/* Step 4. */
|
|
if (pretty) {
|
|
str = ChompXMLWhitespace(cx, xml->xml_value);
|
|
if (!str)
|
|
return NULL;
|
|
} else {
|
|
str = xml->xml_value;
|
|
}
|
|
return EscapeElementValue(cx, &sb, str);
|
|
|
|
case JSXML_CLASS_ATTRIBUTE:
|
|
/* Step 5. */
|
|
return EscapeAttributeValue(cx, &sb, xml->xml_value,
|
|
(indentLevel & TO_SOURCE_FLAG) != 0);
|
|
|
|
case JSXML_CLASS_COMMENT:
|
|
/* Step 6. */
|
|
return MakeXMLCommentString(cx, &sb, xml->xml_value);
|
|
|
|
case JSXML_CLASS_PROCESSING_INSTRUCTION:
|
|
/* Step 7. */
|
|
return MakeXMLPIString(cx, &sb, xml->name->localName, xml->xml_value);
|
|
|
|
case JSXML_CLASS_LIST:
|
|
/* ECMA-357 10.2.2. */
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
i = 0;
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (pretty && i != 0)
|
|
js_AppendChar(&sb, '\n');
|
|
|
|
kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel);
|
|
if (!kidstr)
|
|
break;
|
|
|
|
js_AppendJSString(&sb, kidstr);
|
|
++i;
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (kid)
|
|
goto list_out;
|
|
|
|
if (!sb.base) {
|
|
if (!STRING_BUFFER_OK(&sb)) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return NULL;
|
|
}
|
|
return cx->runtime->emptyString;
|
|
}
|
|
|
|
str = js_NewString(cx, sb.base, STRING_BUFFER_OFFSET(&sb), 0);
|
|
list_out:
|
|
if (!str)
|
|
js_FinishStringBuffer(&sb);
|
|
return str;
|
|
|
|
default:;
|
|
}
|
|
|
|
/* After this point, control must flow through label out: to exit. */
|
|
if (!js_EnterLocalRootScope(cx))
|
|
return NULL;
|
|
|
|
/* ECMA-357 10.2.1 step 8 onward: handle ToXMLString on an XML element. */
|
|
if (!ancestorNSes) {
|
|
XMLArrayInit(cx, &empty, 0);
|
|
ancestorNSes = ∅
|
|
}
|
|
XMLArrayInit(cx, &decls, 0);
|
|
ancdecls.capacity = 0;
|
|
|
|
/* Clone in-scope namespaces not in ancestorNSes into decls. */
|
|
XMLArrayCursorInit(&cursor, &xml->xml_namespaces);
|
|
while ((ns = (JSXMLNamespace *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (!ns->declared)
|
|
continue;
|
|
if (!XMLARRAY_HAS_MEMBER(ancestorNSes, ns, namespace_identity)) {
|
|
/* NOTE: may want to exclude unused namespaces here. */
|
|
ns2 = js_NewXMLNamespace(cx, ns->prefix, ns->uri, JS_TRUE);
|
|
if (!ns2 || !XMLARRAY_APPEND(cx, &decls, ns2))
|
|
break;
|
|
}
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (ns)
|
|
goto out;
|
|
|
|
/*
|
|
* Union ancestorNSes and decls into ancdecls. Note that ancdecls does
|
|
* not own its member references. In the spec, ancdecls has no name, but
|
|
* is always written out as (AncestorNamespaces U namespaceDeclarations).
|
|
*/
|
|
if (!XMLArrayInit(cx, &ancdecls, ancestorNSes->length + decls.length))
|
|
goto out;
|
|
for (i = 0, n = ancestorNSes->length; i < n; i++) {
|
|
ns2 = XMLARRAY_MEMBER(ancestorNSes, i, JSXMLNamespace);
|
|
if (!ns2)
|
|
continue;
|
|
JS_ASSERT(!XMLARRAY_HAS_MEMBER(&decls, ns2, namespace_identity));
|
|
if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
|
|
goto out;
|
|
}
|
|
for (i = 0, n = decls.length; i < n; i++) {
|
|
ns2 = XMLARRAY_MEMBER(&decls, i, JSXMLNamespace);
|
|
if (!ns2)
|
|
continue;
|
|
JS_ASSERT(!XMLARRAY_HAS_MEMBER(&ancdecls, ns2, namespace_identity));
|
|
if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
|
|
goto out;
|
|
}
|
|
|
|
/* Step 11, except we don't clone ns unless its prefix is undefined. */
|
|
ns = GetNamespace(cx, xml->name, &ancdecls);
|
|
if (!ns)
|
|
goto out;
|
|
|
|
/* Step 12 (NULL means *undefined* here), plus the deferred ns cloning. */
|
|
if (!ns->prefix) {
|
|
/*
|
|
* Create a namespace prefix that isn't used by any member of decls.
|
|
* Assign the new prefix to a copy of ns. Flag this namespace as if
|
|
* it were declared, for assertion-testing's sake later below.
|
|
*
|
|
* Erratum: if ns->prefix and xml->name are both null (*undefined* in
|
|
* ECMA-357), we know that xml was named using the default namespace
|
|
* (proof: see GetNamespace and the Namespace constructor called with
|
|
* two arguments). So we ought not generate a new prefix here, when
|
|
* we can declare ns as the default namespace for xml.
|
|
*
|
|
* This helps descendants inherit the namespace instead of redundantly
|
|
* redeclaring it with generated prefixes in each descendant.
|
|
*/
|
|
if (!xml->name->prefix) {
|
|
prefix = cx->runtime->emptyString;
|
|
} else {
|
|
prefix = GeneratePrefix(cx, ns->uri, &ancdecls);
|
|
if (!prefix)
|
|
goto out;
|
|
}
|
|
ns = js_NewXMLNamespace(cx, prefix, ns->uri, JS_TRUE);
|
|
if (!ns)
|
|
goto out;
|
|
|
|
/*
|
|
* If the xml->name was unprefixed, we must remove any declared default
|
|
* namespace from decls before appending ns. How can you get a default
|
|
* namespace in decls that doesn't match the one from name? Apparently
|
|
* by calling x.setNamespace(ns) where ns has no prefix. The other way
|
|
* to fix this is to update x's in-scope namespaces when setNamespace
|
|
* is called, but that's not specified by ECMA-357.
|
|
*
|
|
* Likely Erratum here, depending on whether the lack of update to x's
|
|
* in-scope namespace in XML.prototype.setNamespace (13.4.4.36) is an
|
|
* erratum or not. Note that changing setNamespace to update the list
|
|
* of in-scope namespaces will change x.namespaceDeclarations().
|
|
*/
|
|
if (IS_EMPTY(prefix)) {
|
|
i = XMLArrayFindMember(&decls, ns, namespace_match);
|
|
if (i != XML_NOT_FOUND)
|
|
XMLArrayDelete(cx, &decls, i, JS_TRUE);
|
|
}
|
|
|
|
/*
|
|
* In the spec, ancdecls has no name, but is always written out as
|
|
* (AncestorNamespaces U namespaceDeclarations). Since we compute
|
|
* that union in ancdecls, any time we append a namespace strong
|
|
* ref to decls, we must also append a weak ref to ancdecls. Order
|
|
* matters here: code at label out: releases strong refs in decls.
|
|
*/
|
|
if (!XMLARRAY_APPEND(cx, &ancdecls, ns) ||
|
|
!XMLARRAY_APPEND(cx, &decls, ns)) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Format the element or point-tag into sb. */
|
|
js_AppendChar(&sb, '<');
|
|
|
|
if (ns->prefix && !IS_EMPTY(ns->prefix)) {
|
|
js_AppendJSString(&sb, ns->prefix);
|
|
js_AppendChar(&sb, ':');
|
|
}
|
|
js_AppendJSString(&sb, xml->name->localName);
|
|
|
|
/*
|
|
* Step 16 makes a union to avoid writing two loops in step 17, to share
|
|
* common attribute value appending spec-code. We prefer two loops for
|
|
* faster code and less data overhead.
|
|
*/
|
|
|
|
/* Step 17(b): append attributes. */
|
|
XMLArrayCursorInit(&cursor, &xml->xml_attrs);
|
|
while ((attr = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
js_AppendChar(&sb, ' ');
|
|
ns2 = GetNamespace(cx, attr->name, &ancdecls);
|
|
if (!ns2)
|
|
break;
|
|
|
|
/* 17(b)(ii): NULL means *undefined* here. */
|
|
if (!ns2->prefix) {
|
|
prefix = GeneratePrefix(cx, ns2->uri, &ancdecls);
|
|
if (!prefix)
|
|
break;
|
|
|
|
/* Again, we avoid copying ns2 until we know it's prefix-less. */
|
|
ns2 = js_NewXMLNamespace(cx, prefix, ns2->uri, JS_TRUE);
|
|
if (!ns2)
|
|
break;
|
|
|
|
/*
|
|
* In the spec, ancdecls has no name, but is always written out as
|
|
* (AncestorNamespaces U namespaceDeclarations). Since we compute
|
|
* that union in ancdecls, any time we append a namespace strong
|
|
* ref to decls, we must also append a weak ref to ancdecls. Order
|
|
* matters here: code at label out: releases strong refs in decls.
|
|
*/
|
|
if (!XMLARRAY_APPEND(cx, &ancdecls, ns2) ||
|
|
!XMLARRAY_APPEND(cx, &decls, ns2)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* 17(b)(iii). */
|
|
if (!IS_EMPTY(ns2->prefix)) {
|
|
js_AppendJSString(&sb, ns2->prefix);
|
|
js_AppendChar(&sb, ':');
|
|
}
|
|
|
|
/* 17(b)(iv). */
|
|
js_AppendJSString(&sb, attr->name->localName);
|
|
|
|
/* 17(d-g). */
|
|
AppendAttributeValue(cx, &sb, attr->xml_value);
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (attr)
|
|
goto out;
|
|
|
|
/* Step 17(c): append XML namespace declarations. */
|
|
XMLArrayCursorInit(&cursor, &decls);
|
|
while ((ns2 = (JSXMLNamespace *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
JS_ASSERT(ns2->declared);
|
|
|
|
js_AppendCString(&sb, " xmlns");
|
|
|
|
/* 17(c)(ii): NULL means *undefined* here. */
|
|
if (!ns2->prefix) {
|
|
prefix = GeneratePrefix(cx, ns2->uri, &ancdecls);
|
|
if (!prefix)
|
|
break;
|
|
ns2->prefix = prefix;
|
|
}
|
|
|
|
/* 17(c)(iii). */
|
|
if (!IS_EMPTY(ns2->prefix)) {
|
|
js_AppendChar(&sb, ':');
|
|
js_AppendJSString(&sb, ns2->prefix);
|
|
}
|
|
|
|
/* 17(d-g). */
|
|
AppendAttributeValue(cx, &sb, ns2->uri);
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (ns2)
|
|
goto out;
|
|
|
|
/* Step 18: handle point tags. */
|
|
n = xml->xml_kids.length;
|
|
if (n == 0) {
|
|
js_AppendCString(&sb, "/>");
|
|
} else {
|
|
/* Steps 19 through 25: handle element content, and open the end-tag. */
|
|
js_AppendChar(&sb, '>');
|
|
indentKids = n > 1 ||
|
|
(n == 1 &&
|
|
(kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
|
|
kid->xml_class != JSXML_CLASS_TEXT);
|
|
|
|
if (pretty && indentKids) {
|
|
if (!GetUint32XMLSetting(cx, js_prettyIndent_str, &i))
|
|
goto out;
|
|
nextIndentLevel = indentLevel + i;
|
|
} else {
|
|
nextIndentLevel = indentLevel & TO_SOURCE_FLAG;
|
|
}
|
|
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (pretty && indentKids)
|
|
js_AppendChar(&sb, '\n');
|
|
|
|
kidstr = XMLToXMLString(cx, kid, &ancdecls, nextIndentLevel);
|
|
if (!kidstr)
|
|
break;
|
|
|
|
js_AppendJSString(&sb, kidstr);
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (kid)
|
|
goto out;
|
|
|
|
if (pretty && indentKids) {
|
|
js_AppendChar(&sb, '\n');
|
|
js_RepeatChar(&sb, ' ', indentLevel & ~TO_SOURCE_FLAG);
|
|
}
|
|
js_AppendCString(&sb, "</");
|
|
|
|
/* Step 26. */
|
|
if (ns->prefix && !IS_EMPTY(ns->prefix)) {
|
|
js_AppendJSString(&sb, ns->prefix);
|
|
js_AppendChar(&sb, ':');
|
|
}
|
|
|
|
/* Step 27. */
|
|
js_AppendJSString(&sb, xml->name->localName);
|
|
js_AppendChar(&sb, '>');
|
|
}
|
|
|
|
if (!STRING_BUFFER_OK(&sb)) {
|
|
JS_ReportOutOfMemory(cx);
|
|
goto out;
|
|
}
|
|
|
|
str = js_NewString(cx, sb.base, STRING_BUFFER_OFFSET(&sb), 0);
|
|
out:
|
|
js_LeaveLocalRootScopeWithResult(cx, STRING_TO_JSVAL(str));
|
|
if (!str && STRING_BUFFER_OK(&sb))
|
|
js_FinishStringBuffer(&sb);
|
|
XMLArrayFinish(cx, &decls);
|
|
if (ancdecls.capacity != 0)
|
|
XMLArrayFinish(cx, &ancdecls);
|
|
return str;
|
|
}
|
|
|
|
/* ECMA-357 10.2 */
|
|
static JSString *
|
|
ToXMLString(JSContext *cx, jsval v, uint32 toSourceFlag)
|
|
{
|
|
JSObject *obj;
|
|
JSString *str;
|
|
JSXML *xml;
|
|
|
|
if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XML_CONVERSION,
|
|
js_type_strs[JSVAL_IS_NULL(v)
|
|
? JSTYPE_NULL
|
|
: JSTYPE_VOID]);
|
|
return NULL;
|
|
}
|
|
|
|
if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
|
|
return js_ValueToString(cx, v);
|
|
|
|
if (JSVAL_IS_STRING(v))
|
|
return EscapeElementValue(cx, NULL, JSVAL_TO_STRING(v));
|
|
|
|
obj = JSVAL_TO_OBJECT(v);
|
|
if (!OBJECT_IS_XML(cx, obj)) {
|
|
if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, &v))
|
|
return NULL;
|
|
str = js_ValueToString(cx, v);
|
|
if (!str)
|
|
return NULL;
|
|
return EscapeElementValue(cx, NULL, str);
|
|
}
|
|
|
|
/* Handle non-element cases in this switch, returning from each case. */
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
return XMLToXMLString(cx, xml, NULL, toSourceFlag | 0);
|
|
}
|
|
|
|
static JSXMLQName *
|
|
ToAttributeName(JSContext *cx, jsval v)
|
|
{
|
|
JSString *name, *uri, *prefix;
|
|
JSObject *obj;
|
|
JSClass *clasp;
|
|
JSXMLQName *qn;
|
|
JSTempValueRooter tvr;
|
|
|
|
if (JSVAL_IS_STRING(v)) {
|
|
name = JSVAL_TO_STRING(v);
|
|
uri = prefix = cx->runtime->emptyString;
|
|
} else {
|
|
if (JSVAL_IS_PRIMITIVE(v)) {
|
|
name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
|
|
if (name) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XML_ATTR_NAME,
|
|
JS_GetStringBytes(name));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
obj = JSVAL_TO_OBJECT(v);
|
|
clasp = OBJ_GET_CLASS(cx, obj);
|
|
if (clasp == &js_AttributeNameClass)
|
|
return (JSXMLQName *) JS_GetPrivate(cx, obj);
|
|
|
|
if (clasp == &js_QNameClass.base) {
|
|
qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
|
|
uri = qn->uri;
|
|
prefix = qn->prefix;
|
|
name = qn->localName;
|
|
} else {
|
|
if (clasp == &js_AnyNameClass) {
|
|
name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
|
|
} else {
|
|
name = js_ValueToString(cx, v);
|
|
if (!name)
|
|
return NULL;
|
|
}
|
|
uri = prefix = cx->runtime->emptyString;
|
|
}
|
|
}
|
|
|
|
qn = js_NewXMLQName(cx, uri, prefix, name);
|
|
if (!qn)
|
|
return NULL;
|
|
|
|
JS_PUSH_TEMP_ROOT_QNAME(cx, qn, &tvr);
|
|
obj = js_GetAttributeNameObject(cx, qn);
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
if (!obj)
|
|
return NULL;
|
|
return qn;
|
|
}
|
|
|
|
static JSXMLQName *
|
|
ToXMLName(JSContext *cx, jsval v, jsid *funidp)
|
|
{
|
|
JSString *name;
|
|
JSObject *obj;
|
|
JSClass *clasp;
|
|
uint32 index;
|
|
JSXMLQName *qn;
|
|
JSAtom *atom;
|
|
|
|
if (JSVAL_IS_STRING(v)) {
|
|
name = JSVAL_TO_STRING(v);
|
|
} else {
|
|
if (JSVAL_IS_PRIMITIVE(v)) {
|
|
name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
|
|
if (name)
|
|
goto bad;
|
|
return NULL;
|
|
}
|
|
|
|
obj = JSVAL_TO_OBJECT(v);
|
|
clasp = OBJ_GET_CLASS(cx, obj);
|
|
if (clasp == &js_AttributeNameClass || clasp == &js_QNameClass.base)
|
|
goto out;
|
|
if (clasp == &js_AnyNameClass) {
|
|
name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
|
|
goto construct;
|
|
}
|
|
name = js_ValueToString(cx, v);
|
|
if (!name)
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* ECMA-357 10.6.1 step 1 seems to be incorrect. The spec says:
|
|
*
|
|
* 1. If ToString(ToNumber(P)) == ToString(P), throw a TypeError exception
|
|
*
|
|
* First, _P_ should be _s_, to refer to the given string.
|
|
*
|
|
* Second, why does ToXMLName applied to the string type throw TypeError
|
|
* only for numeric literals without any leading or trailing whitespace?
|
|
*
|
|
* If the idea is to reject uint32 property names, then the check needs to
|
|
* be stricter, to exclude hexadecimal and floating point literals.
|
|
*/
|
|
if (js_IdIsIndex(STRING_TO_JSVAL(name), &index))
|
|
goto bad;
|
|
|
|
if (*JSSTRING_CHARS(name) == '@') {
|
|
name = js_NewDependentString(cx, name, 1, JSSTRING_LENGTH(name) - 1, 0);
|
|
if (!name)
|
|
return NULL;
|
|
*funidp = 0;
|
|
return ToAttributeName(cx, STRING_TO_JSVAL(name));
|
|
}
|
|
|
|
construct:
|
|
v = STRING_TO_JSVAL(name);
|
|
obj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &v);
|
|
if (!obj)
|
|
return NULL;
|
|
|
|
out:
|
|
qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
|
|
atom = cx->runtime->atomState.lazy.functionNamespaceURIAtom;
|
|
if (qn->uri && atom &&
|
|
(qn->uri == ATOM_TO_STRING(atom) ||
|
|
js_EqualStrings(qn->uri, ATOM_TO_STRING(atom)))) {
|
|
if (!JS_ValueToId(cx, STRING_TO_JSVAL(qn->localName), funidp))
|
|
return NULL;
|
|
} else {
|
|
*funidp = 0;
|
|
}
|
|
return qn;
|
|
|
|
bad:
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XML_NAME,
|
|
js_ValueToPrintableString(cx, STRING_TO_JSVAL(name)));
|
|
return NULL;
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.13 XML [[AddInScopeNamespace]]. */
|
|
static JSBool
|
|
AddInScopeNamespace(JSContext *cx, JSXML *xml, JSXMLNamespace *ns)
|
|
{
|
|
JSXMLNamespace *match, *ns2;
|
|
uint32 i, n, m;
|
|
|
|
if (xml->xml_class != JSXML_CLASS_ELEMENT)
|
|
return JS_TRUE;
|
|
|
|
/* NULL means *undefined* here -- see ECMA-357 9.1.1.13 step 2. */
|
|
if (!ns->prefix) {
|
|
match = NULL;
|
|
for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
|
|
ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
|
|
if (ns2 && js_EqualStrings(ns2->uri, ns->uri)) {
|
|
match = ns2;
|
|
break;
|
|
}
|
|
}
|
|
if (!match && !XMLARRAY_ADD_MEMBER(cx, &xml->xml_namespaces, n, ns))
|
|
return JS_FALSE;
|
|
} else {
|
|
if (IS_EMPTY(ns->prefix) && IS_EMPTY(xml->name->uri))
|
|
return JS_TRUE;
|
|
match = NULL;
|
|
#ifdef __GNUC__ /* suppress bogus gcc warnings */
|
|
m = XML_NOT_FOUND;
|
|
#endif
|
|
for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
|
|
ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
|
|
if (ns2 && ns2->prefix &&
|
|
js_EqualStrings(ns2->prefix, ns->prefix)) {
|
|
match = ns2;
|
|
m = i;
|
|
break;
|
|
}
|
|
}
|
|
if (match && !js_EqualStrings(match->uri, ns->uri)) {
|
|
ns2 = XMLARRAY_DELETE(cx, &xml->xml_namespaces, m, JS_TRUE,
|
|
JSXMLNamespace);
|
|
JS_ASSERT(ns2 == match);
|
|
match->prefix = NULL;
|
|
if (!AddInScopeNamespace(cx, xml, match))
|
|
return JS_FALSE;
|
|
}
|
|
if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
|
|
return JS_FALSE;
|
|
}
|
|
|
|
/* OPTION: enforce that descendants have superset namespaces. */
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* ECMA-357 9.2.1.6 XMLList [[Append]]. */
|
|
static JSBool
|
|
Append(JSContext *cx, JSXML *list, JSXML *xml)
|
|
{
|
|
uint32 i, j, k, n;
|
|
JSXML *kid;
|
|
|
|
JS_ASSERT(list->xml_class == JSXML_CLASS_LIST);
|
|
i = list->xml_kids.length;
|
|
n = 1;
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
list->xml_target = xml->xml_target;
|
|
list->xml_targetprop = xml->xml_targetprop;
|
|
n = JSXML_LENGTH(xml);
|
|
k = i + n;
|
|
if (!XMLArraySetCapacity(cx, &list->xml_kids, k))
|
|
return JS_FALSE;
|
|
for (j = 0; j < n; j++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, j, JSXML);
|
|
if (kid)
|
|
XMLARRAY_SET_MEMBER(&list->xml_kids, i + j, kid);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
list->xml_target = xml->parent;
|
|
if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
|
|
list->xml_targetprop = NULL;
|
|
else
|
|
list->xml_targetprop = xml->name;
|
|
if (!XMLARRAY_ADD_MEMBER(cx, &list->xml_kids, i, xml))
|
|
return JS_FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.7 XML [[DeepCopy]] and 9.2.1.7 XMLList [[DeepCopy]]. */
|
|
static JSXML *
|
|
DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags);
|
|
|
|
static JSXML *
|
|
DeepCopy(JSContext *cx, JSXML *xml, JSObject *obj, uintN flags)
|
|
{
|
|
JSXML *copy;
|
|
JSBool ok;
|
|
|
|
/* Our caller may not be protecting newborns with a local root scope. */
|
|
if (!js_EnterLocalRootScope(cx))
|
|
return NULL;
|
|
copy = DeepCopyInLRS(cx, xml, flags);
|
|
if (copy) {
|
|
if (obj) {
|
|
/* Caller provided the object for this copy, hook 'em up. */
|
|
ok = JS_SetPrivate(cx, obj, copy);
|
|
if (ok)
|
|
copy->object = obj;
|
|
} else {
|
|
ok = js_GetXMLObject(cx, copy) != NULL;
|
|
}
|
|
if (!ok)
|
|
copy = NULL;
|
|
}
|
|
js_LeaveLocalRootScopeWithResult(cx, (jsval) copy);
|
|
return copy;
|
|
}
|
|
|
|
/*
|
|
* (i) We must be in a local root scope (InLRS).
|
|
* (ii) parent must have a rooted object.
|
|
* (iii) from's owning object must be locked if not thread-local.
|
|
*/
|
|
static JSBool
|
|
DeepCopySetInLRS(JSContext *cx, JSXMLArray *from, JSXMLArray *to, JSXML *parent,
|
|
uintN flags)
|
|
{
|
|
uint32 j, n;
|
|
JSXMLArrayCursor cursor;
|
|
JSBool ok;
|
|
JSXML *kid, *kid2;
|
|
JSString *str;
|
|
|
|
JS_ASSERT(cx->localRootStack);
|
|
|
|
n = from->length;
|
|
if (!XMLArraySetCapacity(cx, to, n))
|
|
return JS_FALSE;
|
|
|
|
XMLArrayCursorInit(&cursor, from);
|
|
j = 0;
|
|
ok = JS_TRUE;
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if ((flags & XSF_IGNORE_COMMENTS) &&
|
|
kid->xml_class == JSXML_CLASS_COMMENT) {
|
|
continue;
|
|
}
|
|
if ((flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS) &&
|
|
kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
|
|
continue;
|
|
}
|
|
if ((flags & XSF_IGNORE_WHITESPACE) &&
|
|
(kid->xml_flags & XMLF_WHITESPACE_TEXT)) {
|
|
continue;
|
|
}
|
|
kid2 = DeepCopyInLRS(cx, kid, flags);
|
|
if (!kid2) {
|
|
to->length = j;
|
|
ok = JS_FALSE;
|
|
break;
|
|
}
|
|
|
|
if ((flags & XSF_IGNORE_WHITESPACE) &&
|
|
n > 1 && kid2->xml_class == JSXML_CLASS_TEXT) {
|
|
str = ChompXMLWhitespace(cx, kid2->xml_value);
|
|
if (!str) {
|
|
to->length = j;
|
|
ok = JS_FALSE;
|
|
break;
|
|
}
|
|
kid2->xml_value = str;
|
|
}
|
|
|
|
XMLARRAY_SET_MEMBER(to, j, kid2);
|
|
++j;
|
|
if (parent->xml_class != JSXML_CLASS_LIST)
|
|
kid2->parent = parent;
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
|
|
if (j < n)
|
|
XMLArrayTrim(to);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSXML *
|
|
DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags)
|
|
{
|
|
JSXML *copy;
|
|
JSXMLQName *qn;
|
|
JSBool ok;
|
|
uint32 i, n;
|
|
JSXMLNamespace *ns, *ns2;
|
|
|
|
/* Our caller must be protecting newborn objects. */
|
|
JS_ASSERT(cx->localRootStack);
|
|
|
|
copy = js_NewXML(cx, xml->xml_class);
|
|
if (!copy)
|
|
return NULL;
|
|
qn = xml->name;
|
|
if (qn) {
|
|
qn = js_NewXMLQName(cx, qn->uri, qn->prefix, qn->localName);
|
|
if (!qn) {
|
|
ok = JS_FALSE;
|
|
goto out;
|
|
}
|
|
}
|
|
copy->name = qn;
|
|
copy->xml_flags = xml->xml_flags;
|
|
|
|
if (JSXML_HAS_VALUE(xml)) {
|
|
copy->xml_value = xml->xml_value;
|
|
ok = JS_TRUE;
|
|
} else {
|
|
ok = DeepCopySetInLRS(cx, &xml->xml_kids, ©->xml_kids, copy, flags);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
copy->xml_target = xml->xml_target;
|
|
copy->xml_targetprop = xml->xml_targetprop;
|
|
} else {
|
|
n = xml->xml_namespaces.length;
|
|
ok = XMLArraySetCapacity(cx, ©->xml_namespaces, n);
|
|
if (!ok)
|
|
goto out;
|
|
for (i = 0; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
|
|
if (!ns)
|
|
continue;
|
|
ns2 = js_NewXMLNamespace(cx, ns->prefix, ns->uri, ns->declared);
|
|
if (!ns2) {
|
|
copy->xml_namespaces.length = i;
|
|
ok = JS_FALSE;
|
|
goto out;
|
|
}
|
|
XMLARRAY_SET_MEMBER(©->xml_namespaces, i, ns2);
|
|
}
|
|
|
|
ok = DeepCopySetInLRS(cx, &xml->xml_attrs, ©->xml_attrs, copy,
|
|
0);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (!ok)
|
|
return NULL;
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
ReportBadXMLName(JSContext *cx, jsval id)
|
|
{
|
|
JSString *name;
|
|
|
|
name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, id, NULL);
|
|
if (name) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XML_NAME,
|
|
JS_GetStringBytes(name));
|
|
}
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.4 XML [[DeleteByIndex]]. */
|
|
static JSBool
|
|
DeleteByIndex(JSContext *cx, JSXML *xml, jsval id, jsval *vp)
|
|
{
|
|
uint32 index;
|
|
JSXML *kid;
|
|
|
|
if (!js_IdIsIndex(id, &index)) {
|
|
ReportBadXMLName(cx, id);
|
|
return JS_FALSE;
|
|
}
|
|
|
|
if (JSXML_HAS_KIDS(xml) && index < xml->xml_kids.length) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
|
|
if (kid)
|
|
kid->parent = NULL;
|
|
XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
|
|
}
|
|
|
|
*vp = JSVAL_TRUE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
typedef JSBool (*JSXMLNameMatcher)(JSXMLQName *nameqn, JSXML *xml);
|
|
|
|
static JSBool
|
|
MatchAttrName(JSXMLQName *nameqn, JSXML *attr)
|
|
{
|
|
JSXMLQName *attrqn = attr->name;
|
|
|
|
return (IS_STAR(nameqn->localName) ||
|
|
js_EqualStrings(attrqn->localName, nameqn->localName)) &&
|
|
(!nameqn->uri ||
|
|
js_EqualStrings(attrqn->uri, nameqn->uri));
|
|
}
|
|
|
|
static JSBool
|
|
MatchElemName(JSXMLQName *nameqn, JSXML *elem)
|
|
{
|
|
return (IS_STAR(nameqn->localName) ||
|
|
(elem->xml_class == JSXML_CLASS_ELEMENT &&
|
|
js_EqualStrings(elem->name->localName, nameqn->localName))) &&
|
|
(!nameqn->uri ||
|
|
(elem->xml_class == JSXML_CLASS_ELEMENT &&
|
|
js_EqualStrings(elem->name->uri, nameqn->uri)));
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.8 XML [[Descendants]] and 9.2.1.8 XMLList [[Descendants]]. */
|
|
static JSBool
|
|
DescendantsHelper(JSContext *cx, JSXML *xml, JSXMLQName *nameqn, JSXML *list)
|
|
{
|
|
uint32 i, n;
|
|
JSXML *attr, *kid;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_ELEMENT &&
|
|
OBJ_GET_CLASS(cx, nameqn->object) == &js_AttributeNameClass) {
|
|
for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
|
|
attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
|
|
if (attr && MatchAttrName(nameqn, attr)) {
|
|
if (!Append(cx, list, attr))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (!kid)
|
|
continue;
|
|
if (OBJ_GET_CLASS(cx, nameqn->object) != &js_AttributeNameClass &&
|
|
MatchElemName(nameqn, kid)) {
|
|
if (!Append(cx, list, kid))
|
|
return JS_FALSE;
|
|
}
|
|
if (!DescendantsHelper(cx, kid, nameqn, list))
|
|
return JS_FALSE;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSXML *
|
|
Descendants(JSContext *cx, JSXML *xml, jsval id)
|
|
{
|
|
jsid funid;
|
|
JSXMLQName *nameqn;
|
|
JSObject *listobj;
|
|
JSXML *list, *kid;
|
|
uint32 i, n;
|
|
JSBool ok;
|
|
|
|
nameqn = ToXMLName(cx, id, &funid);
|
|
if (!nameqn)
|
|
return NULL;
|
|
|
|
listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (!listobj)
|
|
return NULL;
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
if (funid)
|
|
return list;
|
|
|
|
/*
|
|
* Protect nameqn's object and strings from GC by linking list to it
|
|
* temporarily. The cx->newborn[GCX_OBJECT] GC root protects listobj,
|
|
* which protects list. Any other object allocations occuring beneath
|
|
* DescendantsHelper use local roots.
|
|
*/
|
|
list->name = nameqn;
|
|
if (!js_EnterLocalRootScope(cx))
|
|
return NULL;
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
ok = JS_TRUE;
|
|
for (i = 0, n = xml->xml_kids.length; i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
ok = DescendantsHelper(cx, kid, nameqn, list);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
ok = DescendantsHelper(cx, xml, nameqn, list);
|
|
}
|
|
js_LeaveLocalRootScopeWithResult(cx, (jsval) list);
|
|
if (!ok)
|
|
return NULL;
|
|
list->name = NULL;
|
|
return list;
|
|
}
|
|
|
|
static JSBool
|
|
xml_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
|
|
|
|
/* Recursive (JSXML *) parameterized version of Equals. */
|
|
static JSBool
|
|
XMLEquals(JSContext *cx, JSXML *xml, JSXML *vxml, JSBool *bp)
|
|
{
|
|
JSXMLQName *qn, *vqn;
|
|
uint32 i, j, n;
|
|
JSXMLArrayCursor cursor, vcursor;
|
|
JSXML *kid, *vkid, *attr, *vattr;
|
|
JSBool ok;
|
|
JSObject *xobj, *vobj;
|
|
|
|
retry:
|
|
if (xml->xml_class != vxml->xml_class) {
|
|
if (xml->xml_class == JSXML_CLASS_LIST && xml->xml_kids.length == 1) {
|
|
xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
|
|
if (xml)
|
|
goto retry;
|
|
}
|
|
if (vxml->xml_class == JSXML_CLASS_LIST && vxml->xml_kids.length == 1) {
|
|
vxml = XMLARRAY_MEMBER(&vxml->xml_kids, 0, JSXML);
|
|
if (vxml)
|
|
goto retry;
|
|
}
|
|
*bp = JS_FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
qn = xml->name;
|
|
vqn = vxml->name;
|
|
if (qn) {
|
|
*bp = vqn &&
|
|
js_EqualStrings(qn->localName, vqn->localName) &&
|
|
js_EqualStrings(qn->uri, vqn->uri);
|
|
} else {
|
|
*bp = vqn == NULL;
|
|
}
|
|
if (!*bp)
|
|
return JS_TRUE;
|
|
|
|
if (JSXML_HAS_VALUE(xml)) {
|
|
*bp = js_EqualStrings(xml->xml_value, vxml->xml_value);
|
|
} else if (xml->xml_kids.length != vxml->xml_kids.length) {
|
|
*bp = JS_FALSE;
|
|
} else {
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
XMLArrayCursorInit(&vcursor, &vxml->xml_kids);
|
|
for (;;) {
|
|
kid = (JSXML *) XMLArrayCursorNext(&cursor);
|
|
vkid = (JSXML *) XMLArrayCursorNext(&vcursor);
|
|
if (!kid || !vkid) {
|
|
*bp = !kid && !vkid;
|
|
ok = JS_TRUE;
|
|
break;
|
|
}
|
|
xobj = js_GetXMLObject(cx, kid);
|
|
vobj = js_GetXMLObject(cx, vkid);
|
|
ok = xobj && vobj &&
|
|
xml_equality(cx, xobj, OBJECT_TO_JSVAL(vobj), bp);
|
|
if (!ok || !*bp)
|
|
break;
|
|
}
|
|
XMLArrayCursorFinish(&vcursor);
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
|
|
if (*bp && xml->xml_class == JSXML_CLASS_ELEMENT) {
|
|
n = xml->xml_attrs.length;
|
|
if (n != vxml->xml_attrs.length)
|
|
*bp = JS_FALSE;
|
|
for (i = 0; *bp && i < n; i++) {
|
|
attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
|
|
if (!attr)
|
|
continue;
|
|
j = XMLARRAY_FIND_MEMBER(&vxml->xml_attrs, attr, attr_identity);
|
|
if (j == XML_NOT_FOUND) {
|
|
*bp = JS_FALSE;
|
|
break;
|
|
}
|
|
vattr = XMLARRAY_MEMBER(&vxml->xml_attrs, j, JSXML);
|
|
if (!vattr)
|
|
continue;
|
|
*bp = js_EqualStrings(attr->xml_value, vattr->xml_value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.9 XML [[Equals]] and 9.2.1.9 XMLList [[Equals]]. */
|
|
static JSBool
|
|
Equals(JSContext *cx, JSXML *xml, jsval v, JSBool *bp)
|
|
{
|
|
JSObject *vobj;
|
|
JSXML *vxml;
|
|
|
|
if (JSVAL_IS_PRIMITIVE(v)) {
|
|
*bp = JS_FALSE;
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
if (xml->xml_kids.length == 1) {
|
|
vxml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
|
|
if (!vxml)
|
|
return JS_TRUE;
|
|
vobj = js_GetXMLObject(cx, vxml);
|
|
if (!vobj)
|
|
return JS_FALSE;
|
|
return js_XMLObjectOps.equality(cx, vobj, v, bp);
|
|
}
|
|
if (JSVAL_IS_VOID(v) && xml->xml_kids.length == 0)
|
|
*bp = JS_TRUE;
|
|
}
|
|
} else {
|
|
vobj = JSVAL_TO_OBJECT(v);
|
|
if (!OBJECT_IS_XML(cx, vobj)) {
|
|
*bp = JS_FALSE;
|
|
} else {
|
|
vxml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
if (!XMLEquals(cx, xml, vxml, bp))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
CheckCycle(JSContext *cx, JSXML *xml, JSXML *kid)
|
|
{
|
|
JS_ASSERT(kid->xml_class != JSXML_CLASS_LIST);
|
|
|
|
do {
|
|
if (xml == kid) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_CYCLIC_VALUE, js_XML_str);
|
|
return JS_FALSE;
|
|
}
|
|
} while ((xml = xml->parent) != NULL);
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.11 XML [[Insert]]. */
|
|
static JSBool
|
|
Insert(JSContext *cx, JSXML *xml, uint32 i, jsval v)
|
|
{
|
|
uint32 j, n;
|
|
JSXML *vxml, *kid;
|
|
JSObject *vobj;
|
|
JSString *str;
|
|
|
|
if (!JSXML_HAS_KIDS(xml))
|
|
return JS_TRUE;
|
|
|
|
n = 1;
|
|
vxml = NULL;
|
|
if (!JSVAL_IS_PRIMITIVE(v)) {
|
|
vobj = JSVAL_TO_OBJECT(v);
|
|
if (OBJECT_IS_XML(cx, vobj)) {
|
|
vxml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
if (vxml->xml_class == JSXML_CLASS_LIST) {
|
|
n = vxml->xml_kids.length;
|
|
if (n == 0)
|
|
return JS_TRUE;
|
|
for (j = 0; j < n; j++) {
|
|
kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
|
|
if (!kid)
|
|
continue;
|
|
if (!CheckCycle(cx, xml, kid))
|
|
return JS_FALSE;
|
|
}
|
|
} else if (vxml->xml_class == JSXML_CLASS_ELEMENT) {
|
|
/* OPTION: enforce that descendants have superset namespaces. */
|
|
if (!CheckCycle(cx, xml, vxml))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
}
|
|
if (!vxml) {
|
|
str = js_ValueToString(cx, v);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
|
|
vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
|
|
if (!vxml)
|
|
return JS_FALSE;
|
|
vxml->xml_value = str;
|
|
}
|
|
|
|
if (i > xml->xml_kids.length)
|
|
i = xml->xml_kids.length;
|
|
|
|
if (!XMLArrayInsert(cx, &xml->xml_kids, i, n))
|
|
return JS_FALSE;
|
|
|
|
if (vxml->xml_class == JSXML_CLASS_LIST) {
|
|
for (j = 0; j < n; j++) {
|
|
kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
|
|
if (!kid)
|
|
continue;
|
|
kid->parent = xml;
|
|
XMLARRAY_SET_MEMBER(&xml->xml_kids, i + j, kid);
|
|
|
|
/* OPTION: enforce that descendants have superset namespaces. */
|
|
}
|
|
} else {
|
|
vxml->parent = xml;
|
|
XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
IndexToIdVal(JSContext *cx, uint32 index, jsval *idvp)
|
|
{
|
|
JSString *str;
|
|
|
|
if (index <= JSVAL_INT_MAX) {
|
|
*idvp = INT_TO_JSVAL(index);
|
|
} else {
|
|
str = js_NumberToString(cx, (jsdouble) index);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
*idvp = STRING_TO_JSVAL(str);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.12 XML [[Replace]]. */
|
|
static JSBool
|
|
Replace(JSContext *cx, JSXML *xml, jsval id, jsval v)
|
|
{
|
|
uint32 i, n;
|
|
JSXML *vxml, *kid;
|
|
JSObject *vobj;
|
|
jsval junk;
|
|
JSString *str;
|
|
|
|
if (!JSXML_HAS_KIDS(xml))
|
|
return JS_TRUE;
|
|
|
|
if (!js_IdIsIndex(id, &i)) {
|
|
ReportBadXMLName(cx, id);
|
|
return JS_FALSE;
|
|
}
|
|
|
|
/*
|
|
* 9.1.1.12
|
|
* [[Replace]] handles _i >= x.[[Length]]_ by incrementing _x.[[Length]_.
|
|
* It should therefore constrain callers to pass in _i <= x.[[Length]]_.
|
|
*/
|
|
n = xml->xml_kids.length;
|
|
if (i >= n) {
|
|
if (!IndexToIdVal(cx, n, &id))
|
|
return JS_FALSE;
|
|
i = n;
|
|
}
|
|
|
|
vxml = NULL;
|
|
if (!JSVAL_IS_PRIMITIVE(v)) {
|
|
vobj = JSVAL_TO_OBJECT(v);
|
|
if (OBJECT_IS_XML(cx, vobj))
|
|
vxml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
}
|
|
|
|
switch (vxml ? vxml->xml_class : JSXML_CLASS_LIMIT) {
|
|
case JSXML_CLASS_ELEMENT:
|
|
/* OPTION: enforce that descendants have superset namespaces. */
|
|
if (!CheckCycle(cx, xml, vxml))
|
|
return JS_FALSE;
|
|
case JSXML_CLASS_COMMENT:
|
|
case JSXML_CLASS_PROCESSING_INSTRUCTION:
|
|
case JSXML_CLASS_TEXT:
|
|
goto do_replace;
|
|
|
|
case JSXML_CLASS_LIST:
|
|
if (i < n && !DeleteByIndex(cx, xml, id, &junk))
|
|
return JS_FALSE;
|
|
if (!Insert(cx, xml, i, v))
|
|
return JS_FALSE;
|
|
break;
|
|
|
|
default:
|
|
str = js_ValueToString(cx, v);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
|
|
vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
|
|
if (!vxml)
|
|
return JS_FALSE;
|
|
vxml->xml_value = str;
|
|
|
|
do_replace:
|
|
vxml->parent = xml;
|
|
if (i < n) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid)
|
|
kid->parent = NULL;
|
|
}
|
|
if (!XMLARRAY_ADD_MEMBER(cx, &xml->xml_kids, i, vxml))
|
|
return JS_FALSE;
|
|
break;
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* Forward declared -- its implementation uses other statics that call it. */
|
|
static JSBool
|
|
ResolveValue(JSContext *cx, JSXML *list, JSXML **result);
|
|
|
|
/* ECMA-357 9.1.1.3 XML [[Delete]], 9.2.1.3 XML [[Delete]]. */
|
|
static JSBool
|
|
DeleteProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSXML *xml, *kid, *parent;
|
|
JSBool isIndex;
|
|
JSXMLArray *array;
|
|
uint32 length, index, kidIndex, deleteCount;
|
|
JSXMLQName *nameqn;
|
|
jsid funid;
|
|
JSObject *nameobj, *kidobj;
|
|
JSXMLNameMatcher matcher;
|
|
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
isIndex = js_IdIsIndex(id, &index);
|
|
if (JSXML_HAS_KIDS(xml)) {
|
|
array = &xml->xml_kids;
|
|
length = array->length;
|
|
} else {
|
|
array = NULL;
|
|
length = 0;
|
|
}
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
/* ECMA-357 9.2.1.3. */
|
|
if (isIndex && index < length) {
|
|
kid = XMLARRAY_MEMBER(array, index, JSXML);
|
|
if (!kid)
|
|
goto out;
|
|
parent = kid->parent;
|
|
if (parent) {
|
|
JS_ASSERT(parent != xml);
|
|
JS_ASSERT(JSXML_HAS_KIDS(parent));
|
|
|
|
if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
|
|
nameqn = kid->name;
|
|
nameobj = js_GetAttributeNameObject(cx, nameqn);
|
|
if (!nameobj || !js_GetXMLObject(cx, parent))
|
|
return JS_FALSE;
|
|
|
|
id = OBJECT_TO_JSVAL(nameobj);
|
|
if (!DeleteProperty(cx, parent->object, id, vp))
|
|
return JS_FALSE;
|
|
} else {
|
|
kidIndex = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid,
|
|
NULL);
|
|
JS_ASSERT(kidIndex != XML_NOT_FOUND);
|
|
if (!IndexToIdVal(cx, kidIndex, &id))
|
|
return JS_FALSE;
|
|
if (!DeleteByIndex(cx, parent, id, vp))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
|
|
XMLArrayDelete(cx, array, index, JS_TRUE);
|
|
} else {
|
|
for (index = 0; index < length; index++) {
|
|
kid = XMLARRAY_MEMBER(array, index, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj || !DeleteProperty(cx, kidobj, id, vp))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* ECMA-357 9.1.1.3. */
|
|
if (isIndex) {
|
|
/* See NOTE in spec: this variation is reserved for future use. */
|
|
ReportBadXMLName(cx, id);
|
|
return JS_FALSE;
|
|
}
|
|
|
|
nameqn = ToXMLName(cx, id, &funid);
|
|
if (!nameqn)
|
|
return JS_FALSE;
|
|
if (funid)
|
|
goto out;
|
|
nameobj = nameqn->object;
|
|
|
|
if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
|
|
if (xml->xml_class != JSXML_CLASS_ELEMENT)
|
|
goto out;
|
|
array = &xml->xml_attrs;
|
|
length = array->length;
|
|
matcher = MatchAttrName;
|
|
} else {
|
|
matcher = MatchElemName;
|
|
}
|
|
if (length != 0) {
|
|
deleteCount = 0;
|
|
for (index = 0; index < length; index++) {
|
|
kid = XMLARRAY_MEMBER(array, index, JSXML);
|
|
if (kid && matcher(nameqn, kid)) {
|
|
kid->parent = NULL;
|
|
XMLArrayDelete(cx, array, index, JS_FALSE);
|
|
++deleteCount;
|
|
} else if (deleteCount != 0) {
|
|
XMLARRAY_SET_MEMBER(array,
|
|
index - deleteCount,
|
|
array->vector[index]);
|
|
}
|
|
}
|
|
array->length -= deleteCount;
|
|
}
|
|
}
|
|
|
|
out:
|
|
*vp = JSVAL_TRUE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
SyncInScopeNamespaces(JSContext *cx, JSXML *xml)
|
|
{
|
|
JSXMLArray *nsarray;
|
|
uint32 i, n;
|
|
JSXMLNamespace *ns;
|
|
|
|
nsarray = &xml->xml_namespaces;
|
|
while ((xml = xml->parent) != NULL) {
|
|
for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
|
|
if (ns && !XMLARRAY_HAS_MEMBER(nsarray, ns, namespace_identity)) {
|
|
if (!XMLARRAY_APPEND(cx, nsarray, ns))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
GetNamedProperty(JSContext *cx, JSXML *xml, JSXMLQName* nameqn,
|
|
JSBool attributes, JSXML *list)
|
|
{
|
|
JSXMLArray *array;
|
|
JSXMLNameMatcher matcher;
|
|
JSXMLArrayCursor cursor;
|
|
JSXML *kid;
|
|
JSBool ok;
|
|
|
|
if (!JSXML_HAS_KIDS(xml))
|
|
return JS_TRUE;
|
|
|
|
if (attributes) {
|
|
array = &xml->xml_attrs;
|
|
matcher = MatchAttrName;
|
|
} else {
|
|
array = &xml->xml_kids;
|
|
matcher = MatchElemName;
|
|
}
|
|
|
|
XMLArrayCursorInit(&cursor, array);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (matcher(nameqn, kid)) {
|
|
if (!attributes && kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
ok = SyncInScopeNamespaces(cx, kid);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
ok = Append(cx, list, kid);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
}
|
|
ok = JS_TRUE;
|
|
|
|
out:
|
|
XMLArrayCursorFinish(&cursor);
|
|
return ok;
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.1 XML [[Get]] and 9.2.1.1 XMLList [[Get]]. */
|
|
static JSBool
|
|
GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSXML *xml, *list, *kid;
|
|
uint32 index;
|
|
JSObject *kidobj, *listobj;
|
|
JSXMLQName *nameqn;
|
|
jsid funid;
|
|
jsval roots[2];
|
|
JSTempValueRooter tvr;
|
|
JSBool attributes;
|
|
JSXMLArrayCursor cursor;
|
|
|
|
xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
|
|
if (!xml)
|
|
return JS_TRUE;
|
|
|
|
if (js_IdIsIndex(id, &index)) {
|
|
if (xml->xml_class != JSXML_CLASS_LIST) {
|
|
*vp = (index == 0) ? OBJECT_TO_JSVAL(obj) : JSVAL_VOID;
|
|
} else {
|
|
/*
|
|
* ECMA-357 9.2.1.1 starts here.
|
|
*
|
|
* Erratum: 9.2 is not completely clear that indexed properties
|
|
* correspond to kids, but that's what it seems to say, and it's
|
|
* what any sane user would want.
|
|
*/
|
|
if (index < xml->xml_kids.length) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
|
|
if (!kid) {
|
|
*vp = JSVAL_VOID;
|
|
return JS_TRUE;
|
|
}
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
return JS_FALSE;
|
|
|
|
*vp = OBJECT_TO_JSVAL(kidobj);
|
|
} else {
|
|
*vp = JSVAL_VOID;
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* ECMA-357 9.2.1.1/9.1.1.1 qname case.
|
|
*/
|
|
nameqn = ToXMLName(cx, id, &funid);
|
|
if (!nameqn)
|
|
return JS_FALSE;
|
|
if (funid)
|
|
return js_GetXMLFunction(cx, obj, funid, vp);
|
|
|
|
roots[0] = OBJECT_TO_JSVAL(nameqn->object);
|
|
JS_PUSH_TEMP_ROOT(cx, 1, roots, &tvr);
|
|
|
|
listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (listobj) {
|
|
roots[1] = OBJECT_TO_JSVAL(listobj);
|
|
tvr.count++;
|
|
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
attributes = (OBJ_GET_CLASS(cx, nameqn->object) ==
|
|
&js_AttributeNameClass);
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (kid->xml_class == JSXML_CLASS_ELEMENT &&
|
|
!GetNamedProperty(cx, kid, nameqn, attributes, list)) {
|
|
listobj = NULL;
|
|
break;
|
|
}
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
} else {
|
|
if (!GetNamedProperty(cx, xml, nameqn, attributes, list))
|
|
listobj = NULL;
|
|
}
|
|
|
|
/*
|
|
* Erratum: ECMA-357 9.1.1.1 misses that [[Append]] sets the given
|
|
* list's [[TargetProperty]] to the property that is being appended.
|
|
* This means that any use of the internal [[Get]] property returns
|
|
* a list which, when used by e.g. [[Insert]] duplicates the last
|
|
* element matched by id.
|
|
* See bug 336921.
|
|
*/
|
|
list->xml_target = xml;
|
|
list->xml_targetprop = nameqn;
|
|
*vp = OBJECT_TO_JSVAL(listobj);
|
|
}
|
|
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
return listobj != NULL;
|
|
}
|
|
|
|
static JSXML *
|
|
CopyOnWrite(JSContext *cx, JSXML *xml, JSObject *obj)
|
|
{
|
|
JS_ASSERT(xml->object != obj);
|
|
|
|
xml = DeepCopy(cx, xml, obj, 0);
|
|
if (!xml)
|
|
return NULL;
|
|
|
|
JS_ASSERT(xml->object == obj);
|
|
return xml;
|
|
}
|
|
|
|
#define CHECK_COPY_ON_WRITE(cx,xml,obj) \
|
|
(xml->object == obj ? xml : CopyOnWrite(cx, xml, obj))
|
|
|
|
static JSString *
|
|
KidToString(JSContext *cx, JSXML *xml, uint32 index)
|
|
{
|
|
JSXML *kid;
|
|
JSObject *kidobj;
|
|
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
|
|
if (!kid)
|
|
return cx->runtime->emptyString;
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
return NULL;
|
|
return js_ValueToString(cx, OBJECT_TO_JSVAL(kidobj));
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.2 XML [[Put]] and 9.2.1.2 XMLList [[Put]]. */
|
|
static JSBool
|
|
PutProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSBool ok, primitiveAssign;
|
|
enum { OBJ_ROOT, ID_ROOT, VAL_ROOT };
|
|
jsval roots[3];
|
|
JSTempValueRooter tvr;
|
|
JSXML *xml, *vxml, *rxml, *kid, *attr, *parent, *copy, *kid2, *match;
|
|
JSObject *vobj, *nameobj, *attrobj, *parentobj, *kidobj, *copyobj;
|
|
JSXMLQName *targetprop, *nameqn, *attrqn;
|
|
uint32 index, i, j, k, n, q;
|
|
jsval attrval, nsval, junk;
|
|
jsid funid;
|
|
JSString *left, *right, *space;
|
|
JSXMLNamespace *ns;
|
|
|
|
xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
|
|
if (!xml)
|
|
return JS_TRUE;
|
|
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
|
|
/* Precompute vxml for 9.2.1.2 2(c)(vii)(2-3) and 2(d) and 9.1.1.2 1. */
|
|
vxml = NULL;
|
|
if (!JSVAL_IS_PRIMITIVE(*vp)) {
|
|
vobj = JSVAL_TO_OBJECT(*vp);
|
|
if (OBJECT_IS_XML(cx, vobj))
|
|
vxml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
}
|
|
|
|
/* Control flow after here must exit via label out. */
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
|
|
roots[ID_ROOT] = id;
|
|
roots[VAL_ROOT] = *vp;
|
|
JS_PUSH_TEMP_ROOT(cx, 3, roots, &tvr);
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
/* ECMA-357 9.2.1.2. */
|
|
if (js_IdIsIndex(id, &index)) {
|
|
/* Step 1 sets i to the property index. */
|
|
i = index;
|
|
|
|
/* 2(a-b). */
|
|
if (xml->xml_target) {
|
|
ok = ResolveValue(cx, xml->xml_target, &rxml);
|
|
if (!ok)
|
|
goto out;
|
|
if (!rxml)
|
|
goto out;
|
|
JS_ASSERT(rxml->object);
|
|
} else {
|
|
rxml = NULL;
|
|
}
|
|
|
|
/* 2(c). */
|
|
if (index >= xml->xml_kids.length) {
|
|
/* 2(c)(i). */
|
|
if (rxml) {
|
|
if (rxml->xml_class == JSXML_CLASS_LIST) {
|
|
if (rxml->xml_kids.length != 1)
|
|
goto out;
|
|
rxml = XMLARRAY_MEMBER(&rxml->xml_kids, 0, JSXML);
|
|
if (!rxml)
|
|
goto out;
|
|
ok = js_GetXMLObject(cx, rxml) != NULL;
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Erratum: ECMA-357 9.2.1.2 step 2(c)(ii) sets
|
|
* _y.[[Parent]] = r_ where _r_ is the result of
|
|
* [[ResolveValue]] called on _x.[[TargetObject]] in
|
|
* 2(a)(i). This can result in text parenting text:
|
|
*
|
|
* var MYXML = new XML();
|
|
* MYXML.appendChild(new XML("<TEAM>Giants</TEAM>"));
|
|
*
|
|
* (testcase from Werner Sharp <wsharp@macromedia.com>).
|
|
*
|
|
* To match insertChildAfter, insertChildBefore,
|
|
* prependChild, and setChildren, we should silently
|
|
* do nothing in this case.
|
|
*/
|
|
if (!JSXML_HAS_KIDS(rxml))
|
|
goto out;
|
|
}
|
|
|
|
/* 2(c)(ii) is distributed below as several js_NewXML calls. */
|
|
targetprop = xml->xml_targetprop;
|
|
if (!targetprop || IS_STAR(targetprop->localName)) {
|
|
/* 2(c)(iv)(1-2), out of order w.r.t. 2(c)(iii). */
|
|
kid = js_NewXML(cx, JSXML_CLASS_TEXT);
|
|
if (!kid)
|
|
goto bad;
|
|
} else {
|
|
nameobj = js_GetXMLQNameObject(cx, targetprop);
|
|
if (!nameobj)
|
|
goto bad;
|
|
if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
|
|
/*
|
|
* 2(c)(iii)(1-3).
|
|
* Note that rxml can't be null here, because target
|
|
* and targetprop are non-null.
|
|
*/
|
|
ok = GetProperty(cx, rxml->object, id, &attrval);
|
|
if (!ok)
|
|
goto out;
|
|
if (JSVAL_IS_PRIMITIVE(attrval)) /* no such attribute */
|
|
goto out;
|
|
attrobj = JSVAL_TO_OBJECT(attrval);
|
|
attr = (JSXML *) JS_GetPrivate(cx, attrobj);
|
|
if (JSXML_LENGTH(attr) != 0)
|
|
goto out;
|
|
|
|
kid = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
|
|
} else {
|
|
/* 2(c)(v). */
|
|
kid = js_NewXML(cx, JSXML_CLASS_ELEMENT);
|
|
}
|
|
if (!kid)
|
|
goto bad;
|
|
|
|
/* An important bit of 2(c)(ii). */
|
|
kid->name = targetprop;
|
|
}
|
|
|
|
/* Final important bit of 2(c)(ii). */
|
|
kid->parent = rxml;
|
|
|
|
/* 2(c)(vi-vii). */
|
|
i = xml->xml_kids.length;
|
|
if (kid->xml_class != JSXML_CLASS_ATTRIBUTE) {
|
|
/*
|
|
* 2(c)(vii)(1) tests whether _y.[[Parent]]_ is not null.
|
|
* y.[[Parent]] is here called kid->parent, which we know
|
|
* from 2(c)(ii) is _r_, here called rxml. So let's just
|
|
* test that! Erratum, the spec should be simpler here.
|
|
*/
|
|
if (rxml) {
|
|
JS_ASSERT(JSXML_HAS_KIDS(rxml));
|
|
n = rxml->xml_kids.length;
|
|
j = n - 1;
|
|
if (n != 0 && i != 0) {
|
|
for (n = j, j = 0; j < n; j++) {
|
|
if (rxml->xml_kids.vector[j] ==
|
|
xml->xml_kids.vector[i-1]) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
goto bad;
|
|
ok = Insert(cx, rxml, j + 1, OBJECT_TO_JSVAL(kidobj));
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* 2(c)(vii)(2-3).
|
|
* Erratum: [[PropertyName]] in 2(c)(vii)(3) must be a
|
|
* typo for [[TargetProperty]].
|
|
*/
|
|
if (vxml) {
|
|
kid->name = (vxml->xml_class == JSXML_CLASS_LIST)
|
|
? vxml->xml_targetprop
|
|
: vxml->name;
|
|
}
|
|
}
|
|
|
|
/* 2(c)(viii). */
|
|
ok = Append(cx, xml, kid);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
|
|
/* 2(d). */
|
|
if (!vxml ||
|
|
vxml->xml_class == JSXML_CLASS_TEXT ||
|
|
vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
|
|
ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
|
|
if (!ok)
|
|
goto out;
|
|
roots[VAL_ROOT] = *vp;
|
|
}
|
|
|
|
/* 2(e). */
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (!kid)
|
|
goto out;
|
|
parent = kid->parent;
|
|
if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
|
|
nameobj = js_GetAttributeNameObject(cx, kid->name);
|
|
if (!nameobj)
|
|
goto bad;
|
|
id = OBJECT_TO_JSVAL(nameobj);
|
|
|
|
if (parent) {
|
|
/* 2(e)(i). */
|
|
parentobj = js_GetXMLObject(cx, parent);
|
|
if (!parentobj)
|
|
goto bad;
|
|
ok = PutProperty(cx, parentobj, id, vp);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
/* 2(e)(ii). */
|
|
ok = GetProperty(cx, parentobj, id, vp);
|
|
if (!ok)
|
|
goto out;
|
|
attr = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*vp));
|
|
|
|
/* 2(e)(iii). */
|
|
xml->xml_kids.vector[i] = attr->xml_kids.vector[0];
|
|
}
|
|
}
|
|
|
|
/* 2(f). */
|
|
else if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
|
|
/* 2(f)(i) Create a shallow copy _c_ of _V_. */
|
|
copyobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (!copyobj)
|
|
goto bad;
|
|
copy = (JSXML *) JS_GetPrivate(cx, copyobj);
|
|
n = vxml->xml_kids.length;
|
|
ok = XMLArraySetCapacity(cx, ©->xml_kids, n);
|
|
if (!ok)
|
|
goto out;
|
|
for (k = 0; k < n; k++) {
|
|
kid2 = XMLARRAY_MEMBER(&vxml->xml_kids, k, JSXML);
|
|
XMLARRAY_SET_MEMBER(©->xml_kids, k, kid2);
|
|
}
|
|
|
|
JS_ASSERT(parent != xml);
|
|
if (parent) {
|
|
q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
|
|
JS_ASSERT(q != XML_NOT_FOUND);
|
|
|
|
ok = IndexToIdVal(cx, q, &id);
|
|
if (!ok)
|
|
goto out;
|
|
ok = Replace(cx, parent, id, OBJECT_TO_JSVAL(copyobj));
|
|
if (!ok)
|
|
goto out;
|
|
|
|
#ifdef DEBUG
|
|
/* Erratum: this loop in the spec is useless. */
|
|
for (j = 0, n = copy->xml_kids.length; j < n; j++) {
|
|
kid2 = XMLARRAY_MEMBER(&parent->xml_kids, q + j, JSXML);
|
|
JS_ASSERT(XMLARRAY_MEMBER(©->xml_kids, j, JSXML)
|
|
== kid2);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* 2(f)(iv-vi).
|
|
* Erratum: notice the unhandled zero-length V basis case and
|
|
* the off-by-one errors for the n != 0 cases in the spec.
|
|
*/
|
|
if (n == 0) {
|
|
XMLArrayDelete(cx, &xml->xml_kids, i, JS_TRUE);
|
|
} else {
|
|
ok = XMLArrayInsert(cx, &xml->xml_kids, i + 1, n - 1);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
for (j = 0; j < n; j++)
|
|
xml->xml_kids.vector[i + j] = copy->xml_kids.vector[j];
|
|
}
|
|
}
|
|
|
|
/* 2(g). */
|
|
else if (vxml || JSXML_HAS_VALUE(kid)) {
|
|
if (parent) {
|
|
q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
|
|
JS_ASSERT(q != XML_NOT_FOUND);
|
|
|
|
ok = IndexToIdVal(cx, q, &id);
|
|
if (!ok)
|
|
goto out;
|
|
ok = Replace(cx, parent, id, *vp);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
vxml = XMLARRAY_MEMBER(&parent->xml_kids, q, JSXML);
|
|
if (!vxml)
|
|
goto out;
|
|
roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vxml->object);
|
|
}
|
|
|
|
/*
|
|
* 2(g)(iii).
|
|
* Erratum: _V_ may not be of type XML, but all index-named
|
|
* properties _x[i]_ in an XMLList _x_ must be of type XML,
|
|
* according to 9.2.1.1 Overview and other places in the spec.
|
|
*
|
|
* Thanks to 2(d), we know _V_ (*vp here) is either a string
|
|
* or an XML/XMLList object. If *vp is a string, call ToXML
|
|
* on it to satisfy the constraint.
|
|
*/
|
|
if (!vxml) {
|
|
JS_ASSERT(JSVAL_IS_STRING(*vp));
|
|
vobj = ToXML(cx, *vp);
|
|
if (!vobj)
|
|
goto bad;
|
|
roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vobj);
|
|
vxml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
}
|
|
XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
|
|
}
|
|
|
|
/* 2(h). */
|
|
else {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
goto bad;
|
|
id = ATOM_KEY(cx->runtime->atomState.starAtom);
|
|
ok = PutProperty(cx, kidobj, id, vp);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
} else {
|
|
/*
|
|
* 3.
|
|
* Erratum: if x.[[Length]] > 1 or [[ResolveValue]] returns null
|
|
* or an r with r.[[Length]] != 1, throw TypeError.
|
|
*/
|
|
n = JSXML_LENGTH(xml);
|
|
if (n > 1)
|
|
goto type_error;
|
|
if (n == 0) {
|
|
ok = ResolveValue(cx, xml, &rxml);
|
|
if (!ok)
|
|
goto out;
|
|
if (!rxml || JSXML_LENGTH(rxml) != 1)
|
|
goto type_error;
|
|
ok = Append(cx, xml, rxml);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
JS_ASSERT(JSXML_LENGTH(xml) == 1);
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
|
|
if (!kid)
|
|
goto out;
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
goto bad;
|
|
ok = PutProperty(cx, kidobj, id, vp);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
} else {
|
|
/*
|
|
* ECMA-357 9.1.1.2.
|
|
* Erratum: move steps 3 and 4 to before 1 and 2, to avoid wasted
|
|
* effort in ToString or [[DeepCopy]].
|
|
*/
|
|
if (js_IdIsIndex(id, &index)) {
|
|
/* See NOTE in spec: this variation is reserved for future use. */
|
|
ReportBadXMLName(cx, id);
|
|
goto bad;
|
|
}
|
|
|
|
nameqn = ToXMLName(cx, id, &funid);
|
|
if (!nameqn)
|
|
goto bad;
|
|
if (funid) {
|
|
ok = js_SetProperty(cx, obj, funid, vp);
|
|
goto out;
|
|
}
|
|
nameobj = nameqn->object;
|
|
|
|
if (JSXML_HAS_VALUE(xml))
|
|
goto out;
|
|
|
|
if (!vxml ||
|
|
vxml->xml_class == JSXML_CLASS_TEXT ||
|
|
vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
|
|
ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
|
|
if (!ok)
|
|
goto out;
|
|
} else {
|
|
rxml = DeepCopyInLRS(cx, vxml, 0);
|
|
if (!rxml || !js_GetXMLObject(cx, rxml))
|
|
goto bad;
|
|
vxml = rxml;
|
|
*vp = OBJECT_TO_JSVAL(vxml->object);
|
|
}
|
|
roots[VAL_ROOT] = *vp;
|
|
|
|
/*
|
|
* 6.
|
|
* Erratum: why is this done here, so early? use is way later....
|
|
*/
|
|
ok = js_GetDefaultXMLNamespace(cx, &nsval);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
|
|
/* 7(a). */
|
|
if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)))
|
|
goto out;
|
|
|
|
/* 7(b-c). */
|
|
if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
|
|
n = vxml->xml_kids.length;
|
|
if (n == 0) {
|
|
*vp = STRING_TO_JSVAL(cx->runtime->emptyString);
|
|
} else {
|
|
left = KidToString(cx, vxml, 0);
|
|
if (!left)
|
|
goto bad;
|
|
|
|
space = ATOM_TO_STRING(cx->runtime->atomState.spaceAtom);
|
|
for (i = 1; i < n; i++) {
|
|
left = js_ConcatStrings(cx, left, space);
|
|
if (!left)
|
|
goto bad;
|
|
right = KidToString(cx, vxml, i);
|
|
if (!right)
|
|
goto bad;
|
|
left = js_ConcatStrings(cx, left, right);
|
|
if (!left)
|
|
goto bad;
|
|
}
|
|
|
|
roots[VAL_ROOT] = *vp = STRING_TO_JSVAL(left);
|
|
}
|
|
} else {
|
|
ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
|
|
if (!ok)
|
|
goto out;
|
|
roots[VAL_ROOT] = *vp;
|
|
}
|
|
|
|
/* 7(d-e). */
|
|
match = NULL;
|
|
for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
|
|
attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
|
|
if (!attr)
|
|
continue;
|
|
attrqn = attr->name;
|
|
if (js_EqualStrings(attrqn->localName, nameqn->localName) &&
|
|
(!nameqn->uri ||
|
|
js_EqualStrings(attrqn->uri, nameqn->uri))) {
|
|
if (!match) {
|
|
match = attr;
|
|
} else {
|
|
nameobj = js_GetAttributeNameObject(cx, attrqn);
|
|
if (!nameobj)
|
|
goto bad;
|
|
|
|
id = OBJECT_TO_JSVAL(nameobj);
|
|
ok = DeleteProperty(cx, obj, id, &junk);
|
|
if (!ok)
|
|
goto out;
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 7(f). */
|
|
attr = match;
|
|
if (!attr) {
|
|
/* 7(f)(i-ii). */
|
|
if (!nameqn->uri) {
|
|
left = right = cx->runtime->emptyString;
|
|
} else {
|
|
left = nameqn->uri;
|
|
right = nameqn->prefix;
|
|
}
|
|
nameqn = js_NewXMLQName(cx, left, right, nameqn->localName);
|
|
if (!nameqn)
|
|
goto bad;
|
|
|
|
/* 7(f)(iii). */
|
|
attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
|
|
if (!attr)
|
|
goto bad;
|
|
attr->parent = xml;
|
|
attr->name = nameqn;
|
|
|
|
/* 7(f)(iv). */
|
|
ok = XMLARRAY_ADD_MEMBER(cx, &xml->xml_attrs, n, attr);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
/* 7(f)(v-vi). */
|
|
ns = GetNamespace(cx, nameqn, NULL);
|
|
if (!ns)
|
|
goto bad;
|
|
ok = AddInScopeNamespace(cx, xml, ns);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
|
|
/* 7(g). */
|
|
attr->xml_value = JSVAL_TO_STRING(*vp);
|
|
goto out;
|
|
}
|
|
|
|
/* 8-9. */
|
|
if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)) &&
|
|
!IS_STAR(nameqn->localName)) {
|
|
goto out;
|
|
}
|
|
|
|
/* 10-11. */
|
|
id = JSVAL_VOID;
|
|
primitiveAssign = !vxml && !IS_STAR(nameqn->localName);
|
|
|
|
/* 12. */
|
|
k = n = xml->xml_kids.length;
|
|
kid2 = NULL;
|
|
while (k != 0) {
|
|
--k;
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, k, JSXML);
|
|
if (kid && MatchElemName(nameqn, kid)) {
|
|
if (!JSVAL_IS_VOID(id)) {
|
|
ok = DeleteByIndex(cx, xml, id, &junk);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
ok = IndexToIdVal(cx, k, &id);
|
|
if (!ok)
|
|
goto out;
|
|
kid2 = kid;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Erratum: ECMA-357 specified child insertion inconsistently:
|
|
* insertChildBefore and insertChildAfter insert an arbitrary XML
|
|
* instance, and therefore can create cycles, but appendChild as
|
|
* specified by the "Overview" of 13.4.4.3 calls [[DeepCopy]] on
|
|
* its argument. But the "Semantics" in 13.4.4.3 do not include
|
|
* any [[DeepCopy]] call.
|
|
*
|
|
* Fixing this (https://bugzilla.mozilla.org/show_bug.cgi?id=312692)
|
|
* required adding cycle detection, and allowing duplicate kids to
|
|
* be created (see comment 6 in the bug). Allowing duplicate kid
|
|
* references means the loop above will delete all but the lowest
|
|
* indexed reference, and each [[DeleteByIndex]] nulls the kid's
|
|
* parent. Thus the need to restore parent here. This is covered
|
|
* by https://bugzilla.mozilla.org/show_bug.cgi?id=327564.
|
|
*/
|
|
if (kid2) {
|
|
JS_ASSERT(kid2->parent == xml || !kid2->parent);
|
|
if (!kid2->parent)
|
|
kid2->parent = xml;
|
|
}
|
|
|
|
/* 13. */
|
|
if (JSVAL_IS_VOID(id)) {
|
|
/* 13(a). */
|
|
ok = IndexToIdVal(cx, n, &id);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
/* 13(b). */
|
|
if (primitiveAssign) {
|
|
if (!nameqn->uri) {
|
|
ns = (JSXMLNamespace *)
|
|
JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
|
|
left = ns->uri;
|
|
right = ns->prefix;
|
|
} else {
|
|
left = nameqn->uri;
|
|
right = nameqn->prefix;
|
|
}
|
|
nameqn = js_NewXMLQName(cx, left, right, nameqn->localName);
|
|
if (!nameqn)
|
|
goto bad;
|
|
|
|
/* 13(b)(iii). */
|
|
vobj = js_NewXMLObject(cx, JSXML_CLASS_ELEMENT);
|
|
if (!vobj)
|
|
goto bad;
|
|
vxml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
vxml->parent = xml;
|
|
vxml->name = nameqn;
|
|
|
|
/* 13(b)(iv-vi). */
|
|
ns = GetNamespace(cx, nameqn, NULL);
|
|
if (!ns)
|
|
goto bad;
|
|
ok = Replace(cx, xml, id, OBJECT_TO_JSVAL(vobj));
|
|
if (!ok)
|
|
goto out;
|
|
ok = AddInScopeNamespace(cx, vxml, ns);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* 14. */
|
|
if (primitiveAssign) {
|
|
JSXMLArrayCursor cursor;
|
|
|
|
js_IdIsIndex(id, &index);
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
cursor.index = index;
|
|
kid = (JSXML *) XMLArrayCursorItem(&cursor);
|
|
if (JSXML_HAS_KIDS(kid)) {
|
|
XMLArrayFinish(cx, &kid->xml_kids);
|
|
ok = XMLArrayInit(cx, &kid->xml_kids, 1);
|
|
}
|
|
|
|
/* 14(b-c). */
|
|
/* XXXbe Erratum? redundant w.r.t. 7(b-c) else clause above */
|
|
if (ok) {
|
|
ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
|
|
if (ok && !IS_EMPTY(JSVAL_TO_STRING(*vp))) {
|
|
roots[VAL_ROOT] = *vp;
|
|
if ((JSXML *) XMLArrayCursorItem(&cursor) == kid)
|
|
ok = Replace(cx, kid, JSVAL_ZERO, *vp);
|
|
}
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
} else {
|
|
/* 15(a). */
|
|
ok = Replace(cx, xml, id, *vp);
|
|
}
|
|
}
|
|
|
|
out:
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
js_LeaveLocalRootScope(cx);
|
|
return ok;
|
|
|
|
type_error:
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_BAD_XMLLIST_PUT,
|
|
js_ValueToPrintableString(cx, id));
|
|
bad:
|
|
ok = JS_FALSE;
|
|
goto out;
|
|
}
|
|
|
|
/* ECMA-357 9.1.1.10 XML [[ResolveValue]], 9.2.1.10 XMLList [[ResolveValue]]. */
|
|
static JSBool
|
|
ResolveValue(JSContext *cx, JSXML *list, JSXML **result)
|
|
{
|
|
JSXML *target, *base;
|
|
JSXMLQName *targetprop;
|
|
JSObject *targetpropobj;
|
|
jsval id, tv;
|
|
|
|
/* Our caller must be protecting newborn objects. */
|
|
JS_ASSERT(cx->localRootStack);
|
|
|
|
if (list->xml_class != JSXML_CLASS_LIST || list->xml_kids.length != 0) {
|
|
if (!js_GetXMLObject(cx, list))
|
|
return JS_FALSE;
|
|
*result = list;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
target = list->xml_target;
|
|
targetprop = list->xml_targetprop;
|
|
if (!target || !targetprop || IS_STAR(targetprop->localName)) {
|
|
*result = NULL;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
targetpropobj = js_GetXMLQNameObject(cx, targetprop);
|
|
if (!targetpropobj)
|
|
return JS_FALSE;
|
|
if (OBJ_GET_CLASS(cx, targetpropobj) == &js_AttributeNameClass) {
|
|
*result = NULL;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
if (!ResolveValue(cx, target, &base))
|
|
return JS_FALSE;
|
|
if (!base) {
|
|
*result = NULL;
|
|
return JS_TRUE;
|
|
}
|
|
if (!js_GetXMLObject(cx, base))
|
|
return JS_FALSE;
|
|
|
|
id = OBJECT_TO_JSVAL(targetpropobj);
|
|
if (!GetProperty(cx, base->object, id, &tv))
|
|
return JS_FALSE;
|
|
target = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(tv));
|
|
|
|
if (JSXML_LENGTH(target) == 0) {
|
|
if (base->xml_class == JSXML_CLASS_LIST && JSXML_LENGTH(base) > 1) {
|
|
*result = NULL;
|
|
return JS_TRUE;
|
|
}
|
|
tv = STRING_TO_JSVAL(cx->runtime->emptyString);
|
|
if (!PutProperty(cx, base->object, id, &tv))
|
|
return JS_FALSE;
|
|
if (!GetProperty(cx, base->object, id, &tv))
|
|
return JS_FALSE;
|
|
target = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(tv));
|
|
}
|
|
|
|
*result = target;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* HasProperty must be able to return a found JSProperty and the object in
|
|
* which it was found, if id is of the form function::name. For other ids,
|
|
* if they index or name an XML child, we return FOUND_XML_PROPERTY in *propp
|
|
* and null in *objp.
|
|
*
|
|
* DROP_PROPERTY helps HasProperty callers drop function properties without
|
|
* trying to drop the magic FOUND_XML_PROPERTY cookie.
|
|
*/
|
|
#define FOUND_XML_PROPERTY ((JSProperty *) 1)
|
|
#define DROP_PROPERTY(cx,pobj,prop) (((prop) != FOUND_XML_PROPERTY) \
|
|
? OBJ_DROP_PROPERTY(cx, pobj, prop) \
|
|
: (void) 0)
|
|
|
|
/* ECMA-357 9.1.1.6 XML [[HasProperty]] and 9.2.1.5 XMLList [[HasProperty]]. */
|
|
static JSBool
|
|
HasProperty(JSContext *cx, JSObject *obj, jsval id, JSObject **objp,
|
|
JSProperty **propp)
|
|
{
|
|
JSXML *xml, *kid;
|
|
JSXMLArrayCursor cursor;
|
|
JSObject *kidobj;
|
|
JSXMLQName *qn;
|
|
jsid funid;
|
|
JSXMLArray *array;
|
|
JSXMLNameMatcher matcher;
|
|
uint32 i, n;
|
|
|
|
*objp = NULL;
|
|
*propp = NULL;
|
|
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
n = JSXML_LENGTH(xml);
|
|
if (js_IdIsIndex(id, &i)) {
|
|
if (i < n)
|
|
*propp = FOUND_XML_PROPERTY;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj || !HasProperty(cx, kidobj, id, objp, propp))
|
|
break;
|
|
if (*propp)
|
|
break;
|
|
}
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (kid)
|
|
return *propp != NULL;
|
|
} else {
|
|
if (xml->xml_class == JSXML_CLASS_ELEMENT && js_IdIsIndex(id, &i)) {
|
|
if (i == 0)
|
|
*propp = FOUND_XML_PROPERTY;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
qn = ToXMLName(cx, id, &funid);
|
|
if (!qn)
|
|
return JS_FALSE;
|
|
if (funid)
|
|
return js_LookupProperty(cx, obj, funid, objp, propp);
|
|
|
|
if (xml->xml_class != JSXML_CLASS_ELEMENT)
|
|
return JS_TRUE;
|
|
|
|
if (OBJ_GET_CLASS(cx, qn->object) == &js_AttributeNameClass) {
|
|
array = &xml->xml_attrs;
|
|
matcher = MatchAttrName;
|
|
} else {
|
|
array = &xml->xml_kids;
|
|
matcher = MatchElemName;
|
|
}
|
|
for (i = 0, n = array->length; i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(array, i, JSXML);
|
|
if (kid && matcher(qn, kid)) {
|
|
*propp = FOUND_XML_PROPERTY;
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static void
|
|
xml_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSXML *xml;
|
|
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
if (!xml)
|
|
return;
|
|
if (xml->object == obj)
|
|
xml->object = NULL;
|
|
UNMETER(xml_stats.livexmlobj);
|
|
}
|
|
|
|
static void
|
|
xml_mark_vector(JSContext *cx, JSXML **vec, uint32 len)
|
|
{
|
|
uint32 i;
|
|
JSXML *elt;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
elt = vec[i];
|
|
{
|
|
#ifdef GC_MARK_DEBUG
|
|
char buf[120];
|
|
|
|
if (elt->xml_class == JSXML_CLASS_LIST) {
|
|
strcpy(buf, js_XMLList_str);
|
|
} else if (JSXML_HAS_NAME(elt)) {
|
|
JSXMLQName *qn = elt->name;
|
|
|
|
JS_snprintf(buf, sizeof buf, "%s::%s",
|
|
qn->uri ? JS_GetStringBytes(qn->uri) : "*",
|
|
JS_GetStringBytes(qn->localName));
|
|
} else {
|
|
JSString *str = elt->xml_value;
|
|
size_t srclen = JSSTRING_LENGTH(str);
|
|
size_t dstlen = sizeof buf;
|
|
|
|
if (srclen >= sizeof buf / 6)
|
|
srclen = sizeof buf / 6 - 1;
|
|
js_DeflateStringToBuffer(cx, JSSTRING_CHARS(str), srclen,
|
|
buf, &dstlen);
|
|
}
|
|
#endif
|
|
GC_MARK(cx, elt, buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* js_XMLObjectOps.newObjectMap == js_NewObjectMap, so XML objects appear to
|
|
* be native. Therefore, xml_lookupProperty must return a valid JSProperty
|
|
* pointer parameter via *propp to signify "property found". Since the only
|
|
* call to xml_lookupProperty is via OBJ_LOOKUP_PROPERTY, and then only from
|
|
* js_FindXMLProperty (in this file), js_FindProperty (in jsobj.c, called from
|
|
* jsinterp.c) or from JSOP_IN case in the interpreter, the only time we add a
|
|
* JSScopeProperty here is when an unqualified name or XML name is being
|
|
* accessed or when "name in xml" is called.
|
|
*
|
|
* This scope property keeps the JSOP_NAME code in js_Interpret happy by
|
|
* giving it an sprop with (getter, setter) == (GetProperty, PutProperty).
|
|
*
|
|
* NB: xml_deleteProperty must take care to remove any property added here.
|
|
*
|
|
* FIXME This clashes with the function namespace implementation which also
|
|
* uses native properties. Effectively after xml_lookupProperty any property
|
|
* stored previously using assignments to xml.function::name will be removed.
|
|
* We partially workaround the problem in js_GetXMLFunction. There we take
|
|
* advantage of the fact that typically function:: is used to access the
|
|
* functions from XML.prototype. So when js_GetProperty returns a non-function
|
|
* property, we assume that it represents the result of GetProperty setter
|
|
* hiding the function and use an extra prototype chain lookup to recover it.
|
|
* For a proper solution see bug 355257.
|
|
*/
|
|
static JSBool
|
|
xml_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
|
|
JSProperty **propp)
|
|
{
|
|
JSScopeProperty *sprop;
|
|
|
|
if (!HasProperty(cx, obj, ID_TO_VALUE(id), objp, propp))
|
|
return JS_FALSE;
|
|
|
|
if (*propp == FOUND_XML_PROPERTY) {
|
|
sprop = js_AddNativeProperty(cx, obj, id, GetProperty, PutProperty,
|
|
SPROP_INVALID_SLOT, JSPROP_ENUMERATE,
|
|
0, 0);
|
|
if (!sprop)
|
|
return JS_FALSE;
|
|
|
|
JS_LOCK_OBJ(cx, obj);
|
|
*objp = obj;
|
|
*propp = (JSProperty *) sprop;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
|
|
JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
|
|
JSProperty **propp)
|
|
{
|
|
if (VALUE_IS_FUNCTION(cx, value) || getter || setter ||
|
|
(attrs & JSPROP_ENUMERATE) == 0 ||
|
|
(attrs & (JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED))) {
|
|
return js_DefineProperty(cx, obj, id, value, getter, setter, attrs,
|
|
propp);
|
|
}
|
|
|
|
if (!PutProperty(cx, obj, ID_TO_VALUE(id), &value))
|
|
return JS_FALSE;
|
|
if (propp)
|
|
*propp = NULL;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
|
|
{
|
|
if (id == JS_DEFAULT_XML_NAMESPACE_ID) {
|
|
*vp = JSVAL_VOID;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
return GetProperty(cx, obj, ID_TO_VALUE(id), vp);
|
|
}
|
|
|
|
static JSBool
|
|
xml_setProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
|
|
{
|
|
return PutProperty(cx, obj, ID_TO_VALUE(id), vp);
|
|
}
|
|
|
|
static JSBool
|
|
FoundProperty(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
|
|
JSBool *foundp)
|
|
{
|
|
JSObject *pobj;
|
|
|
|
if (prop) {
|
|
*foundp = JS_TRUE;
|
|
} else {
|
|
if (!HasProperty(cx, obj, ID_TO_VALUE(id), &pobj, &prop))
|
|
return JS_FALSE;
|
|
if (prop)
|
|
DROP_PROPERTY(cx, pobj, prop);
|
|
*foundp = (prop != NULL);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_getAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
|
|
uintN *attrsp)
|
|
{
|
|
JSBool found;
|
|
|
|
if (!FoundProperty(cx, obj, id, prop, &found))
|
|
return JS_FALSE;
|
|
*attrsp = found ? JSPROP_ENUMERATE : 0;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_setAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
|
|
uintN *attrsp)
|
|
{
|
|
JSBool found;
|
|
|
|
if (!FoundProperty(cx, obj, id, prop, &found))
|
|
return JS_FALSE;
|
|
if (found) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_CANT_SET_XML_ATTRS);
|
|
}
|
|
return !found;
|
|
}
|
|
|
|
static JSBool
|
|
xml_deleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval)
|
|
{
|
|
/*
|
|
* If this object has its own (mutable) scope, and if id isn't an index,
|
|
* then we may have added a property to the scope in xml_lookupProperty
|
|
* for it to return to mean "found" and to provide a handle for access
|
|
* operations to call the property's getter or setter. The property also
|
|
* helps speed up unqualified accesses via the property cache, avoiding
|
|
* what amount to two HasProperty searches.
|
|
*
|
|
* But now it's time to remove any such property, to purge the property
|
|
* cache and remove the scope entry.
|
|
*/
|
|
if (OBJ_SCOPE(obj)->object == obj && !JSID_IS_INT(id)) {
|
|
if (!js_DeleteProperty(cx, obj, id, rval))
|
|
return JS_FALSE;
|
|
}
|
|
|
|
return DeleteProperty(cx, obj, ID_TO_VALUE(id), rval);
|
|
}
|
|
|
|
static JSBool
|
|
xml_defaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp)
|
|
{
|
|
JSXML *xml;
|
|
|
|
if (hint == JSTYPE_OBJECT) {
|
|
/* Called from for..in code in js_Interpret: return an XMLList. */
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
if (xml->xml_class != JSXML_CLASS_LIST) {
|
|
obj = ToXMLList(cx, OBJECT_TO_JSVAL(obj));
|
|
if (!obj)
|
|
return JS_FALSE;
|
|
}
|
|
*vp = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
return JS_CallFunctionName(cx, obj, js_toString_str, 0, NULL, vp);
|
|
}
|
|
|
|
static JSBool
|
|
xml_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
|
|
jsval *statep, jsid *idp)
|
|
{
|
|
JSXML *xml;
|
|
uint32 length, index;
|
|
JSXMLArrayCursor *cursor;
|
|
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
length = JSXML_LENGTH(xml);
|
|
|
|
switch (enum_op) {
|
|
case JSENUMERATE_INIT:
|
|
if (length == 0) {
|
|
cursor = NULL;
|
|
} else {
|
|
cursor = (JSXMLArrayCursor *) JS_malloc(cx, sizeof *cursor);
|
|
if (!cursor)
|
|
return JS_FALSE;
|
|
XMLArrayCursorInit(cursor, &xml->xml_kids);
|
|
}
|
|
*statep = PRIVATE_TO_JSVAL(cursor);
|
|
if (idp)
|
|
*idp = INT_TO_JSID(length);
|
|
break;
|
|
|
|
case JSENUMERATE_NEXT:
|
|
cursor = JSVAL_TO_PRIVATE(*statep);
|
|
if (cursor && cursor->array && (index = cursor->index) < length) {
|
|
*idp = INT_TO_JSID(index);
|
|
cursor->index = index + 1;
|
|
break;
|
|
}
|
|
/* FALL THROUGH */
|
|
|
|
case JSENUMERATE_DESTROY:
|
|
cursor = JSVAL_TO_PRIVATE(*statep);
|
|
if (cursor) {
|
|
XMLArrayCursorFinish(cursor);
|
|
JS_free(cx, cursor);
|
|
}
|
|
*statep = JSVAL_NULL;
|
|
break;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
|
|
{
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static uint32
|
|
xml_mark(JSContext *cx, JSObject *obj, void *arg)
|
|
{
|
|
JSXML *xml;
|
|
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
GC_MARK(cx, xml, "private");
|
|
return js_Mark(cx, obj, NULL);
|
|
}
|
|
|
|
static void
|
|
xml_clear(JSContext *cx, JSObject *obj)
|
|
{
|
|
}
|
|
|
|
static JSBool
|
|
HasSimpleContent(JSXML *xml)
|
|
{
|
|
JSXML *kid;
|
|
JSBool simple;
|
|
uint32 i, n;
|
|
|
|
again:
|
|
switch (xml->xml_class) {
|
|
case JSXML_CLASS_COMMENT:
|
|
case JSXML_CLASS_PROCESSING_INSTRUCTION:
|
|
return JS_FALSE;
|
|
case JSXML_CLASS_LIST:
|
|
if (xml->xml_kids.length == 0)
|
|
return JS_TRUE;
|
|
if (xml->xml_kids.length == 1) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
|
|
if (kid) {
|
|
xml = kid;
|
|
goto again;
|
|
}
|
|
}
|
|
/* FALL THROUGH */
|
|
default:
|
|
simple = JS_TRUE;
|
|
for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
simple = JS_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
return simple;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 11.2.2.1 Step 3(d) onward.
|
|
*/
|
|
static JSObject *
|
|
xml_getMethod(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
|
|
{
|
|
JSTempValueRooter tvr;
|
|
|
|
JS_ASSERT(JS_InstanceOf(cx, obj, &js_XMLClass, NULL));
|
|
|
|
/*
|
|
* As our callers have a bad habit of passing a pointer to an unrooted
|
|
* local value as vp, we use a proper root here.
|
|
*/
|
|
JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr);
|
|
if (!js_GetXMLFunction(cx, obj, id, &tvr.u.value))
|
|
obj = NULL;
|
|
*vp = tvr.u.value;
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
return obj;
|
|
}
|
|
|
|
static JSBool
|
|
xml_setMethod(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
|
|
{
|
|
return js_SetProperty(cx, obj, id, vp);
|
|
}
|
|
|
|
static JSBool
|
|
xml_enumerateValues(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
|
|
jsval *statep, jsid *idp, jsval *vp)
|
|
{
|
|
JSXML *xml, *kid;
|
|
uint32 length, index;
|
|
JSXMLArrayCursor *cursor;
|
|
JSObject *kidobj;
|
|
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
length = JSXML_LENGTH(xml);
|
|
JS_ASSERT(INT_FITS_IN_JSVAL(length));
|
|
|
|
switch (enum_op) {
|
|
case JSENUMERATE_INIT:
|
|
if (length == 0) {
|
|
cursor = NULL;
|
|
} else {
|
|
cursor = (JSXMLArrayCursor *) JS_malloc(cx, sizeof *cursor);
|
|
if (!cursor)
|
|
return JS_FALSE;
|
|
XMLArrayCursorInit(cursor, &xml->xml_kids);
|
|
}
|
|
*statep = PRIVATE_TO_JSVAL(cursor);
|
|
if (idp)
|
|
*idp = INT_TO_JSID(length);
|
|
if (vp)
|
|
*vp = JSVAL_VOID;
|
|
break;
|
|
|
|
case JSENUMERATE_NEXT:
|
|
cursor = JSVAL_TO_PRIVATE(*statep);
|
|
if (cursor && cursor->array && (index = cursor->index) < length) {
|
|
while (!(kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML))) {
|
|
if (++index == length)
|
|
goto destroy;
|
|
}
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
return JS_FALSE;
|
|
JS_ASSERT(INT_FITS_IN_JSVAL(index));
|
|
*idp = INT_TO_JSID(index);
|
|
*vp = OBJECT_TO_JSVAL(kidobj);
|
|
cursor->index = index + 1;
|
|
break;
|
|
}
|
|
/* FALL THROUGH */
|
|
|
|
case JSENUMERATE_DESTROY:
|
|
cursor = JSVAL_TO_PRIVATE(*statep);
|
|
if (cursor) {
|
|
destroy:
|
|
XMLArrayCursorFinish(cursor);
|
|
JS_free(cx, cursor);
|
|
}
|
|
*statep = JSVAL_NULL;
|
|
break;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
|
|
{
|
|
JSXML *xml, *vxml;
|
|
JSObject *vobj;
|
|
JSBool ok;
|
|
JSString *str, *vstr;
|
|
jsdouble d, d2;
|
|
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
vxml = NULL;
|
|
if (!JSVAL_IS_PRIMITIVE(v)) {
|
|
vobj = JSVAL_TO_OBJECT(v);
|
|
if (OBJECT_IS_XML(cx, vobj))
|
|
vxml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
}
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
ok = Equals(cx, xml, v, bp);
|
|
} else if (vxml) {
|
|
if (vxml->xml_class == JSXML_CLASS_LIST) {
|
|
ok = Equals(cx, vxml, OBJECT_TO_JSVAL(obj), bp);
|
|
} else {
|
|
if (((xml->xml_class == JSXML_CLASS_TEXT ||
|
|
xml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
|
|
HasSimpleContent(vxml)) ||
|
|
((vxml->xml_class == JSXML_CLASS_TEXT ||
|
|
vxml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
|
|
HasSimpleContent(xml))) {
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (ok) {
|
|
str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
|
|
vstr = js_ValueToString(cx, v);
|
|
ok = str && vstr;
|
|
if (ok)
|
|
*bp = js_EqualStrings(str, vstr);
|
|
js_LeaveLocalRootScope(cx);
|
|
}
|
|
} else {
|
|
ok = XMLEquals(cx, xml, vxml, bp);
|
|
}
|
|
}
|
|
} else {
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (ok) {
|
|
if (HasSimpleContent(xml)) {
|
|
str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
|
|
vstr = js_ValueToString(cx, v);
|
|
ok = str && vstr;
|
|
if (ok)
|
|
*bp = js_EqualStrings(str, vstr);
|
|
} else if (JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v)) {
|
|
str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
|
|
if (!str) {
|
|
ok = JS_FALSE;
|
|
} else if (JSVAL_IS_STRING(v)) {
|
|
*bp = js_EqualStrings(str, JSVAL_TO_STRING(v));
|
|
} else {
|
|
ok = js_ValueToNumber(cx, STRING_TO_JSVAL(str), &d);
|
|
if (ok) {
|
|
d2 = JSVAL_IS_INT(v) ? JSVAL_TO_INT(v)
|
|
: *JSVAL_TO_DOUBLE(v);
|
|
*bp = JSDOUBLE_COMPARE(d, ==, d2, JS_FALSE);
|
|
}
|
|
}
|
|
} else {
|
|
*bp = JS_FALSE;
|
|
}
|
|
js_LeaveLocalRootScope(cx);
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static JSBool
|
|
xml_concatenate(JSContext *cx, JSObject *obj, jsval v, jsval *vp)
|
|
{
|
|
JSBool ok;
|
|
JSObject *listobj, *robj;
|
|
JSXML *list, *lxml, *rxml;
|
|
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
|
|
listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (!listobj) {
|
|
ok = JS_FALSE;
|
|
goto out;
|
|
}
|
|
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
lxml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
ok = Append(cx, list, lxml);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
if (VALUE_IS_XML(cx, v)) {
|
|
rxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
|
|
} else {
|
|
robj = ToXML(cx, v);
|
|
if (!robj) {
|
|
ok = JS_FALSE;
|
|
goto out;
|
|
}
|
|
rxml = (JSXML *) JS_GetPrivate(cx, robj);
|
|
}
|
|
ok = Append(cx, list, rxml);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
*vp = OBJECT_TO_JSVAL(listobj);
|
|
out:
|
|
js_LeaveLocalRootScopeWithResult(cx, *vp);
|
|
return ok;
|
|
}
|
|
|
|
/* Use js_NewObjectMap so XML objects satisfy OBJ_IS_NATIVE tests. */
|
|
JS_FRIEND_DATA(JSXMLObjectOps) js_XMLObjectOps = {
|
|
{ js_NewObjectMap, js_DestroyObjectMap,
|
|
xml_lookupProperty, xml_defineProperty,
|
|
xml_getProperty, xml_setProperty,
|
|
xml_getAttributes, xml_setAttributes,
|
|
xml_deleteProperty, xml_defaultValue,
|
|
xml_enumerate, js_CheckAccess,
|
|
NULL, NULL,
|
|
NULL, NULL,
|
|
NULL, xml_hasInstance,
|
|
js_SetProtoOrParent, js_SetProtoOrParent,
|
|
xml_mark, xml_clear,
|
|
NULL, NULL },
|
|
xml_getMethod, xml_setMethod,
|
|
xml_enumerateValues, xml_equality,
|
|
xml_concatenate
|
|
};
|
|
|
|
static JSObjectOps *
|
|
xml_getObjectOps(JSContext *cx, JSClass *clasp)
|
|
{
|
|
return &js_XMLObjectOps.base;
|
|
}
|
|
|
|
JS_FRIEND_DATA(JSClass) js_XMLClass = {
|
|
js_XML_str,
|
|
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_XML),
|
|
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, xml_finalize,
|
|
xml_getObjectOps, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL
|
|
};
|
|
|
|
static JSXML *
|
|
StartNonListXMLMethod(JSContext *cx, JSObject **objp, jsval *argv)
|
|
{
|
|
JSXML *xml;
|
|
JSFunction *fun;
|
|
|
|
JS_ASSERT(VALUE_IS_FUNCTION(cx, argv[-2]));
|
|
|
|
xml = (JSXML *) JS_GetInstancePrivate(cx, *objp, &js_XMLClass, argv);
|
|
if (!xml || xml->xml_class != JSXML_CLASS_LIST)
|
|
return xml;
|
|
|
|
if (xml->xml_kids.length == 1) {
|
|
xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
|
|
if (xml) {
|
|
*objp = js_GetXMLObject(cx, xml);
|
|
if (!*objp)
|
|
return NULL;
|
|
argv[-1] = OBJECT_TO_JSVAL(*objp);
|
|
return xml;
|
|
}
|
|
}
|
|
|
|
fun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(argv[-2]));
|
|
if (fun) {
|
|
char numBuf[12];
|
|
JS_snprintf(numBuf, sizeof numBuf, "%u", xml->xml_kids.length);
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_NON_LIST_XML_METHOD,
|
|
JS_GetFunctionName(fun), numBuf);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#define XML_METHOD_PROLOG \
|
|
JS_BEGIN_MACRO \
|
|
xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, argv); \
|
|
if (!xml) \
|
|
return JS_FALSE; \
|
|
JS_END_MACRO
|
|
|
|
#define NON_LIST_XML_METHOD_PROLOG \
|
|
JS_BEGIN_MACRO \
|
|
xml = StartNonListXMLMethod(cx, &obj, argv); \
|
|
if (!xml) \
|
|
return JS_FALSE; \
|
|
JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST); \
|
|
JS_END_MACRO
|
|
|
|
static JSBool
|
|
xml_addNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
JSXMLNamespace *ns;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (xml->xml_class != JSXML_CLASS_ELEMENT)
|
|
goto done;
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
|
|
if (!NamespaceHelper(cx, NULL, 1, argv, rval))
|
|
return JS_FALSE;
|
|
JS_ASSERT(!JSVAL_IS_PRIMITIVE(*rval));
|
|
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*rval));
|
|
if (!AddInScopeNamespace(cx, xml, ns))
|
|
return JS_FALSE;
|
|
ns->declared = JS_TRUE;
|
|
|
|
done:
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_appendChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *vxml;
|
|
jsval name, v;
|
|
JSObject *vobj;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
|
|
if (!js_GetAnyName(cx, &name))
|
|
return JS_FALSE;
|
|
|
|
if (!GetProperty(cx, obj, name, &v))
|
|
return JS_FALSE;
|
|
|
|
JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
|
|
vobj = JSVAL_TO_OBJECT(v);
|
|
JS_ASSERT(OBJECT_IS_XML(cx, vobj));
|
|
vxml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
JS_ASSERT(vxml->xml_class == JSXML_CLASS_LIST);
|
|
|
|
if (!IndexToIdVal(cx, vxml->xml_kids.length, &name))
|
|
return JS_FALSE;
|
|
if (!PutProperty(cx, JSVAL_TO_OBJECT(v), name, &argv[0]))
|
|
return JS_FALSE;
|
|
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_attribute(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXMLQName *qn;
|
|
|
|
qn = ToAttributeName(cx, argv[0]);
|
|
if (!qn)
|
|
return JS_FALSE;
|
|
argv[0] = OBJECT_TO_JSVAL(qn->object); /* local root */
|
|
return GetProperty(cx, obj, argv[0], rval);
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_attributes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
jsval name;
|
|
JSXMLQName *qn;
|
|
JSTempValueRooter tvr;
|
|
JSBool ok;
|
|
|
|
name = ATOM_KEY(cx->runtime->atomState.starAtom);
|
|
qn = ToAttributeName(cx, name);
|
|
if (!qn)
|
|
return JS_FALSE;
|
|
name = OBJECT_TO_JSVAL(qn->object);
|
|
JS_PUSH_SINGLE_TEMP_ROOT(cx, name, &tvr);
|
|
ok = GetProperty(cx, obj, name, rval);
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
return ok;
|
|
}
|
|
|
|
static JSXML *
|
|
xml_list_helper(JSContext *cx, JSXML *xml, jsval *rval)
|
|
{
|
|
JSObject *listobj;
|
|
JSXML *list;
|
|
|
|
listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (!listobj)
|
|
return NULL;
|
|
|
|
*rval = OBJECT_TO_JSVAL(listobj);
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
list->xml_target = xml;
|
|
return list;
|
|
}
|
|
|
|
static JSBool
|
|
xml_child_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval name,
|
|
jsval *rval)
|
|
{
|
|
uint32 index;
|
|
JSXML *kid;
|
|
JSObject *kidobj;
|
|
|
|
/* ECMA-357 13.4.4.6 */
|
|
JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
|
|
|
|
if (js_IdIsIndex(name, &index)) {
|
|
if (index >= JSXML_LENGTH(xml)) {
|
|
*rval = JSVAL_VOID;
|
|
} else {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
|
|
if (!kid) {
|
|
*rval = JSVAL_VOID;
|
|
} else {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(kidobj);
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
return GetProperty(cx, obj, name, rval);
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_child(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml, *list, *kid, *vxml;
|
|
JSXMLArrayCursor cursor;
|
|
jsval name, v;
|
|
JSObject *kidobj;
|
|
|
|
XML_METHOD_PROLOG;
|
|
name = argv[0];
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
/* ECMA-357 13.5.4.4 */
|
|
list = xml_list_helper(cx, xml, rval);
|
|
if (!list)
|
|
return JS_FALSE;
|
|
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
break;
|
|
if (!xml_child_helper(cx, kidobj, kid, name, &v))
|
|
break;
|
|
if (JSVAL_IS_VOID(v)) {
|
|
/* The property didn't exist in this kid. */
|
|
continue;
|
|
}
|
|
|
|
JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
|
|
vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
|
|
if ((!JSXML_HAS_KIDS(vxml) || vxml->xml_kids.length != 0) &&
|
|
!Append(cx, list, vxml)) {
|
|
break;
|
|
}
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
return !kid;
|
|
}
|
|
|
|
/* ECMA-357 Edition 2 13.3.4.6 (note 13.3, not 13.4 as in Edition 1). */
|
|
if (!xml_child_helper(cx, obj, xml, name, rval))
|
|
return JS_FALSE;
|
|
if (JSVAL_IS_VOID(*rval) && !xml_list_helper(cx, xml, rval))
|
|
return JS_FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_childIndex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *parent;
|
|
uint32 i, n;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
parent = xml->parent;
|
|
if (!parent || xml->xml_class == JSXML_CLASS_ATTRIBUTE) {
|
|
*rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN);
|
|
return JS_TRUE;
|
|
}
|
|
for (i = 0, n = JSXML_LENGTH(parent); i < n; i++) {
|
|
if (XMLARRAY_MEMBER(&parent->xml_kids, i, JSXML) == xml)
|
|
break;
|
|
}
|
|
JS_ASSERT(i < n);
|
|
return js_NewNumberValue(cx, i, rval);
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_children(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
jsval name;
|
|
|
|
name = ATOM_KEY(cx->runtime->atomState.starAtom);
|
|
return GetProperty(cx, obj, name, rval);
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_comments(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *list, *kid, *vxml;
|
|
JSBool ok;
|
|
uint32 i, n;
|
|
JSObject *kidobj;
|
|
jsval v;
|
|
|
|
XML_METHOD_PROLOG;
|
|
list = xml_list_helper(cx, xml, rval);
|
|
if (!list)
|
|
return JS_FALSE;
|
|
|
|
ok = JS_TRUE;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
/* 13.5.4.6 Step 2. */
|
|
for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (!ok)
|
|
break;
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (kidobj) {
|
|
ok = xml_comments(cx, kidobj, argc, argv, &v);
|
|
} else {
|
|
ok = JS_FALSE;
|
|
v = JSVAL_NULL;
|
|
}
|
|
js_LeaveLocalRootScopeWithResult(cx, v);
|
|
if (!ok)
|
|
break;
|
|
vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
|
|
if (JSXML_LENGTH(vxml) != 0) {
|
|
ok = Append(cx, list, vxml);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* 13.4.4.9 Step 2. */
|
|
for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_COMMENT) {
|
|
ok = Append(cx, list, kid);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_contains(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *kid;
|
|
jsval value;
|
|
JSBool eq;
|
|
JSXMLArrayCursor cursor;
|
|
JSObject *kidobj;
|
|
|
|
XML_METHOD_PROLOG;
|
|
value = argv[0];
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
eq = JS_FALSE;
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj || !xml_equality(cx, kidobj, value, &eq))
|
|
break;
|
|
if (eq)
|
|
break;
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (kid && !eq)
|
|
return JS_FALSE;
|
|
} else {
|
|
if (!xml_equality(cx, obj, value, &eq))
|
|
return JS_FALSE;
|
|
}
|
|
*rval = BOOLEAN_TO_JSVAL(eq);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_copy(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml, *copy;
|
|
|
|
XML_METHOD_PROLOG;
|
|
copy = DeepCopy(cx, xml, NULL, 0);
|
|
if (!copy)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(copy->object);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_descendants(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *list;
|
|
jsval name;
|
|
|
|
XML_METHOD_PROLOG;
|
|
name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
|
|
list = Descendants(cx, xml, name);
|
|
if (!list)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(list->object);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_elements(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *list, *kid, *vxml;
|
|
jsval name, v;
|
|
JSXMLQName *nameqn;
|
|
jsid funid;
|
|
JSBool ok;
|
|
JSXMLArrayCursor cursor;
|
|
JSObject *kidobj;
|
|
uint32 i, n;
|
|
|
|
XML_METHOD_PROLOG;
|
|
name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
|
|
nameqn = ToXMLName(cx, name, &funid);
|
|
if (!nameqn)
|
|
return JS_FALSE;
|
|
if (argc)
|
|
argv[0] = OBJECT_TO_JSVAL(nameqn->object);
|
|
|
|
list = xml_list_helper(cx, xml, rval);
|
|
if (!list)
|
|
return JS_FALSE;
|
|
if (funid)
|
|
return JS_TRUE;
|
|
|
|
list->xml_targetprop = nameqn;
|
|
ok = JS_TRUE;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
/* 13.5.4.6 */
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (!ok)
|
|
break;
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (kidobj) {
|
|
ok = xml_elements(cx, kidobj, argc, argv, &v);
|
|
} else {
|
|
ok = JS_FALSE;
|
|
v = JSVAL_NULL;
|
|
}
|
|
js_LeaveLocalRootScopeWithResult(cx, v);
|
|
if (!ok)
|
|
break;
|
|
vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
|
|
if (JSXML_LENGTH(vxml) != 0) {
|
|
ok = Append(cx, list, vxml);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
} else {
|
|
for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_ELEMENT &&
|
|
MatchElemName(nameqn, kid)) {
|
|
ok = Append(cx, list, kid);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_hasOwnProperty(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
jsval name;
|
|
JSObject *pobj;
|
|
JSProperty *prop;
|
|
|
|
if (!JS_InstanceOf(cx, obj, &js_XMLClass, argv))
|
|
return JS_FALSE;
|
|
|
|
name = argv[0];
|
|
if (!HasProperty(cx, obj, name, &pobj, &prop))
|
|
return JS_FALSE;
|
|
if (!prop) {
|
|
return js_HasOwnPropertyHelper(cx, obj, js_LookupProperty, argc, argv,
|
|
rval);
|
|
}
|
|
DROP_PROPERTY(cx, pobj, prop);
|
|
*rval = JSVAL_TRUE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_hasComplexContent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *kid;
|
|
JSObject *kidobj;
|
|
uint32 i, n;
|
|
|
|
XML_METHOD_PROLOG;
|
|
again:
|
|
switch (xml->xml_class) {
|
|
case JSXML_CLASS_ATTRIBUTE:
|
|
case JSXML_CLASS_COMMENT:
|
|
case JSXML_CLASS_PROCESSING_INSTRUCTION:
|
|
case JSXML_CLASS_TEXT:
|
|
*rval = JSVAL_FALSE;
|
|
break;
|
|
case JSXML_CLASS_LIST:
|
|
if (xml->xml_kids.length == 0) {
|
|
*rval = JSVAL_TRUE;
|
|
} else if (xml->xml_kids.length == 1) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
|
|
if (kid) {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
return JS_FALSE;
|
|
obj = kidobj;
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
goto again;
|
|
}
|
|
}
|
|
/* FALL THROUGH */
|
|
default:
|
|
*rval = JSVAL_FALSE;
|
|
for (i = 0, n = xml->xml_kids.length; i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
*rval = JSVAL_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_hasSimpleContent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
|
|
XML_METHOD_PROLOG;
|
|
*rval = BOOLEAN_TO_JSVAL(HasSimpleContent(xml));
|
|
return JS_TRUE;
|
|
}
|
|
|
|
typedef struct JSTempRootedNSArray {
|
|
JSTempValueRooter tvr;
|
|
JSXMLArray array;
|
|
jsval value; /* extra root for temporaries */
|
|
} JSTempRootedNSArray;
|
|
|
|
JS_STATIC_DLL_CALLBACK(void)
|
|
mark_temp_ns_array(JSContext *cx, JSTempValueRooter *tvr)
|
|
{
|
|
JSTempRootedNSArray *tmp = (JSTempRootedNSArray *)tvr;
|
|
|
|
namespace_mark_vector(cx,
|
|
(JSXMLNamespace **)tmp->array.vector,
|
|
tmp->array.length);
|
|
XMLArrayCursorMark(cx, tmp->array.cursors);
|
|
if (JSVAL_IS_GCTHING(tmp->value))
|
|
GC_MARK(cx, JSVAL_TO_GCTHING(tmp->value), "temp_ns_array_value");
|
|
}
|
|
|
|
static void
|
|
InitTempNSArray(JSContext *cx, JSTempRootedNSArray *tmp)
|
|
{
|
|
XMLArrayInit(cx, &tmp->array, 0);
|
|
tmp->value = JSVAL_NULL;
|
|
JS_PUSH_TEMP_ROOT_MARKER(cx, mark_temp_ns_array, &tmp->tvr);
|
|
}
|
|
|
|
static void
|
|
FinishTempNSArray(JSContext *cx, JSTempRootedNSArray *tmp)
|
|
{
|
|
JS_ASSERT(tmp->tvr.u.marker == mark_temp_ns_array);
|
|
JS_POP_TEMP_ROOT(cx, &tmp->tvr);
|
|
XMLArrayFinish(cx, &tmp->array);
|
|
}
|
|
|
|
/*
|
|
* Populate a new JS array with elements of JSTempRootedNSArray.array and
|
|
* place the result into rval. rval must point to a rooted location.
|
|
*/
|
|
static JSBool
|
|
TempNSArrayToJSArray(JSContext *cx, JSTempRootedNSArray *tmp, jsval *rval)
|
|
{
|
|
JSObject *arrayobj;
|
|
uint32 i, n;
|
|
JSXMLNamespace *ns;
|
|
JSObject *nsobj;
|
|
|
|
arrayobj = js_NewArrayObject(cx, 0, NULL);
|
|
if (!arrayobj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(arrayobj);
|
|
for (i = 0, n = tmp->array.length; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(&tmp->array, i, JSXMLNamespace);
|
|
if (!ns)
|
|
continue;
|
|
nsobj = js_GetXMLNamespaceObject(cx, ns);
|
|
if (!nsobj)
|
|
return JS_FALSE;
|
|
tmp->value = OBJECT_TO_JSVAL(nsobj);
|
|
if (!OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(i), &tmp->value))
|
|
return JS_FALSE;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
FindInScopeNamespaces(JSContext *cx, JSXML *xml, JSXMLArray *nsarray)
|
|
{
|
|
uint32 length, i, j, n;
|
|
JSXMLNamespace *ns, *ns2;
|
|
|
|
length = nsarray->length;
|
|
do {
|
|
if (xml->xml_class != JSXML_CLASS_ELEMENT)
|
|
continue;
|
|
for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
|
|
if (!ns)
|
|
continue;
|
|
|
|
for (j = 0; j < length; j++) {
|
|
ns2 = XMLARRAY_MEMBER(nsarray, j, JSXMLNamespace);
|
|
if (ns2 &&
|
|
((ns2->prefix && ns->prefix)
|
|
? js_EqualStrings(ns2->prefix, ns->prefix)
|
|
: js_EqualStrings(ns2->uri, ns->uri))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j == length) {
|
|
if (!XMLARRAY_APPEND(cx, nsarray, ns))
|
|
return JS_FALSE;
|
|
++length;
|
|
}
|
|
}
|
|
} while ((xml = xml->parent) != NULL);
|
|
JS_ASSERT(length == nsarray->length);
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_inScopeNamespaces(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
JSTempRootedNSArray namespaces;
|
|
JSBool ok;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
|
|
InitTempNSArray(cx, &namespaces);
|
|
ok = FindInScopeNamespaces(cx, xml, &namespaces.array) &&
|
|
TempNSArrayToJSArray(cx, &namespaces, rval);
|
|
FinishTempNSArray(cx, &namespaces);
|
|
return ok;
|
|
}
|
|
|
|
static JSBool
|
|
xml_insertChildAfter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *kid;
|
|
jsval arg;
|
|
uint32 i;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (!JSXML_HAS_KIDS(xml))
|
|
return JS_TRUE;
|
|
|
|
arg = argv[0];
|
|
if (JSVAL_IS_NULL(arg)) {
|
|
kid = NULL;
|
|
i = 0;
|
|
} else {
|
|
if (!VALUE_IS_XML(cx, arg))
|
|
return JS_TRUE;
|
|
kid = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(arg));
|
|
i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
|
|
if (i == XML_NOT_FOUND)
|
|
return JS_TRUE;
|
|
++i;
|
|
}
|
|
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
if (!Insert(cx, xml, i, argv[1]))
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_insertChildBefore(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *kid;
|
|
jsval arg;
|
|
uint32 i;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (!JSXML_HAS_KIDS(xml))
|
|
return JS_TRUE;
|
|
|
|
arg = argv[0];
|
|
if (JSVAL_IS_NULL(arg)) {
|
|
kid = NULL;
|
|
i = xml->xml_kids.length;
|
|
} else {
|
|
if (!VALUE_IS_XML(cx, arg))
|
|
return JS_TRUE;
|
|
kid = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(arg));
|
|
i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
|
|
if (i == XML_NOT_FOUND)
|
|
return JS_TRUE;
|
|
}
|
|
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
if (!Insert(cx, xml, i, argv[1]))
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_length(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
|
|
XML_METHOD_PROLOG;
|
|
if (xml->xml_class != JSXML_CLASS_LIST) {
|
|
*rval = JSVAL_ONE;
|
|
} else {
|
|
if (!js_NewNumberValue(cx, xml->xml_kids.length, rval))
|
|
return JS_FALSE;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_localName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
*rval = xml->name ? STRING_TO_JSVAL(xml->name->localName) : JSVAL_NULL;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_name(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
JSObject *nameobj;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (!xml->name) {
|
|
*rval = JSVAL_NULL;
|
|
} else {
|
|
nameobj = js_GetXMLQNameObject(cx, xml->name);
|
|
if (!nameobj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(nameobj);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
JSString *prefix;
|
|
JSTempRootedNSArray inScopeNSes;
|
|
JSBool ok;
|
|
jsuint i, length;
|
|
JSXMLNamespace *ns;
|
|
JSObject *nsobj;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (argc == 0 && !JSXML_HAS_NAME(xml)) {
|
|
*rval = JSVAL_NULL;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
if (argc == 0) {
|
|
prefix = NULL;
|
|
} else {
|
|
prefix = js_ValueToString(cx, argv[0]);
|
|
if (!prefix)
|
|
return JS_FALSE;
|
|
argv[0] = STRING_TO_JSVAL(prefix); /* local root */
|
|
}
|
|
|
|
/* After this point the control must flow through label out. */
|
|
InitTempNSArray(cx, &inScopeNSes);
|
|
ok = FindInScopeNamespaces(cx, xml, &inScopeNSes.array);
|
|
if (!ok)
|
|
goto out;
|
|
|
|
if (!prefix) {
|
|
ns = GetNamespace(cx, xml->name, &inScopeNSes.array);
|
|
if (!ns) {
|
|
ok = JS_FALSE;
|
|
goto out;
|
|
}
|
|
} else {
|
|
ns = NULL;
|
|
for (i = 0, length = inScopeNSes.array.length; i < length; i++) {
|
|
ns = XMLARRAY_MEMBER(&inScopeNSes.array, i, JSXMLNamespace);
|
|
if (ns && ns->prefix && js_EqualStrings(ns->prefix, prefix))
|
|
break;
|
|
ns = NULL;
|
|
}
|
|
}
|
|
|
|
if (!ns) {
|
|
*rval = JSVAL_VOID;
|
|
} else {
|
|
nsobj = js_GetXMLNamespaceObject(cx, ns);
|
|
if (!nsobj) {
|
|
ok = JS_FALSE;
|
|
goto out;
|
|
}
|
|
*rval = OBJECT_TO_JSVAL(nsobj);
|
|
}
|
|
|
|
out:
|
|
FinishTempNSArray(cx, &inScopeNSes);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_namespaceDeclarations(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *yml;
|
|
JSBool ok;
|
|
JSTempRootedNSArray ancestors, declared;
|
|
uint32 i, n;
|
|
JSXMLNamespace *ns;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (JSXML_HAS_VALUE(xml))
|
|
return JS_TRUE;
|
|
|
|
/* From here, control flow must goto out to finish these arrays. */
|
|
ok = JS_TRUE;
|
|
InitTempNSArray(cx, &ancestors);
|
|
InitTempNSArray(cx, &declared);
|
|
yml = xml;
|
|
|
|
while ((yml = yml->parent) != NULL) {
|
|
JS_ASSERT(yml->xml_class == JSXML_CLASS_ELEMENT);
|
|
for (i = 0, n = yml->xml_namespaces.length; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(&yml->xml_namespaces, i, JSXMLNamespace);
|
|
if (ns &&
|
|
!XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
|
|
ok = XMLARRAY_APPEND(cx, &ancestors.array, ns);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
|
|
if (!ns)
|
|
continue;
|
|
if (!ns->declared)
|
|
continue;
|
|
if (!XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
|
|
ok = XMLARRAY_APPEND(cx, &declared.array, ns);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ok = TempNSArrayToJSArray(cx, &declared, rval);
|
|
|
|
out:
|
|
/* Finishing must be in reverse order of initialization to follow LIFO. */
|
|
FinishTempNSArray(cx, &declared);
|
|
FinishTempNSArray(cx, &ancestors);
|
|
return ok;
|
|
}
|
|
|
|
static const char js_attribute_str[] = "attribute";
|
|
static const char js_text_str[] = "text";
|
|
|
|
/* Exported to jsgc.c #ifdef GC_MARK_DEBUG. */
|
|
const char *js_xml_class_str[] = {
|
|
"list",
|
|
"element",
|
|
js_attribute_str,
|
|
"processing-instruction",
|
|
js_text_str,
|
|
"comment"
|
|
};
|
|
|
|
static JSBool
|
|
xml_nodeKind(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
JSString *str;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
str = JS_InternString(cx, js_xml_class_str[xml->xml_class]);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
*rval = STRING_TO_JSVAL(str);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
NormalizingDelete(JSContext *cx, JSObject *obj, JSXML *xml, jsval id)
|
|
{
|
|
jsval junk;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST)
|
|
return DeleteProperty(cx, obj, id, &junk);
|
|
return DeleteByIndex(cx, xml, id, &junk);
|
|
}
|
|
|
|
/*
|
|
* Erratum? the testcase js/tests/e4x/XML/13.4.4.26.js wants all-whitespace
|
|
* text between tags to be removed by normalize.
|
|
*/
|
|
static JSBool
|
|
IsXMLSpace(JSString *str)
|
|
{
|
|
const jschar *cp, *end;
|
|
|
|
cp = JSSTRING_CHARS(str);
|
|
end = cp + JSSTRING_LENGTH(str);
|
|
while (cp < end) {
|
|
if (!JS_ISXMLSPACE(*cp))
|
|
return JS_FALSE;
|
|
++cp;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_normalize(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *kid, *kid2;
|
|
uint32 i, n;
|
|
JSObject *kidobj;
|
|
JSString *str;
|
|
jsval junk;
|
|
|
|
XML_METHOD_PROLOG;
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
if (!JSXML_HAS_KIDS(xml))
|
|
return JS_TRUE;
|
|
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
|
|
for (i = 0, n = xml->xml_kids.length; i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (!kid)
|
|
continue;
|
|
if (kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj || !xml_normalize(cx, kidobj, argc, argv, &junk))
|
|
return JS_FALSE;
|
|
} else if (kid->xml_class == JSXML_CLASS_TEXT) {
|
|
while (i + 1 < n &&
|
|
(kid2 = XMLARRAY_MEMBER(&xml->xml_kids, i + 1, JSXML)) &&
|
|
kid2->xml_class == JSXML_CLASS_TEXT) {
|
|
str = js_ConcatStrings(cx, kid->xml_value, kid2->xml_value);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
if (!NormalizingDelete(cx, obj, xml, INT_TO_JSVAL(i + 1)))
|
|
return JS_FALSE;
|
|
n = xml->xml_kids.length;
|
|
kid->xml_value = str;
|
|
}
|
|
if (IS_EMPTY(kid->xml_value) || IsXMLSpace(kid->xml_value)) {
|
|
if (!NormalizingDelete(cx, obj, xml, INT_TO_JSVAL(i)))
|
|
return JS_FALSE;
|
|
n = xml->xml_kids.length;
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_parent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml, *parent, *kid;
|
|
uint32 i, n;
|
|
JSObject *parentobj;
|
|
|
|
XML_METHOD_PROLOG;
|
|
parent = xml->parent;
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
*rval = JSVAL_VOID;
|
|
n = xml->xml_kids.length;
|
|
if (n == 0)
|
|
return JS_TRUE;
|
|
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
|
|
if (!kid)
|
|
return JS_TRUE;
|
|
parent = kid->parent;
|
|
for (i = 1; i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->parent != parent)
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
|
|
if (!parent) {
|
|
*rval = JSVAL_NULL;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
parentobj = js_GetXMLObject(cx, parent);
|
|
if (!parentobj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(parentobj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_processingInstructions(JSContext *cx, JSObject *obj, uintN argc,
|
|
jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml, *list, *kid, *vxml;
|
|
jsval name, v;
|
|
JSXMLQName *nameqn;
|
|
jsid funid;
|
|
JSBool ok;
|
|
JSXMLArrayCursor cursor;
|
|
JSObject *kidobj;
|
|
uint32 i, n;
|
|
|
|
XML_METHOD_PROLOG;
|
|
name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
|
|
nameqn = ToXMLName(cx, name, &funid);
|
|
if (!nameqn)
|
|
return JS_FALSE;
|
|
argv[0] = OBJECT_TO_JSVAL(nameqn->object);
|
|
|
|
list = xml_list_helper(cx, xml, rval);
|
|
if (!list)
|
|
return JS_FALSE;
|
|
if (funid)
|
|
return JS_TRUE;
|
|
|
|
list->xml_targetprop = nameqn;
|
|
ok = JS_TRUE;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
/* 13.5.4.17 Step 4 (misnumbered 9 -- Erratum?). */
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (!ok)
|
|
break;
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (kidobj) {
|
|
ok = xml_processingInstructions(cx, kidobj, argc, argv, &v);
|
|
} else {
|
|
ok = JS_FALSE;
|
|
v = JSVAL_NULL;
|
|
}
|
|
js_LeaveLocalRootScopeWithResult(cx, v);
|
|
if (!ok)
|
|
break;
|
|
vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
|
|
if (JSXML_LENGTH(vxml) != 0) {
|
|
ok = Append(cx, list, vxml);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
} else {
|
|
/* 13.4.4.28 Step 4. */
|
|
for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
|
|
(IS_STAR(nameqn->localName) ||
|
|
js_EqualStrings(nameqn->localName, kid->name->localName))) {
|
|
ok = Append(cx, list, kid);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
static JSBool
|
|
xml_prependChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return Insert(cx, xml, 0, argv[0]);
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_propertyIsEnumerable(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
jsval name;
|
|
uint32 index;
|
|
|
|
XML_METHOD_PROLOG;
|
|
name = argv[0];
|
|
*rval = JSVAL_FALSE;
|
|
if (js_IdIsIndex(name, &index)) {
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
/* 13.5.4.18. */
|
|
*rval = BOOLEAN_TO_JSVAL(index < xml->xml_kids.length);
|
|
} else {
|
|
/* 13.4.4.30. */
|
|
*rval = BOOLEAN_TO_JSVAL(index == 0);
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
namespace_full_match(const void *a, const void *b)
|
|
{
|
|
const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
|
|
const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
|
|
|
|
if (nsa->prefix && nsb->prefix &&
|
|
!js_EqualStrings(nsa->prefix, nsb->prefix)) {
|
|
return JS_FALSE;
|
|
}
|
|
return js_EqualStrings(nsa->uri, nsb->uri);
|
|
}
|
|
|
|
static JSBool
|
|
xml_removeNamespace_helper(JSContext *cx, JSXML *xml, JSXMLNamespace *ns)
|
|
{
|
|
JSXMLNamespace *thisns, *attrns;
|
|
uint32 i, n;
|
|
JSXML *attr, *kid;
|
|
|
|
thisns = GetNamespace(cx, xml->name, &xml->xml_namespaces);
|
|
JS_ASSERT(thisns);
|
|
if (thisns == ns)
|
|
return JS_TRUE;
|
|
|
|
for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
|
|
attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
|
|
if (!attr)
|
|
continue;
|
|
attrns = GetNamespace(cx, attr->name, &xml->xml_namespaces);
|
|
JS_ASSERT(attrns);
|
|
if (attrns == ns)
|
|
return JS_TRUE;
|
|
}
|
|
|
|
i = XMLARRAY_FIND_MEMBER(&xml->xml_namespaces, ns, namespace_full_match);
|
|
if (i != XML_NOT_FOUND)
|
|
XMLArrayDelete(cx, &xml->xml_namespaces, i, JS_TRUE);
|
|
|
|
for (i = 0, n = xml->xml_kids.length; i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
if (!xml_removeNamespace_helper(cx, kid, ns))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_removeNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
JSXMLNamespace *ns;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (xml->xml_class != JSXML_CLASS_ELEMENT)
|
|
goto done;
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
|
|
if (!NamespaceHelper(cx, NULL, 1, argv, rval))
|
|
return JS_FALSE;
|
|
JS_ASSERT(!JSVAL_IS_PRIMITIVE(*rval));
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*rval));
|
|
|
|
/* NOTE: remove ns from each ancestor if not used by that ancestor. */
|
|
if (!xml_removeNamespace_helper(cx, xml, ns))
|
|
return JS_FALSE;
|
|
done:
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_replace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml, *vxml, *kid;
|
|
jsval value, id, junk;
|
|
uint32 index;
|
|
JSXMLQName *nameqn;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
if (xml->xml_class != JSXML_CLASS_ELEMENT)
|
|
goto done;
|
|
|
|
value = argv[1];
|
|
vxml = VALUE_IS_XML(cx, value)
|
|
? (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(value))
|
|
: NULL;
|
|
if (!vxml) {
|
|
if (!JS_ConvertValue(cx, value, JSTYPE_STRING, &argv[1]))
|
|
return JS_FALSE;
|
|
value = argv[1];
|
|
} else {
|
|
vxml = DeepCopy(cx, vxml, NULL, 0);
|
|
if (!vxml)
|
|
return JS_FALSE;
|
|
value = argv[1] = OBJECT_TO_JSVAL(vxml->object);
|
|
}
|
|
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
|
|
id = argv[0];
|
|
if (!js_IdIsIndex(id, &index)) {
|
|
/*
|
|
* Call function QName per spec, not ToXMLName, to avoid attribute
|
|
* names.
|
|
*/
|
|
if (!QNameHelper(cx, NULL, &js_QNameClass.base, 1, argv, rval))
|
|
return JS_FALSE;
|
|
JS_ASSERT(!JSVAL_IS_PRIMITIVE(*rval));
|
|
nameqn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*rval));
|
|
|
|
id = JSVAL_VOID;
|
|
index = xml->xml_kids.length;
|
|
while (index != 0) {
|
|
--index;
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
|
|
if (kid && MatchElemName(nameqn, kid)) {
|
|
if (!JSVAL_IS_VOID(id) && !DeleteByIndex(cx, xml, id, &junk))
|
|
return JS_FALSE;
|
|
if (!IndexToIdVal(cx, index, &id))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
if (JSVAL_IS_VOID(id))
|
|
goto done;
|
|
}
|
|
|
|
if (!Replace(cx, xml, id, value))
|
|
return JS_FALSE;
|
|
|
|
done:
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_setChildren(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
if (!StartNonListXMLMethod(cx, &obj, argv))
|
|
return JS_FALSE;
|
|
|
|
if (!PutProperty(cx, obj, ATOM_KEY(cx->runtime->atomState.starAtom),
|
|
&argv[0])) {
|
|
return JS_FALSE;
|
|
}
|
|
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_setLocalName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
jsval name;
|
|
JSXMLQName *nameqn;
|
|
JSString *namestr;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (!JSXML_HAS_NAME(xml))
|
|
return JS_TRUE;
|
|
|
|
name = argv[0];
|
|
if (!JSVAL_IS_PRIMITIVE(name) &&
|
|
OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(name)) == &js_QNameClass.base) {
|
|
nameqn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(name));
|
|
namestr = nameqn->localName;
|
|
} else {
|
|
if (!JS_ConvertValue(cx, name, JSTYPE_STRING, &argv[0]))
|
|
return JS_FALSE;
|
|
name = argv[0];
|
|
namestr = JSVAL_TO_STRING(name);
|
|
}
|
|
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
xml->name->localName = namestr;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_setName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml, *nsowner;
|
|
jsval name;
|
|
JSXMLQName *nameqn;
|
|
JSObject *nameobj;
|
|
JSXMLArray *nsarray;
|
|
uint32 i, n;
|
|
JSXMLNamespace *ns;
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (!JSXML_HAS_NAME(xml))
|
|
return JS_TRUE;
|
|
|
|
name = argv[0];
|
|
if (!JSVAL_IS_PRIMITIVE(name) &&
|
|
OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(name)) == &js_QNameClass.base &&
|
|
!(nameqn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(name)))
|
|
->uri) {
|
|
name = argv[0] = STRING_TO_JSVAL(nameqn->localName);
|
|
}
|
|
|
|
nameobj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &name);
|
|
if (!nameobj)
|
|
return JS_FALSE;
|
|
nameqn = (JSXMLQName *) JS_GetPrivate(cx, nameobj);
|
|
|
|
/* ECMA-357 13.4.4.35 Step 4. */
|
|
if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
|
|
nameqn->uri = cx->runtime->emptyString;
|
|
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
xml->name = nameqn;
|
|
|
|
/*
|
|
* Erratum: nothing in 13.4.4.35 talks about making the name match the
|
|
* in-scope namespaces, either by finding an in-scope namespace with a
|
|
* matching uri and setting the new name's prefix to that namespace's
|
|
* prefix, or by extending the in-scope namespaces for xml (which are in
|
|
* xml->parent if xml is an attribute or a PI).
|
|
*/
|
|
if (xml->xml_class == JSXML_CLASS_ELEMENT) {
|
|
nsowner = xml;
|
|
} else {
|
|
if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
|
|
return JS_TRUE;
|
|
nsowner = xml->parent;
|
|
}
|
|
|
|
if (nameqn->prefix) {
|
|
/*
|
|
* The name being set has a prefix, which originally came from some
|
|
* namespace object (which may be the null namespace, where both the
|
|
* prefix and uri are the empty string). We must go through a full
|
|
* GetNamespace in case that namespace is in-scope in nsowner.
|
|
*
|
|
* If we find such an in-scope namespace, we return true right away,
|
|
* in this block. Otherwise, we fall through to the final return of
|
|
* AddInScopeNamespace(cx, nsowner, ns).
|
|
*/
|
|
ns = GetNamespace(cx, nameqn, &nsowner->xml_namespaces);
|
|
if (!ns)
|
|
return JS_FALSE;
|
|
|
|
/* XXXbe have to test membership to see whether GetNamespace added */
|
|
if (XMLARRAY_HAS_MEMBER(&nsowner->xml_namespaces, ns, NULL))
|
|
return JS_TRUE;
|
|
} else {
|
|
/*
|
|
* At this point, we know nameqn->prefix is null, so nameqn->uri can't
|
|
* be the empty string (the null namespace always uses the empty string
|
|
* for both prefix and uri).
|
|
*
|
|
* This means we must inline GetNamespace and specialize it to match
|
|
* uri only, never prefix. If we find a namespace with nameqn's uri
|
|
* already in nsowner->xml_namespaces, then all that we need do is set
|
|
* nameqn->prefix to that namespace's prefix.
|
|
*
|
|
* If no such namespace exists, we can create one without going through
|
|
* the constructor, because we know nameqn->uri is non-empty (so prefix
|
|
* does not need to be converted from null to empty by QName).
|
|
*/
|
|
JS_ASSERT(!IS_EMPTY(nameqn->uri));
|
|
|
|
nsarray = &nsowner->xml_namespaces;
|
|
for (i = 0, n = nsarray->length; i < n; i++) {
|
|
ns = XMLARRAY_MEMBER(nsarray, i, JSXMLNamespace);
|
|
if (ns && js_EqualStrings(ns->uri, nameqn->uri)) {
|
|
nameqn->prefix = ns->prefix;
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
|
|
ns = js_NewXMLNamespace(cx, NULL, nameqn->uri, JS_TRUE);
|
|
if (!ns)
|
|
return JS_FALSE;
|
|
}
|
|
|
|
return AddInScopeNamespace(cx, nsowner, ns);
|
|
}
|
|
|
|
static JSBool
|
|
xml_setNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml, *nsowner;
|
|
JSObject *nsobj, *qnobj;
|
|
JSXMLNamespace *ns;
|
|
jsval qnargv[2];
|
|
|
|
NON_LIST_XML_METHOD_PROLOG;
|
|
if (!JSXML_HAS_NAME(xml))
|
|
return JS_TRUE;
|
|
|
|
xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
|
|
if (!xml || !js_GetXMLQNameObject(cx, xml->name))
|
|
return JS_FALSE;
|
|
|
|
nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, obj, 1, argv);
|
|
if (!nsobj)
|
|
return JS_FALSE;
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
|
|
ns->declared = JS_TRUE;
|
|
|
|
qnargv[0] = argv[0] = OBJECT_TO_JSVAL(nsobj);
|
|
qnargv[1] = OBJECT_TO_JSVAL(xml->name->object);
|
|
qnobj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, qnargv);
|
|
if (!qnobj)
|
|
return JS_FALSE;
|
|
|
|
xml->name = (JSXMLQName *) JS_GetPrivate(cx, qnobj);
|
|
|
|
/*
|
|
* Erratum: the spec fails to update the governing in-scope namespaces.
|
|
* See the erratum noted in xml_setName, above.
|
|
*/
|
|
if (xml->xml_class == JSXML_CLASS_ELEMENT) {
|
|
nsowner = xml;
|
|
} else {
|
|
if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
|
|
return JS_TRUE;
|
|
nsowner = xml->parent;
|
|
}
|
|
return AddInScopeNamespace(cx, nsowner, ns);
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_text(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSXML *xml, *list, *kid, *vxml;
|
|
uint32 i, n;
|
|
JSBool ok;
|
|
JSObject *kidobj;
|
|
jsval v;
|
|
|
|
XML_METHOD_PROLOG;
|
|
list = xml_list_helper(cx, xml, rval);
|
|
if (!list)
|
|
return JS_FALSE;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
ok = JS_TRUE;
|
|
for (i = 0, n = xml->xml_kids.length; i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (!ok)
|
|
break;
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (kidobj) {
|
|
ok = xml_text(cx, kidobj, argc, argv, &v);
|
|
} else {
|
|
ok = JS_FALSE;
|
|
v = JSVAL_NULL;
|
|
}
|
|
js_LeaveLocalRootScopeWithResult(cx, v);
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
|
|
if (JSXML_LENGTH(vxml) != 0 && !Append(cx, list, vxml))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
|
|
kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
|
|
if (kid && kid->xml_class == JSXML_CLASS_TEXT) {
|
|
if (!Append(cx, list, kid))
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_toXMLString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSString *str;
|
|
|
|
str = ToXMLString(cx, OBJECT_TO_JSVAL(obj), 0);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
*rval = STRING_TO_JSVAL(str);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSString *
|
|
xml_toString_helper(JSContext *cx, JSXML *xml)
|
|
{
|
|
JSString *str, *kidstr;
|
|
JSXML *kid;
|
|
JSXMLArrayCursor cursor;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_ATTRIBUTE ||
|
|
xml->xml_class == JSXML_CLASS_TEXT) {
|
|
return xml->xml_value;
|
|
}
|
|
|
|
if (!HasSimpleContent(xml))
|
|
return ToXMLString(cx, OBJECT_TO_JSVAL(xml->object), 0);
|
|
|
|
str = cx->runtime->emptyString;
|
|
js_EnterLocalRootScope(cx);
|
|
XMLArrayCursorInit(&cursor, &xml->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
if (kid->xml_class != JSXML_CLASS_COMMENT &&
|
|
kid->xml_class != JSXML_CLASS_PROCESSING_INSTRUCTION) {
|
|
kidstr = xml_toString_helper(cx, kid);
|
|
if (!kidstr) {
|
|
str = NULL;
|
|
break;
|
|
}
|
|
str = js_ConcatStrings(cx, str, kidstr);
|
|
if (!str)
|
|
break;
|
|
}
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
js_LeaveLocalRootScopeWithResult(cx, STRING_TO_JSVAL(str));
|
|
return str;
|
|
}
|
|
|
|
static JSBool
|
|
xml_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSString *str;
|
|
|
|
str = ToXMLString(cx, OBJECT_TO_JSVAL(obj), TO_SOURCE_FLAG);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
*rval = STRING_TO_JSVAL(str);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
xml_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSXML *xml;
|
|
JSString *str;
|
|
|
|
XML_METHOD_PROLOG;
|
|
str = xml_toString_helper(cx, xml);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
*rval = STRING_TO_JSVAL(str);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* XML and XMLList */
|
|
static JSBool
|
|
xml_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSFunctionSpec xml_methods[] = {
|
|
{"addNamespace", xml_addNamespace, 1,0,0},
|
|
{"appendChild", xml_appendChild, 1,0,0},
|
|
{js_attribute_str, xml_attribute, 1,0,0},
|
|
{"attributes", xml_attributes, 0,0,0},
|
|
{"child", xml_child, 1,0,0},
|
|
{"childIndex", xml_childIndex, 0,0,0},
|
|
{"children", xml_children, 0,0,0},
|
|
{"comments", xml_comments, 0,0,0},
|
|
{"contains", xml_contains, 1,0,0},
|
|
{"copy", xml_copy, 0,0,0},
|
|
{"descendants", xml_descendants, 1,0,0},
|
|
{"elements", xml_elements, 1,0,0},
|
|
{"hasOwnProperty", xml_hasOwnProperty, 1,0,0},
|
|
{"hasComplexContent", xml_hasComplexContent, 1,0,0},
|
|
{"hasSimpleContent", xml_hasSimpleContent, 1,0,0},
|
|
{"inScopeNamespaces", xml_inScopeNamespaces, 0,0,0},
|
|
{"insertChildAfter", xml_insertChildAfter, 2,0,0},
|
|
{"insertChildBefore", xml_insertChildBefore, 2,0,0},
|
|
{js_length_str, xml_length, 0,0,0},
|
|
{js_localName_str, xml_localName, 0,0,0},
|
|
{js_name_str, xml_name, 0,0,0},
|
|
{js_namespace_str, xml_namespace, 1,0,0},
|
|
{"namespaceDeclarations", xml_namespaceDeclarations, 0,0,0},
|
|
{"nodeKind", xml_nodeKind, 0,0,0},
|
|
{"normalize", xml_normalize, 0,0,0},
|
|
{js_xml_parent_str, xml_parent, 0,0,0},
|
|
{"processingInstructions",xml_processingInstructions,1,0,0},
|
|
{"prependChild", xml_prependChild, 1,0,0},
|
|
{"propertyIsEnumerable", xml_propertyIsEnumerable, 1,0,0},
|
|
{"removeNamespace", xml_removeNamespace, 1,0,0},
|
|
{"replace", xml_replace, 2,0,0},
|
|
{"setChildren", xml_setChildren, 1,0,0},
|
|
{"setLocalName", xml_setLocalName, 1,0,0},
|
|
{"setName", xml_setName, 1,0,0},
|
|
{"setNamespace", xml_setNamespace, 1,0,0},
|
|
{js_text_str, xml_text, 0,0,0},
|
|
{js_toSource_str, xml_toSource, 0,0,0},
|
|
{js_toString_str, xml_toString, 0,0,0},
|
|
{js_toXMLString_str, xml_toXMLString, 0,0,0},
|
|
{js_valueOf_str, xml_valueOf, 0,0,0},
|
|
{0,0,0,0,0}
|
|
};
|
|
|
|
static JSBool
|
|
CopyXMLSettings(JSContext *cx, JSObject *from, JSObject *to)
|
|
{
|
|
int i;
|
|
const char *name;
|
|
jsval v;
|
|
|
|
for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
|
|
name = xml_static_props[i].name;
|
|
if (!JS_GetProperty(cx, from, name, &v))
|
|
return JS_FALSE;
|
|
if (JSVAL_IS_BOOLEAN(v) && !JS_SetProperty(cx, to, name, &v))
|
|
return JS_FALSE;
|
|
}
|
|
|
|
name = xml_static_props[i].name;
|
|
if (!JS_GetProperty(cx, from, name, &v))
|
|
return JS_FALSE;
|
|
if (JSVAL_IS_NUMBER(v) && !JS_SetProperty(cx, to, name, &v))
|
|
return JS_FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
SetDefaultXMLSettings(JSContext *cx, JSObject *obj)
|
|
{
|
|
int i;
|
|
jsval v;
|
|
|
|
for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
|
|
v = JSVAL_TRUE;
|
|
if (!JS_SetProperty(cx, obj, xml_static_props[i].name, &v))
|
|
return JS_FALSE;
|
|
}
|
|
v = INT_TO_JSVAL(2);
|
|
return JS_SetProperty(cx, obj, xml_static_props[i].name, &v);
|
|
}
|
|
|
|
static JSBool
|
|
xml_settings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
JSObject *settings;
|
|
|
|
settings = JS_NewObject(cx, NULL, NULL, NULL);
|
|
if (!settings)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(settings);
|
|
return CopyXMLSettings(cx, obj, settings);
|
|
}
|
|
|
|
static JSBool
|
|
xml_setSettings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
jsval v;
|
|
JSBool ok;
|
|
JSObject *settings;
|
|
|
|
v = argv[0];
|
|
if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
|
|
cx->xmlSettingFlags = 0;
|
|
ok = SetDefaultXMLSettings(cx, obj);
|
|
} else {
|
|
if (JSVAL_IS_PRIMITIVE(v))
|
|
return JS_TRUE;
|
|
settings = JSVAL_TO_OBJECT(v);
|
|
cx->xmlSettingFlags = 0;
|
|
ok = CopyXMLSettings(cx, settings, obj);
|
|
}
|
|
if (ok)
|
|
cx->xmlSettingFlags |= XSF_CACHE_VALID;
|
|
return ok;
|
|
}
|
|
|
|
static JSBool
|
|
xml_defaultSettings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
JSObject *settings;
|
|
|
|
settings = JS_NewObject(cx, NULL, NULL, NULL);
|
|
if (!settings)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(settings);
|
|
return SetDefaultXMLSettings(cx, settings);
|
|
}
|
|
|
|
static JSFunctionSpec xml_static_methods[] = {
|
|
{"settings", xml_settings, 0,0,0},
|
|
{"setSettings", xml_setSettings, 1,0,0},
|
|
{"defaultSettings", xml_defaultSettings, 0,0,0},
|
|
{0,0,0,0,0}
|
|
};
|
|
|
|
static JSBool
|
|
XML(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
jsval v;
|
|
JSXML *xml, *copy;
|
|
JSObject *xobj, *vobj;
|
|
JSClass *clasp;
|
|
|
|
v = argv[0];
|
|
if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
|
|
v = STRING_TO_JSVAL(cx->runtime->emptyString);
|
|
|
|
xobj = ToXML(cx, v);
|
|
if (!xobj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(xobj);
|
|
xml = (JSXML *) JS_GetPrivate(cx, xobj);
|
|
|
|
if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && !JSVAL_IS_PRIMITIVE(v)) {
|
|
vobj = JSVAL_TO_OBJECT(v);
|
|
clasp = OBJ_GET_CLASS(cx, vobj);
|
|
if (clasp == &js_XMLClass ||
|
|
(clasp->flags & JSCLASS_DOCUMENT_OBSERVER)) {
|
|
/* No need to lock obj, it's newly constructed and thread local. */
|
|
copy = DeepCopy(cx, xml, obj, 0);
|
|
if (!copy)
|
|
return JS_FALSE;
|
|
JS_ASSERT(copy->object == obj);
|
|
*rval = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool
|
|
XMLList(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
jsval v;
|
|
JSObject *vobj, *listobj;
|
|
JSXML *xml, *list;
|
|
|
|
v = argv[0];
|
|
if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
|
|
v = STRING_TO_JSVAL(cx->runtime->emptyString);
|
|
|
|
if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && !JSVAL_IS_PRIMITIVE(v)) {
|
|
vobj = JSVAL_TO_OBJECT(v);
|
|
if (OBJECT_IS_XML(cx, vobj)) {
|
|
xml = (JSXML *) JS_GetPrivate(cx, vobj);
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (!listobj)
|
|
return JS_FALSE;
|
|
*rval = OBJECT_TO_JSVAL(listobj);
|
|
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
if (!Append(cx, list, xml))
|
|
return JS_FALSE;
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Toggle on XML support since the script has explicitly requested it. */
|
|
listobj = ToXMLList(cx, v);
|
|
if (!listobj)
|
|
return JS_FALSE;
|
|
|
|
*rval = OBJECT_TO_JSVAL(listobj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
#define JSXML_LIST_SIZE (offsetof(JSXML, u) + sizeof(struct JSXMLListVar))
|
|
#define JSXML_ELEMENT_SIZE (offsetof(JSXML, u) + sizeof(struct JSXMLVar))
|
|
#define JSXML_LEAF_SIZE (offsetof(JSXML, u) + sizeof(JSString *))
|
|
|
|
static size_t sizeof_JSXML[JSXML_CLASS_LIMIT] = {
|
|
JSXML_LIST_SIZE, /* JSXML_CLASS_LIST */
|
|
JSXML_ELEMENT_SIZE, /* JSXML_CLASS_ELEMENT */
|
|
JSXML_LEAF_SIZE, /* JSXML_CLASS_ATTRIBUTE */
|
|
JSXML_LEAF_SIZE, /* JSXML_CLASS_PROCESSING_INSTRUCTION */
|
|
JSXML_LEAF_SIZE, /* JSXML_CLASS_TEXT */
|
|
JSXML_LEAF_SIZE /* JSXML_CLASS_COMMENT */
|
|
};
|
|
|
|
#ifdef DEBUG_notme
|
|
JSCList xml_leaks = JS_INIT_STATIC_CLIST(&xml_leaks);
|
|
uint32 xml_serial;
|
|
#endif
|
|
|
|
JSXML *
|
|
js_NewXML(JSContext *cx, JSXMLClass xml_class)
|
|
{
|
|
JSXML *xml;
|
|
|
|
xml = (JSXML *) js_NewGCThing(cx, GCX_XML, sizeof_JSXML[xml_class]);
|
|
if (!xml)
|
|
return NULL;
|
|
|
|
xml->object = NULL;
|
|
xml->domnode = NULL;
|
|
xml->parent = NULL;
|
|
xml->name = NULL;
|
|
xml->xml_class = xml_class;
|
|
xml->xml_flags = 0;
|
|
if (JSXML_CLASS_HAS_VALUE(xml_class)) {
|
|
xml->xml_value = cx->runtime->emptyString;
|
|
} else {
|
|
XMLArrayInit(cx, &xml->xml_kids, 0);
|
|
if (xml_class == JSXML_CLASS_LIST) {
|
|
xml->xml_target = NULL;
|
|
xml->xml_targetprop = NULL;
|
|
} else {
|
|
XMLArrayInit(cx, &xml->xml_namespaces, 0);
|
|
XMLArrayInit(cx, &xml->xml_attrs, 0);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_notme
|
|
JS_APPEND_LINK(&xml->links, &xml_leaks);
|
|
xml->serial = xml_serial++;
|
|
#endif
|
|
METER(xml_stats.xml);
|
|
METER(xml_stats.livexml);
|
|
return xml;
|
|
}
|
|
|
|
void
|
|
js_MarkXML(JSContext *cx, JSXML *xml)
|
|
{
|
|
GC_MARK(cx, xml->object, "object");
|
|
GC_MARK(cx, xml->name, "name");
|
|
GC_MARK(cx, xml->parent, "xml_parent");
|
|
|
|
if (JSXML_HAS_VALUE(xml)) {
|
|
GC_MARK(cx, xml->xml_value, "value");
|
|
return;
|
|
}
|
|
|
|
xml_mark_vector(cx,
|
|
(JSXML **) xml->xml_kids.vector,
|
|
xml->xml_kids.length);
|
|
XMLArrayCursorMark(cx, xml->xml_kids.cursors);
|
|
XMLArrayTrim(&xml->xml_kids);
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
if (xml->xml_target)
|
|
GC_MARK(cx, xml->xml_target, "target");
|
|
if (xml->xml_targetprop)
|
|
GC_MARK(cx, xml->xml_targetprop, "targetprop");
|
|
} else {
|
|
namespace_mark_vector(cx,
|
|
(JSXMLNamespace **) xml->xml_namespaces.vector,
|
|
xml->xml_namespaces.length);
|
|
XMLArrayCursorMark(cx, xml->xml_namespaces.cursors);
|
|
XMLArrayTrim(&xml->xml_namespaces);
|
|
|
|
xml_mark_vector(cx,
|
|
(JSXML **) xml->xml_attrs.vector,
|
|
xml->xml_attrs.length);
|
|
XMLArrayCursorMark(cx, xml->xml_attrs.cursors);
|
|
XMLArrayTrim(&xml->xml_attrs);
|
|
}
|
|
}
|
|
|
|
void
|
|
js_FinalizeXML(JSContext *cx, JSXML *xml)
|
|
{
|
|
if (JSXML_HAS_KIDS(xml)) {
|
|
XMLArrayFinish(cx, &xml->xml_kids);
|
|
if (xml->xml_class == JSXML_CLASS_ELEMENT) {
|
|
XMLArrayFinish(cx, &xml->xml_namespaces);
|
|
XMLArrayFinish(cx, &xml->xml_attrs);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_notme
|
|
JS_REMOVE_LINK(&xml->links);
|
|
#endif
|
|
|
|
UNMETER(xml_stats.livexml);
|
|
}
|
|
|
|
JSObject *
|
|
js_ParseNodeToXMLObject(JSContext *cx, JSParseNode *pn)
|
|
{
|
|
jsval nsval;
|
|
JSXMLNamespace *ns;
|
|
JSXMLArray nsarray;
|
|
JSXML *xml;
|
|
|
|
if (!js_GetDefaultXMLNamespace(cx, &nsval))
|
|
return NULL;
|
|
JS_ASSERT(!JSVAL_IS_PRIMITIVE(nsval));
|
|
ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
|
|
|
|
if (!XMLArrayInit(cx, &nsarray, 1))
|
|
return NULL;
|
|
|
|
XMLARRAY_APPEND(cx, &nsarray, ns);
|
|
xml = ParseNodeToXML(cx, pn, &nsarray, XSF_PRECOMPILED_ROOT);
|
|
XMLArrayFinish(cx, &nsarray);
|
|
if (!xml)
|
|
return NULL;
|
|
|
|
return xml->object;
|
|
}
|
|
|
|
JSObject *
|
|
js_NewXMLObject(JSContext *cx, JSXMLClass xml_class)
|
|
{
|
|
JSXML *xml;
|
|
JSObject *obj;
|
|
JSTempValueRooter tvr;
|
|
|
|
xml = js_NewXML(cx, xml_class);
|
|
if (!xml)
|
|
return NULL;
|
|
JS_PUSH_TEMP_ROOT_XML(cx, xml, &tvr);
|
|
obj = js_GetXMLObject(cx, xml);
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
return obj;
|
|
}
|
|
|
|
static JSObject *
|
|
NewXMLObject(JSContext *cx, JSXML *xml)
|
|
{
|
|
JSObject *obj;
|
|
|
|
obj = js_NewObject(cx, &js_XMLClass, NULL, NULL);
|
|
if (!obj || !JS_SetPrivate(cx, obj, xml)) {
|
|
cx->weakRoots.newborn[GCX_OBJECT] = NULL;
|
|
return NULL;
|
|
}
|
|
METER(xml_stats.xmlobj);
|
|
METER(xml_stats.livexmlobj);
|
|
return obj;
|
|
}
|
|
|
|
JSObject *
|
|
js_GetXMLObject(JSContext *cx, JSXML *xml)
|
|
{
|
|
JSObject *obj;
|
|
|
|
obj = xml->object;
|
|
if (obj) {
|
|
JS_ASSERT(JS_GetPrivate(cx, obj) == xml);
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
* A JSXML cannot be shared among threads unless it has an object.
|
|
* A JSXML cannot be given an object unless:
|
|
* (a) it has no parent; or
|
|
* (b) its parent has no object (therefore is thread-private); or
|
|
* (c) its parent's object is locked.
|
|
*
|
|
* Once given an object, a JSXML is immutable.
|
|
*/
|
|
JS_ASSERT(!xml->parent ||
|
|
!xml->parent->object ||
|
|
JS_IS_OBJ_LOCKED(cx, xml->parent->object));
|
|
|
|
obj = NewXMLObject(cx, xml);
|
|
if (!obj)
|
|
return NULL;
|
|
xml->object = obj;
|
|
return obj;
|
|
}
|
|
|
|
JSObject *
|
|
js_InitNamespaceClass(JSContext *cx, JSObject *obj)
|
|
{
|
|
return JS_InitClass(cx, obj, NULL, &js_NamespaceClass.base, Namespace, 2,
|
|
namespace_props, namespace_methods, NULL, NULL);
|
|
}
|
|
|
|
JSObject *
|
|
js_InitQNameClass(JSContext *cx, JSObject *obj)
|
|
{
|
|
return JS_InitClass(cx, obj, NULL, &js_QNameClass.base, QName, 2,
|
|
qname_props, qname_methods, NULL, NULL);
|
|
}
|
|
|
|
JSObject *
|
|
js_InitAttributeNameClass(JSContext *cx, JSObject *obj)
|
|
{
|
|
return JS_InitClass(cx, obj, NULL, &js_AttributeNameClass, AttributeName, 2,
|
|
qname_props, qname_methods, NULL, NULL);
|
|
}
|
|
|
|
JSObject *
|
|
js_InitAnyNameClass(JSContext *cx, JSObject *obj)
|
|
{
|
|
jsval v;
|
|
|
|
if (!js_GetAnyName(cx, &v))
|
|
return NULL;
|
|
return JSVAL_TO_OBJECT(v);
|
|
}
|
|
|
|
JSObject *
|
|
js_InitXMLClass(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSObject *proto, *pobj, *ctor;
|
|
JSFunction *fun;
|
|
JSXML *xml;
|
|
JSProperty *prop;
|
|
JSScopeProperty *sprop;
|
|
jsval cval, argv[1], junk;
|
|
|
|
/* Define the isXMLName function. */
|
|
if (!JS_DefineFunction(cx, obj, js_isXMLName_str, xml_isXMLName, 1, 0))
|
|
return NULL;
|
|
|
|
/* Define the XML class constructor and prototype. */
|
|
proto = JS_InitClass(cx, obj, NULL, &js_XMLClass, XML, 1,
|
|
NULL, xml_methods,
|
|
xml_static_props, xml_static_methods);
|
|
if (!proto)
|
|
return NULL;
|
|
|
|
xml = js_NewXML(cx, JSXML_CLASS_TEXT);
|
|
if (!xml || !JS_SetPrivate(cx, proto, xml))
|
|
return NULL;
|
|
xml->object = proto;
|
|
METER(xml_stats.xmlobj);
|
|
METER(xml_stats.livexmlobj);
|
|
|
|
/*
|
|
* Prepare to set default settings on the XML constructor we just made.
|
|
* NB: We can't use JS_GetConstructor, because it calls OBJ_GET_PROPERTY,
|
|
* which is xml_getProperty, which creates a new XMLList every time! We
|
|
* must instead call js_LookupProperty directly.
|
|
*/
|
|
if (!js_LookupProperty(cx, proto,
|
|
ATOM_TO_JSID(cx->runtime->atomState.constructorAtom),
|
|
&pobj, &prop)) {
|
|
return NULL;
|
|
}
|
|
JS_ASSERT(prop);
|
|
sprop = (JSScopeProperty *) prop;
|
|
JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj)));
|
|
cval = OBJ_GET_SLOT(cx, pobj, sprop->slot);
|
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
|
JS_ASSERT(VALUE_IS_FUNCTION(cx, cval));
|
|
|
|
/* Set default settings. */
|
|
ctor = JSVAL_TO_OBJECT(cval);
|
|
argv[0] = JSVAL_VOID;
|
|
if (!xml_setSettings(cx, ctor, 1, argv, &junk))
|
|
return NULL;
|
|
|
|
/* Define the XMLList function and give it the same prototype as XML. */
|
|
fun = JS_DefineFunction(cx, obj, js_XMLList_str, XMLList, 1, 0);
|
|
if (!fun)
|
|
return NULL;
|
|
if (!js_SetClassPrototype(cx, fun->object, proto,
|
|
JSPROP_READONLY | JSPROP_PERMANENT)) {
|
|
return NULL;
|
|
}
|
|
return proto;
|
|
}
|
|
|
|
JSObject *
|
|
js_InitXMLClasses(JSContext *cx, JSObject *obj)
|
|
{
|
|
if (!js_InitNamespaceClass(cx, obj))
|
|
return NULL;
|
|
if (!js_InitQNameClass(cx, obj))
|
|
return NULL;
|
|
if (!js_InitAttributeNameClass(cx, obj))
|
|
return NULL;
|
|
if (!js_InitAnyNameClass(cx, obj))
|
|
return NULL;
|
|
return js_InitXMLClass(cx, obj);
|
|
}
|
|
|
|
JSBool
|
|
js_GetFunctionNamespace(JSContext *cx, jsval *vp)
|
|
{
|
|
JSRuntime *rt;
|
|
JSObject *obj;
|
|
JSAtom *atom;
|
|
JSString *prefix, *uri;
|
|
|
|
/* An invalid URI, for internal use only, guaranteed not to collide. */
|
|
static const char anti_uri[] = "@mozilla.org/js/function";
|
|
|
|
/* Optimize by avoiding JS_LOCK_GC(rt) for the common case. */
|
|
rt = cx->runtime;
|
|
obj = rt->functionNamespaceObject;
|
|
if (!obj) {
|
|
JS_LOCK_GC(rt);
|
|
obj = rt->functionNamespaceObject;
|
|
if (!obj) {
|
|
JS_UNLOCK_GC(rt);
|
|
|
|
/*
|
|
* Note that any race to atomize anti_uri here is resolved by
|
|
* the atom table code, such that at most one atom for anti_uri
|
|
* is created. We store in rt->atomState.lazy unconditionally,
|
|
* since we are guaranteed to overwrite either null or the same
|
|
* atom pointer.
|
|
*/
|
|
atom = js_Atomize(cx, anti_uri, sizeof anti_uri - 1, ATOM_PINNED);
|
|
if (!atom)
|
|
return JS_FALSE;
|
|
rt->atomState.lazy.functionNamespaceURIAtom = atom;
|
|
|
|
prefix = ATOM_TO_STRING(rt->atomState.typeAtoms[JSTYPE_FUNCTION]);
|
|
uri = ATOM_TO_STRING(atom);
|
|
obj = js_NewXMLNamespaceObject(cx, prefix, uri, JS_FALSE);
|
|
if (!obj)
|
|
return JS_FALSE;
|
|
|
|
/*
|
|
* Avoid entraining any in-scope Object.prototype. The loss of
|
|
* Namespace.prototype is not detectable, as there is no way to
|
|
* refer to this instance in scripts. When used to qualify method
|
|
* names, its prefix and uri references are copied to the QName.
|
|
*/
|
|
OBJ_SET_PROTO(cx, obj, NULL);
|
|
OBJ_SET_PARENT(cx, obj, NULL);
|
|
|
|
JS_LOCK_GC(rt);
|
|
if (!rt->functionNamespaceObject)
|
|
rt->functionNamespaceObject = obj;
|
|
else
|
|
obj = rt->functionNamespaceObject;
|
|
}
|
|
JS_UNLOCK_GC(rt);
|
|
}
|
|
*vp = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Note the asymmetry between js_GetDefaultXMLNamespace and js_SetDefaultXML-
|
|
* Namespace. Get searches fp->scopeChain for JS_DEFAULT_XML_NAMESPACE_ID,
|
|
* while Set sets JS_DEFAULT_XML_NAMESPACE_ID in fp->varobj (unless fp is a
|
|
* lightweight function activation). There's no requirement that fp->varobj
|
|
* lie directly on fp->scopeChain, although it should be reachable using the
|
|
* prototype chain from a scope object (cf. JSOPTION_VAROBJFIX in jsapi.h).
|
|
*
|
|
* If Get can't find JS_DEFAULT_XML_NAMESPACE_ID along the scope chain, it
|
|
* creates a default namespace via 'new Namespace()'. In contrast, Set uses
|
|
* its v argument as the uri of a new Namespace, with "" as the prefix. See
|
|
* ECMA-357 12.1 and 12.1.1. Note that if Set is called with a Namespace n,
|
|
* the default XML namespace will be set to ("", n.uri). So the uri string
|
|
* is really the only usefully stored value of the default namespace.
|
|
*/
|
|
JSBool
|
|
js_GetDefaultXMLNamespace(JSContext *cx, jsval *vp)
|
|
{
|
|
JSStackFrame *fp;
|
|
JSObject *nsobj, *obj, *tmp;
|
|
jsval v;
|
|
|
|
fp = cx->fp;
|
|
nsobj = fp->xmlNamespace;
|
|
if (nsobj) {
|
|
*vp = OBJECT_TO_JSVAL(nsobj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
obj = NULL;
|
|
for (tmp = fp->scopeChain; tmp; tmp = OBJ_GET_PARENT(cx, obj)) {
|
|
obj = tmp;
|
|
if (!OBJ_GET_PROPERTY(cx, obj, JS_DEFAULT_XML_NAMESPACE_ID, &v))
|
|
return JS_FALSE;
|
|
if (!JSVAL_IS_PRIMITIVE(v)) {
|
|
fp->xmlNamespace = JSVAL_TO_OBJECT(v);
|
|
*vp = v;
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
|
|
nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, obj, 0, NULL);
|
|
if (!nsobj)
|
|
return JS_FALSE;
|
|
v = OBJECT_TO_JSVAL(nsobj);
|
|
if (obj &&
|
|
!OBJ_DEFINE_PROPERTY(cx, obj, JS_DEFAULT_XML_NAMESPACE_ID, v,
|
|
JS_PropertyStub, JS_PropertyStub,
|
|
JSPROP_PERMANENT, NULL)) {
|
|
return JS_FALSE;
|
|
}
|
|
fp->xmlNamespace = nsobj;
|
|
*vp = v;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool
|
|
js_SetDefaultXMLNamespace(JSContext *cx, jsval v)
|
|
{
|
|
jsval argv[2];
|
|
JSObject *nsobj, *varobj;
|
|
JSStackFrame *fp;
|
|
|
|
argv[0] = STRING_TO_JSVAL(cx->runtime->emptyString);
|
|
argv[1] = v;
|
|
nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL,
|
|
2, argv);
|
|
if (!nsobj)
|
|
return JS_FALSE;
|
|
v = OBJECT_TO_JSVAL(nsobj);
|
|
|
|
fp = cx->fp;
|
|
varobj = fp->varobj;
|
|
if (varobj) {
|
|
if (!OBJ_DEFINE_PROPERTY(cx, varobj, JS_DEFAULT_XML_NAMESPACE_ID, v,
|
|
JS_PropertyStub, JS_PropertyStub,
|
|
JSPROP_PERMANENT, NULL)) {
|
|
return JS_FALSE;
|
|
}
|
|
} else {
|
|
JS_ASSERT(fp->fun && !JSFUN_HEAVYWEIGHT_TEST(fp->fun->flags));
|
|
}
|
|
fp->xmlNamespace = JSVAL_TO_OBJECT(v);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool
|
|
js_ToAttributeName(JSContext *cx, jsval *vp)
|
|
{
|
|
JSXMLQName *qn;
|
|
|
|
qn = ToAttributeName(cx, *vp);
|
|
if (!qn)
|
|
return JS_FALSE;
|
|
*vp = OBJECT_TO_JSVAL(qn->object);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSString *
|
|
js_EscapeAttributeValue(JSContext *cx, JSString *str, JSBool quote)
|
|
{
|
|
return EscapeAttributeValue(cx, NULL, str, quote);
|
|
}
|
|
|
|
JSString *
|
|
js_AddAttributePart(JSContext *cx, JSBool isName, JSString *str, JSString *str2)
|
|
{
|
|
size_t len, len2, newlen;
|
|
jschar *chars;
|
|
|
|
if (JSSTRING_IS_DEPENDENT(str) ||
|
|
!(*js_GetGCThingFlags(str) & GCF_MUTABLE)) {
|
|
str = js_NewStringCopyN(cx, JSSTRING_CHARS(str), JSSTRING_LENGTH(str),
|
|
0);
|
|
if (!str)
|
|
return NULL;
|
|
}
|
|
|
|
len = str->length;
|
|
len2 = JSSTRING_LENGTH(str2);
|
|
newlen = (isName) ? len + 1 + len2 : len + 2 + len2 + 1;
|
|
chars = (jschar *) JS_realloc(cx, str->chars, (newlen+1) * sizeof(jschar));
|
|
if (!chars)
|
|
return NULL;
|
|
|
|
/*
|
|
* Reallocating str (because we know it has no other references) requires
|
|
* purging any deflated string cached for it.
|
|
*/
|
|
js_PurgeDeflatedStringCache(cx->runtime, str);
|
|
|
|
str->chars = chars;
|
|
str->length = newlen;
|
|
chars += len;
|
|
if (isName) {
|
|
*chars++ = ' ';
|
|
js_strncpy(chars, JSSTRING_CHARS(str2), len2);
|
|
chars += len2;
|
|
} else {
|
|
*chars++ = '=';
|
|
*chars++ = '"';
|
|
js_strncpy(chars, JSSTRING_CHARS(str2), len2);
|
|
chars += len2;
|
|
*chars++ = '"';
|
|
}
|
|
*chars = 0;
|
|
return str;
|
|
}
|
|
|
|
JSString *
|
|
js_EscapeElementValue(JSContext *cx, JSString *str)
|
|
{
|
|
return EscapeElementValue(cx, NULL, str);
|
|
}
|
|
|
|
JSString *
|
|
js_ValueToXMLString(JSContext *cx, jsval v)
|
|
{
|
|
return ToXMLString(cx, v, 0);
|
|
}
|
|
|
|
static JSBool
|
|
anyname_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
|
|
jsval *rval)
|
|
{
|
|
*rval = ATOM_KEY(cx->runtime->atomState.starAtom);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool
|
|
js_GetAnyName(JSContext *cx, jsval *vp)
|
|
{
|
|
JSRuntime *rt;
|
|
JSObject *obj;
|
|
JSXMLQName *qn;
|
|
JSBool ok;
|
|
|
|
/* Optimize by avoiding JS_LOCK_GC(rt) for the common case. */
|
|
rt = cx->runtime;
|
|
obj = rt->anynameObject;
|
|
if (!obj) {
|
|
JS_LOCK_GC(rt);
|
|
obj = rt->anynameObject;
|
|
if (!obj) {
|
|
JS_UNLOCK_GC(rt);
|
|
|
|
/*
|
|
* Protect multiple newborns created below, in the do-while(0)
|
|
* loop used to ensure that we leave this local root scope.
|
|
*/
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
|
|
do {
|
|
qn = js_NewXMLQName(cx, rt->emptyString, rt->emptyString,
|
|
ATOM_TO_STRING(rt->atomState.starAtom));
|
|
if (!qn) {
|
|
ok = JS_FALSE;
|
|
break;
|
|
}
|
|
|
|
obj = js_NewObject(cx, &js_AnyNameClass, NULL, NULL);
|
|
if (!obj || !JS_SetPrivate(cx, obj, qn)) {
|
|
cx->weakRoots.newborn[GCX_OBJECT] = NULL;
|
|
ok = JS_FALSE;
|
|
break;
|
|
}
|
|
qn->object = obj;
|
|
METER(xml_stats.qnameobj);
|
|
METER(xml_stats.liveqnameobj);
|
|
|
|
/*
|
|
* Avoid entraining any Object.prototype found via cx's scope
|
|
* chain or global object. This loses the default toString,
|
|
* but no big deal: we want to customize toString anyway for
|
|
* clearer diagnostics.
|
|
*/
|
|
if (!JS_DefineFunction(cx, obj, js_toString_str,
|
|
anyname_toString, 0, 0)) {
|
|
ok = JS_FALSE;
|
|
break;
|
|
}
|
|
OBJ_SET_PROTO(cx, obj, NULL);
|
|
JS_ASSERT(!OBJ_GET_PARENT(cx, obj));
|
|
} while (0);
|
|
|
|
js_LeaveLocalRootScopeWithResult(cx, OBJECT_TO_JSVAL(obj));
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
|
|
JS_LOCK_GC(rt);
|
|
if (!rt->anynameObject)
|
|
rt->anynameObject = obj;
|
|
else
|
|
obj = rt->anynameObject;
|
|
}
|
|
JS_UNLOCK_GC(rt);
|
|
}
|
|
*vp = OBJECT_TO_JSVAL(obj);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool
|
|
js_FindXMLProperty(JSContext *cx, jsval name, JSObject **objp, jsval *namep)
|
|
{
|
|
JSXMLQName *qn;
|
|
jsid funid, id;
|
|
JSObject *obj, *pobj, *lastobj;
|
|
JSProperty *prop;
|
|
const char *printable;
|
|
|
|
qn = ToXMLName(cx, name, &funid);
|
|
if (!qn)
|
|
return JS_FALSE;
|
|
id = OBJECT_TO_JSID(qn->object);
|
|
|
|
obj = cx->fp->scopeChain;
|
|
do {
|
|
if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
|
|
return JS_FALSE;
|
|
if (prop) {
|
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
|
|
|
/*
|
|
* Call OBJ_THIS_OBJECT to skip any With object that wraps an XML
|
|
* object to carry scope chain linkage in js_FilterXMLList.
|
|
*/
|
|
pobj = OBJ_THIS_OBJECT(cx, obj);
|
|
if (OBJECT_IS_XML(cx, pobj)) {
|
|
*objp = pobj;
|
|
*namep = ID_TO_VALUE(id);
|
|
return JS_TRUE;
|
|
}
|
|
}
|
|
|
|
lastobj = obj;
|
|
} while ((obj = OBJ_GET_PARENT(cx, obj)) != NULL);
|
|
|
|
printable = js_ValueToPrintableString(cx, name);
|
|
if (printable) {
|
|
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR,
|
|
js_GetErrorMessage, NULL,
|
|
JSMSG_UNDEFINED_XML_NAME, printable);
|
|
}
|
|
return JS_FALSE;
|
|
}
|
|
|
|
JSBool
|
|
js_GetXMLProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
return GetProperty(cx, obj, name, vp);
|
|
}
|
|
|
|
JSBool
|
|
js_GetXMLFunction(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
|
|
{
|
|
JSObject *target;
|
|
JSXML *xml;
|
|
JSTempValueRooter tvr;
|
|
JSBool ok;
|
|
|
|
JS_ASSERT(OBJECT_IS_XML(cx, obj));
|
|
|
|
/* After this point, control must flow through label out: to exit. */
|
|
JS_PUSH_TEMP_ROOT_OBJECT(cx, NULL, &tvr);
|
|
|
|
/*
|
|
* See comments before xml_lookupProperty about the need for the proto
|
|
* chain lookup.
|
|
*/
|
|
target = obj;
|
|
for (;;) {
|
|
ok = js_GetProperty(cx, target, id, vp);
|
|
if (!ok)
|
|
goto out;
|
|
if (VALUE_IS_FUNCTION(cx, *vp)) {
|
|
ok = JS_TRUE;
|
|
goto out;
|
|
}
|
|
target = OBJ_GET_PROTO(cx, target);
|
|
if (target == NULL)
|
|
break;
|
|
tvr.u.object = target;
|
|
}
|
|
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
if (HasSimpleContent(xml)) {
|
|
/* Search in String.prototype to implement 11.2.2.1 Step 3(f). */
|
|
ok = js_GetClassPrototype(cx, NULL, INT_TO_JSID(JSProto_String),
|
|
&tvr.u.object);
|
|
if (!ok)
|
|
goto out;
|
|
JS_ASSERT(tvr.u.object);
|
|
ok = OBJ_GET_PROPERTY(cx, tvr.u.object, id, vp);
|
|
}
|
|
|
|
out:
|
|
JS_POP_TEMP_ROOT(cx, &tvr);
|
|
return ok;
|
|
}
|
|
|
|
JSBool
|
|
js_SetXMLProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
return PutProperty(cx, obj, name, vp);
|
|
}
|
|
|
|
static JSXML *
|
|
GetPrivate(JSContext *cx, JSObject *obj, const char *method)
|
|
{
|
|
JSXML *xml;
|
|
|
|
xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
|
|
if (!xml) {
|
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
|
JSMSG_INCOMPATIBLE_METHOD,
|
|
js_XML_str, method, OBJ_GET_CLASS(cx, obj)->name);
|
|
}
|
|
return xml;
|
|
}
|
|
|
|
JSBool
|
|
js_GetXMLDescendants(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSXML *xml, *list;
|
|
|
|
xml = GetPrivate(cx, obj, "descendants internal method");
|
|
if (!xml)
|
|
return JS_FALSE;
|
|
|
|
list = Descendants(cx, xml, id);
|
|
if (!list)
|
|
return JS_FALSE;
|
|
*vp = OBJECT_TO_JSVAL(list->object);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool
|
|
js_DeleteXMLListElements(JSContext *cx, JSObject *listobj)
|
|
{
|
|
JSXML *list;
|
|
uint32 n;
|
|
jsval junk;
|
|
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
for (n = list->xml_kids.length; n != 0; --n) {
|
|
if (!DeleteProperty(cx, listobj, INT_TO_JSID(0), &junk))
|
|
return JS_FALSE;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
JSBool
|
|
js_FilterXMLList(JSContext *cx, JSObject *obj, jsbytecode *pc, jsval *vp)
|
|
{
|
|
JSBool ok, match;
|
|
JSStackFrame *fp;
|
|
uint32 flags;
|
|
JSObject *scobj, *listobj, *resobj, *withobj, *kidobj, *obj2;
|
|
JSXML *xml, *list, *result, *kid;
|
|
JSXMLArrayCursor cursor;
|
|
|
|
ok = js_EnterLocalRootScope(cx);
|
|
if (!ok)
|
|
return JS_FALSE;
|
|
|
|
/* All control flow after this point must exit via label out or bad. */
|
|
*vp = JSVAL_NULL;
|
|
fp = cx->fp;
|
|
flags = fp->flags;
|
|
fp->flags = flags | JSFRAME_FILTERING;
|
|
scobj = js_GetScopeChain(cx, fp);
|
|
withobj = NULL;
|
|
if (!scobj)
|
|
goto bad;
|
|
xml = GetPrivate(cx, obj, "filtering predicate operator");
|
|
if (!xml)
|
|
goto bad;
|
|
|
|
if (xml->xml_class == JSXML_CLASS_LIST) {
|
|
list = xml;
|
|
} else {
|
|
listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (!listobj)
|
|
goto bad;
|
|
list = (JSXML *) JS_GetPrivate(cx, listobj);
|
|
ok = Append(cx, list, xml);
|
|
if (!ok)
|
|
goto out;
|
|
}
|
|
|
|
resobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
|
|
if (!resobj)
|
|
goto bad;
|
|
result = (JSXML *) JS_GetPrivate(cx, resobj);
|
|
|
|
/* Hoist the scope chain update out of the loop over kids. */
|
|
withobj = js_NewWithObject(cx, NULL, scobj, -1);
|
|
if (!withobj)
|
|
goto bad;
|
|
fp->scopeChain = withobj;
|
|
|
|
XMLArrayCursorInit(&cursor, &list->xml_kids);
|
|
while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
|
|
kidobj = js_GetXMLObject(cx, kid);
|
|
if (!kidobj)
|
|
break;
|
|
OBJ_SET_PROTO(cx, withobj, kidobj);
|
|
ok = js_Interpret(cx, pc, vp) && js_ValueToBoolean(cx, *vp, &match);
|
|
for (obj2 = fp->scopeChain;
|
|
obj2 != withobj;
|
|
obj2 = OBJ_GET_PARENT(cx, obj2)) {
|
|
if (OBJ_GET_CLASS(cx, obj2) == &js_BlockClass) {
|
|
if (JS_GetPrivate(cx, obj2) != fp)
|
|
break;
|
|
ok &= js_PutBlockObject(cx, obj2);
|
|
}
|
|
}
|
|
if (ok && match)
|
|
ok = Append(cx, result, kid);
|
|
if (!ok)
|
|
break;
|
|
}
|
|
XMLArrayCursorFinish(&cursor);
|
|
if (!ok)
|
|
goto out;
|
|
if (kid)
|
|
goto bad;
|
|
|
|
*vp = OBJECT_TO_JSVAL(resobj);
|
|
|
|
out:
|
|
fp->flags = flags | (fp->flags & JSFRAME_POP_BLOCKS);
|
|
if (withobj) {
|
|
fp->scopeChain = scobj;
|
|
JS_SetPrivate(cx, withobj, NULL);
|
|
}
|
|
js_LeaveLocalRootScopeWithResult(cx, *vp);
|
|
return ok;
|
|
bad:
|
|
ok = JS_FALSE;
|
|
goto out;
|
|
}
|
|
|
|
JSObject *
|
|
js_ValueToXMLObject(JSContext *cx, jsval v)
|
|
{
|
|
return ToXML(cx, v);
|
|
}
|
|
|
|
JSObject *
|
|
js_ValueToXMLListObject(JSContext *cx, jsval v)
|
|
{
|
|
return ToXMLList(cx, v);
|
|
}
|
|
|
|
JSObject *
|
|
js_CloneXMLObject(JSContext *cx, JSObject *obj)
|
|
{
|
|
uintN flags;
|
|
JSXML *xml;
|
|
|
|
if (!GetXMLSettingFlags(cx, &flags))
|
|
return NULL;
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
if (flags & (XSF_IGNORE_COMMENTS |
|
|
XSF_IGNORE_PROCESSING_INSTRUCTIONS |
|
|
XSF_IGNORE_WHITESPACE)) {
|
|
xml = DeepCopy(cx, xml, NULL, flags);
|
|
if (!xml)
|
|
return NULL;
|
|
return xml->object;
|
|
}
|
|
return NewXMLObject(cx, xml);
|
|
}
|
|
|
|
JSObject *
|
|
js_NewXMLSpecialObject(JSContext *cx, JSXMLClass xml_class, JSString *name,
|
|
JSString *value)
|
|
{
|
|
uintN flags;
|
|
JSObject *obj;
|
|
JSXML *xml;
|
|
JSXMLQName *qn;
|
|
|
|
if (!GetXMLSettingFlags(cx, &flags))
|
|
return NULL;
|
|
|
|
if ((xml_class == JSXML_CLASS_COMMENT &&
|
|
(flags & XSF_IGNORE_COMMENTS)) ||
|
|
(xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
|
|
(flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS))) {
|
|
return js_NewXMLObject(cx, JSXML_CLASS_TEXT);
|
|
}
|
|
|
|
obj = js_NewXMLObject(cx, xml_class);
|
|
if (!obj)
|
|
return NULL;
|
|
xml = (JSXML *) JS_GetPrivate(cx, obj);
|
|
if (name) {
|
|
qn = js_NewXMLQName(cx, cx->runtime->emptyString, NULL, name);
|
|
if (!qn)
|
|
return NULL;
|
|
xml->name = qn;
|
|
}
|
|
xml->xml_value = value;
|
|
return obj;
|
|
}
|
|
|
|
JSString *
|
|
js_MakeXMLCDATAString(JSContext *cx, JSString *str)
|
|
{
|
|
return MakeXMLCDATAString(cx, NULL, str);
|
|
}
|
|
|
|
JSString *
|
|
js_MakeXMLCommentString(JSContext *cx, JSString *str)
|
|
{
|
|
return MakeXMLCommentString(cx, NULL, str);
|
|
}
|
|
|
|
JSString *
|
|
js_MakeXMLPIString(JSContext *cx, JSString *name, JSString *str)
|
|
{
|
|
return MakeXMLPIString(cx, NULL, name, str);
|
|
}
|
|
|
|
#endif /* JS_HAS_XML_SUPPORT */
|