1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=4 sw=4 et tw=78:
3  *
4  * ***** BEGIN LICENSE BLOCK *****
5  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is SpiderMonkey E4X code, released August, 2004.
18  *
19  * The Initial Developer of the Original Code is
20  * Netscape Communications Corporation.
21  * Portions created by the Initial Developer are Copyright (C) 1998
22  * the Initial Developer. All Rights Reserved.
23  *
24  * Contributor(s):
25  *
26  * Alternatively, the contents of this file may be used under the terms of
27  * either of the GNU General Public License Version 2 or later (the "GPL"),
28  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29  * in which case the provisions of the GPL or the LGPL are applicable instead
30  * of those above. If you wish to allow use of your version of this file only
31  * under the terms of either the GPL or the LGPL, and not to allow others to
32  * use your version of this file under the terms of the MPL, indicate your
33  * decision by deleting the provisions above and replace them with the notice
34  * and other provisions required by the GPL or the LGPL. If you do not delete
35  * the provisions above, a recipient may use your version of this file under
36  * the terms of any one of the MPL, the GPL or the LGPL.
37  *
38  * ***** END LICENSE BLOCK ***** */
39
40 #include "jsstddef.h"
41 #include "jsconfig.h"
42
43 #if JS_HAS_XML_SUPPORT
44
45 #include <math.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include "jstypes.h"
49 #include "jsbit.h"
50 #include "jsprf.h"
51 #include "jsutil.h"
52 #include "jsapi.h"
53 #include "jsarray.h"
54 #include "jsatom.h"
55 #include "jsbool.h"
56 #include "jscntxt.h"
57 #include "jsfun.h"
58 #include "jsgc.h"
59 #include "jsinterp.h"
60 #include "jslock.h"
61 #include "jsnum.h"
62 #include "jsobj.h"
63 #include "jsopcode.h"
64 #include "jsparse.h"
65 #include "jsscan.h"
66 #include "jsscope.h"
67 #include "jsscript.h"
68 #include "jsstr.h"
69 #include "jsxml.h"
70
71 #ifdef DEBUG
72 #include <string.h>     /* for #ifdef DEBUG memset calls */
73 #endif
74
75 /*
76  * NOTES
77  * - in the js shell, you must use the -x command line option, or call
78  *   options('xml') before compiling anything that uses XML literals
79  *
80  * TODO
81  * - XXXbe patrol
82  * - Fuse objects and their JSXML* private data into single GC-things
83  * - fix function::foo vs. x.(foo == 42) collision using proper namespacing
84  * - fix the !TCF_HAS_DEFXMLNS optimization in js_FoldConstants
85  * - JSCLASS_DOCUMENT_OBSERVER support -- live two-way binding to Gecko's DOM!
86  * - JS_TypeOfValue sure could use a cleaner interface to "types"
87  */
88
89 #ifdef DEBUG_brendan
90 #define METERING        1
91 #endif
92
93 #ifdef METERING
94 static struct {
95     jsrefcount  qname;
96     jsrefcount  qnameobj;
97     jsrefcount  liveqname;
98     jsrefcount  liveqnameobj;
99     jsrefcount  namespace;
100     jsrefcount  namespaceobj;
101     jsrefcount  livenamespace;
102     jsrefcount  livenamespaceobj;
103     jsrefcount  xml;
104     jsrefcount  xmlobj;
105     jsrefcount  livexml;
106     jsrefcount  livexmlobj;
107 } xml_stats;
108
109 #define METER(x)        JS_ATOMIC_INCREMENT(&(x))
110 #define UNMETER(x)      JS_ATOMIC_DECREMENT(&(x))
111 #else
112 #define METER(x)        /* nothing */
113 #define UNMETER(x)      /* nothing */
114 #endif
115
116 /*
117  * Random utilities and global functions.
118  */
119 const char js_AnyName_str[]       = "AnyName";
120 const char js_AttributeName_str[] = "AttributeName";
121 const char js_isXMLName_str[]     = "isXMLName";
122 const char js_XMLList_str[]       = "XMLList";
123 const char js_localName_str[]     = "localName";
124 const char js_xml_parent_str[]    = "parent";
125 const char js_prefix_str[]        = "prefix";
126 const char js_toXMLString_str[]   = "toXMLString";
127 const char js_uri_str[]           = "uri";
128
129 const char js_amp_entity_str[]    = "&amp;";
130 const char js_gt_entity_str[]     = "&gt;";
131 const char js_lt_entity_str[]     = "&lt;";
132 const char js_quot_entity_str[]   = "&quot;";
133
134 #define IS_EMPTY(str) (JSSTRING_LENGTH(str) == 0)
135 #define IS_STAR(str)  (JSSTRING_LENGTH(str) == 1 && *JSSTRING_CHARS(str) == '*')
136
137 static JSBool
138 xml_isXMLName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
139               jsval *rval)
140 0 {
141 0     *rval = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argv[0]));
142 0     return JS_TRUE;
143 }
144
145 /*
146  * Namespace class and library functions.
147  */
148 enum namespace_tinyid {
149     NAMESPACE_PREFIX = -1,
150     NAMESPACE_URI = -2
151 };
152
153 static JSBool
154 namespace_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
155 0 {
156 0     JSXMLNamespace *ns;
157
158 0     if (!JSVAL_IS_INT(id))
159 0         return JS_TRUE;
160
161 0     ns = (JSXMLNamespace *)
162          JS_GetInstancePrivate(cx, obj, &js_NamespaceClass.base, NULL);
163 0     if (!ns)
164 0         return JS_TRUE;
165
166 0     switch (JSVAL_TO_INT(id)) {
167       case NAMESPACE_PREFIX:
168 0         *vp = ns->prefix ? STRING_TO_JSVAL(ns->prefix) : JSVAL_VOID;
169 0         break;
170       case NAMESPACE_URI:
171 0         *vp = STRING_TO_JSVAL(ns->uri);
172         break;
173     }
174 0     return JS_TRUE;
175 }
176
177 static void
178 namespace_finalize(JSContext *cx, JSObject *obj)
179 48 {
180 48     JSXMLNamespace *ns;
181 48     JSRuntime *rt;
182
183 48     ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
184 48     if (!ns)
185 0         return;
186 48     JS_ASSERT(ns->object == obj);
187 48     ns->object = NULL;
188     UNMETER(xml_stats.livenamespaceobj);
189
190 48     rt = cx->runtime;
191 48     if (rt->functionNamespaceObject == obj)
192 0         rt->functionNamespaceObject = NULL;
193 }
194
195 static void
196 namespace_mark_vector(JSContext *cx, JSXMLNamespace **vec, uint32 len,
197                       void *arg)
198 0 {
199 0     uint32 i;
200 0     JSXMLNamespace *ns;
201
202 0     for (i = 0; i < len; i++) {
203 0         ns = vec[i];
204         {
205 #ifdef GC_MARK_DEBUG
206             char buf[100];
207
208             JS_snprintf(buf, sizeof buf, "%s=%s",
209                         ns->prefix ? JS_GetStringBytes(ns->prefix) : "",
210                         JS_GetStringBytes(ns->uri));
211 #else
212 0             const char *buf = NULL;
213 #endif
214 0             JS_MarkGCThing(cx, ns, buf, arg);
215         }
216     }
217 }
218
219 static uint32
220 namespace_mark(JSContext *cx, JSObject *obj, void *arg)
221 16 {
222 16     JSXMLNamespace *ns;
223
224 16     ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
225 16     JS_MarkGCThing(cx, ns, js_private_str, arg);
226 16     return 0;
227 }
228
229 static JSBool
230 namespace_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
231 0 {
232 0     JSXMLNamespace *ns, *ns2;
233 0     JSObject *obj2;
234
235 0     ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
236 0     JS_ASSERT(JSVAL_IS_OBJECT(v));
237 0     obj2 = JSVAL_TO_OBJECT(v);
238 0     if (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_NamespaceClass.base) {
239 0         *bp = JS_FALSE;
240     } else {
241 0         ns2 = (JSXMLNamespace *) JS_GetPrivate(cx, obj2);
242 0         *bp = !js_CompareStrings(ns->uri, ns2->uri);
243     }
244 0     return JS_TRUE;
245 }
246
247 JS_FRIEND_DATA(JSExtendedClass) js_NamespaceClass = {
248   { "Namespace",
249     JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED,
250     JS_PropertyStub,   JS_PropertyStub,   namespace_getProperty, NULL,
251     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    namespace_finalize,
252     NULL,              NULL,              NULL,              NULL,
253     NULL,              NULL,              namespace_mark,    NULL },
254     namespace_equality,
255     NULL, NULL,
256     JSCLASS_NO_RESERVED_MEMBERS
257 };
258
259 #define NAMESPACE_ATTRS                                                       \
260     (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
261
262 static JSPropertySpec namespace_props[] = {
263     {js_prefix_str,    NAMESPACE_PREFIX,  NAMESPACE_ATTRS,   0, 0},
264     {js_uri_str,       NAMESPACE_URI,     NAMESPACE_ATTRS,   0, 0},
265     {0,0,0,0,0}
266 };
267
268 static JSBool
269 namespace_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
270                    jsval *rval)
271 0 {
272 0     JSXMLNamespace *ns;
273
274 0     ns = (JSXMLNamespace *)
275          JS_GetInstancePrivate(cx, obj, &js_NamespaceClass.base, argv);
276 0     if (!ns)
277 0         return JS_FALSE;
278
279 0     *rval = STRING_TO_JSVAL(ns->uri);
280 0     return JS_TRUE;
281 }
282
283 static JSFunctionSpec namespace_methods[] = {
284     {js_toString_str,  namespace_toString,        0,0,0},
285     {0,0,0,0,0}
286 };
287
288 JSXMLNamespace *
289 js_NewXMLNamespace(JSContext *cx, JSString *prefix, JSString *uri,
290                    JSBool declared)
291 48 {
292 48     JSXMLNamespace *ns;
293
294 48     ns = (JSXMLNamespace *)
295          js_NewGCThing(cx, GCX_NAMESPACE, sizeof(JSXMLNamespace));
296 48     if (!ns)
297 0         return NULL;
298 48     ns->object = NULL;
299 48     ns->prefix = prefix;
300 48     ns->uri = uri;
301 48     ns->declared = declared;
302     METER(xml_stats.namespace);
303     METER(xml_stats.livenamespace);
304 48     return ns;
305 }
306
307 void
308 js_MarkXMLNamespace(JSContext *cx, JSXMLNamespace *ns, void *arg)
309 16 {
310 16     JS_MarkGCThing(cx, ns->object, js_object_str, arg);
311 16     JS_MarkGCThing(cx, ns->prefix, js_prefix_str, arg);
312 16     JS_MarkGCThing(cx, ns->uri, js_uri_str, arg);
313 }
314
315 void
316 js_FinalizeXMLNamespace(JSContext *cx, JSXMLNamespace *ns)
317 48 {
318     UNMETER(xml_stats.livenamespace);
319 }
320
321 JSObject *
322 js_NewXMLNamespaceObject(JSContext *cx, JSString *prefix, JSString *uri,
323                          JSBool declared)
324 0 {
325 0     JSXMLNamespace *ns;
326
327 0     ns = js_NewXMLNamespace(cx, prefix, uri, declared);
328 0     if (!ns)
329 0         return NULL;
330 0     return js_GetXMLNamespaceObject(cx, ns);
331 }
332
333 JSObject *
334 js_GetXMLNamespaceObject(JSContext *cx, JSXMLNamespace *ns)
335 0 {
336 0     JSObject *obj;
337
338 0     obj = ns->object;
339 0     if (obj) {
340 0         JS_ASSERT(JS_GetPrivate(cx, obj) == ns);
341 0         return obj;
342     }
343 0     obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
344 0     if (!obj || !JS_SetPrivate(cx, obj, ns)) {
345 0         cx->newborn[GCX_OBJECT] = NULL;
346 0         return NULL;
347     }
348 0     ns->object = obj;
349     METER(xml_stats.namespaceobj);
350     METER(xml_stats.livenamespaceobj);
351 0     return obj;
352 }
353
354 /*
355  * QName class and library functions.
356  */
357 enum qname_tinyid {
358     QNAME_URI = -1,
359     QNAME_LOCALNAME = -2
360 };
361
362 static JSBool
363 qname_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
364 0 {
365 0     JSXMLQName *qn;
366
367 0     if (!JSVAL_IS_INT(id))
368 0         return JS_TRUE;
369
370 0     qn = (JSXMLQName *)
371          JS_GetInstancePrivate(cx, obj, &js_QNameClass.base, NULL);
372 0     if (!qn)
373 0         return JS_TRUE;
374
375 0     switch (JSVAL_TO_INT(id)) {
376       case QNAME_URI:
377 0         *vp = qn->uri ? STRING_TO_JSVAL(qn->uri) : JSVAL_NULL;
378 0         break;
379       case QNAME_LOCALNAME:
380 0         *vp = STRING_TO_JSVAL(qn->localName);
381         break;
382     }
383 0     return JS_TRUE;
384 }
385
386 static void
387 qname_finalize(JSContext *cx, JSObject *obj)
388 48 {
389 48     JSXMLQName *qn;
390
391 48     qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
392 48     if (!qn)
393 0         return;
394 48     JS_ASSERT(qn->object == obj);
395 48     qn->object = NULL;
396     UNMETER(xml_stats.liveqnameobj);
397 }
398
399 static void
400 anyname_finalize(JSContext* cx, JSObject* obj)
401 16 {
402 16     JSRuntime *rt;
403
404     /* Make sure the next call to js_GetAnyName doesn't try to use obj. */
405 16     rt = cx->runtime;
406 16     if (rt->anynameObject == obj)
407 16         rt->anynameObject = NULL;
408
409 16     qname_finalize(cx, obj);
410 }
411
412 static uint32
413 qname_mark(JSContext *cx, JSObject *obj, void *arg)
414 32 {
415 32     JSXMLQName *qn;
416
417 32     qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
418 32     JS_MarkGCThing(cx, qn, js_private_str, arg);
419 32     return 0;
420 }
421
422 static JSBool
423 qname_identity(JSXMLQName *qna, JSXMLQName *qnb)
424 0 {
425 0     if (!qna->uri ^ !qnb->uri)
426 0         return JS_FALSE;
427 0     if (qna->uri && js_CompareStrings(qna->uri, qnb->uri))
428 0         return JS_FALSE;
429 0     return !js_CompareStrings(qna->localName, qnb->localName);
430 }
431
432 static JSBool
433 qname_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
434 0 {
435 0     JSXMLQName *qn, *qn2;
436 0     JSObject *obj2;
437
438 0     qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
439 0     JS_ASSERT(JSVAL_IS_OBJECT(v));
440 0     obj2 = JSVAL_TO_OBJECT(v);
441 0     if (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_QNameClass.base) {
442 0         *bp = JS_FALSE;
443     } else {
444 0         qn2 = (JSXMLQName *) JS_GetPrivate(cx, obj2);
445 0         *bp = qname_identity(qn, qn2);
446     }
447 0     return JS_TRUE;
448 }
449
450 JS_FRIEND_DATA(JSExtendedClass) js_QNameClass = {
451   { "QName",
452     JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED,
453     JS_PropertyStub,   JS_PropertyStub,   qname_getProperty, NULL,
454     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    qname_finalize,
455     NULL,              NULL,              NULL,              NULL,
456     NULL,              NULL,              qname_mark,        NULL },
457     qname_equality,
458     NULL, NULL,
459     JSCLASS_NO_RESERVED_MEMBERS
460 };
461
462 /*
463  * Classes for the ECMA-357-internal types AttributeName and AnyName, which
464  * are like QName, except that they have no property getters.  They share the
465  * qname_toString method, and therefore are exposed as constructable objects
466  * in this implementation.
467  */
468 JS_FRIEND_DATA(JSClass) js_AttributeNameClass = {
469     js_AttributeName_str, JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE,
470     JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,
471     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    qname_finalize,
472     NULL,              NULL,              NULL,              NULL,
473     NULL,              NULL,              qname_mark,        NULL
474 };
475
476 JS_FRIEND_DATA(JSClass) js_AnyNameClass = {
477     js_AnyName_str,    JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE,
478     JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,
479     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    anyname_finalize,
480     NULL,              NULL,              NULL,              NULL,
481     NULL,              NULL,              qname_mark,        NULL
482 };
483
484 #define QNAME_ATTRS                                                           \
485     (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
486
487 static JSPropertySpec qname_props[] = {
488     {js_uri_str,       QNAME_URI,         QNAME_ATTRS,       0, 0},
489     {js_localName_str, QNAME_LOCALNAME,   QNAME_ATTRS,       0, 0},
490     {0,0,0,0,0}
491 };
492
493 static JSBool
494 qname_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
495                jsval *rval)
496 0 {
497 0     JSClass *clasp;
498 0     JSXMLQName *qn;
499 0     JSString *str, *qualstr;
500 0     size_t length;
501 0     jschar *chars;
502
503 0     clasp = OBJ_GET_CLASS(cx, obj);
504 0     if (clasp == &js_AttributeNameClass || clasp == &js_AnyNameClass) {
505 0         qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
506     } else {
507 0         qn = (JSXMLQName *)
508              JS_GetInstancePrivate(cx, obj, &js_QNameClass.base, argv);
509 0         if (!qn)
510 0             return JS_FALSE;
511     }
512
513 0     if (!qn->uri) {
514         /* No uri means wildcard qualifier. */
515 0         str = ATOM_TO_STRING(cx->runtime->atomState.starQualifierAtom);
516 0     } else if (IS_EMPTY(qn->uri)) {
517         /* Empty string for uri means localName is in no namespace. */
518 0         str = cx->runtime->emptyString;
519     } else {
520 0         qualstr = ATOM_TO_STRING(cx->runtime->atomState.qualifierAtom);
521 0         str = js_ConcatStrings(cx, qn->uri, qualstr);
522 0         if (!str)
523 0             return JS_FALSE;
524     }
525 0     str = js_ConcatStrings(cx, str, qn->localName);
526 0     if (!str)
527 0         return JS_FALSE;
528
529 0     if (str && clasp == &js_AttributeNameClass) {
530 0         length = JSSTRING_LENGTH(str);
531 0         chars = (jschar *) JS_malloc(cx, (length + 2) * sizeof(jschar));
532 0         if (!chars)
533 0             return JS_FALSE;
534 0         *chars = '@';
535 0         js_strncpy(chars + 1, JSSTRING_CHARS(str), length);
536 0         chars[++length] = 0;
537 0         str = js_NewString(cx, chars, length, 0);
538 0         if (!str) {
539 0             JS_free(cx, chars);
540 0             return JS_FALSE;
541         }
542     }
543
544 0     *rval = STRING_TO_JSVAL(str);
545 0     return JS_TRUE;
546 }
547
548 static JSFunctionSpec qname_methods[] = {
549     {js_toString_str,  qname_toString,    0,0,0},
550     {0,0,0,0,0}
551 };
552
553 JSXMLQName *
554 js_NewXMLQName(JSContext *cx, JSString *uri, JSString *prefix,
555                JSString *localName)
556 48 {
557 48     JSXMLQName *qn;
558
559 48     qn = (JSXMLQName *) js_NewGCThing(cx, GCX_QNAME, sizeof(JSXMLQName));
560 48     if (!qn)
561 0         return NULL;
562 48     qn->object = NULL;
563 48     qn->uri = uri;
564 48     qn->prefix = prefix;
565 48     qn->localName = localName;
566     METER(xml_stats.qname);
567     METER(xml_stats.liveqname);
568 48     return qn;
569 }
570
571 void
572 js_MarkXMLQName(JSContext *cx, JSXMLQName *qn, void *arg)
573 32 {
574 32     JS_MarkGCThing(cx, qn->object, js_object_str, arg);
575 32     JS_MarkGCThing(cx, qn->uri, js_uri_str, arg);
576 32     JS_MarkGCThing(cx, qn->prefix, js_prefix_str, arg);
577 32     JS_MarkGCThing(cx, qn->localName, js_localName_str, arg);
578 }
579
580 void
581 js_FinalizeXMLQName(JSContext *cx, JSXMLQName *qn)
582 48 {
583     UNMETER(xml_stats.liveqname);
584 }
585
586 JSObject *
587 js_NewXMLQNameObject(JSContext *cx, JSString *uri, JSString *prefix,
588                      JSString *localName)
589 0 {
590 0     JSXMLQName *qn;
591
592 0     qn = js_NewXMLQName(cx, uri, prefix, localName);
593 0     if (!qn)
594 0         return NULL;
595 0     return js_GetXMLQNameObject(cx, qn);
596 }
597
598 JSObject *
599 js_GetXMLQNameObject(JSContext *cx, JSXMLQName *qn)
600 0 {
601 0     JSObject *obj;
602
603 0     obj = qn->object;
604 0     if (obj) {
605 0         JS_ASSERT(JS_GetPrivate(cx, obj) == qn);
606 0         return obj;
607     }
608 0     obj = js_NewObject(cx, &js_QNameClass.base, NULL, NULL);
609 0     if (!obj || !JS_SetPrivate(cx, obj, qn)) {
610 0         cx->newborn[GCX_OBJECT] = NULL;
611 0         return NULL;
612     }
613 0     qn->object = obj;
614     METER(xml_stats.qnameobj);
615     METER(xml_stats.liveqnameobj);
616 0     return obj;
617 }
618
619 JSObject *
620 js_GetAttributeNameObject(JSContext *cx, JSXMLQName *qn)
621 0 {
622 0     JSObject *obj;
623
624 0     obj = qn->object;
625 0     if (obj) {
626 0         if (OBJ_GET_CLASS(cx, obj) == &js_AttributeNameClass)
627 0             return obj;
628 0         qn = js_NewXMLQName(cx, qn->uri, qn->prefix, qn->localName);
629 0         if (!qn)
630 0             return NULL;
631     }
632
633 0     obj = js_NewObject(cx, &js_AttributeNameClass, NULL, NULL);
634 0     if (!obj || !JS_SetPrivate(cx, obj, qn)) {
635 0         cx->newborn[GCX_OBJECT] = NULL;
636 0         return NULL;
637     }
638
639 0     qn->object = obj;
640     METER(xml_stats.qnameobj);
641     METER(xml_stats.liveqnameobj);
642 0     return obj;
643 }
644
645 JSObject *
646 js_ConstructXMLQNameObject(JSContext *cx, jsval nsval, jsval lnval)
647 0 {
648 0     jsval argv[2];
649
650     /*
651      * ECMA-357 11.1.2,
652      * The _QualifiedIdentifier : PropertySelector :: PropertySelector_
653      * production, step 2.
654      */
655 0     if (!JSVAL_IS_PRIMITIVE(nsval) &&
656         OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nsval)) == &js_AnyNameClass) {
657 0         nsval = JSVAL_NULL;
658     }
659
660 0     argv[0] = nsval;
661 0     argv[1] = lnval;
662 0     return js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, argv);
663 }
664
665 static JSBool
666 IsXMLName(const jschar *cp, size_t n)
667 0 {
668 0     JSBool rv;
669 0     jschar c;
670
671 0     rv = JS_FALSE;
672 0     if (n != 0 && JS_ISXMLNSSTART(*cp)) {
673 0         while (--n != 0) {
674 0             c = *++cp;
675 0             if (!JS_ISXMLNS(c))
676 0                 return rv;
677         }
678 0         rv = JS_TRUE;
679     }
680 0     return rv;
681 }
682
683 JSBool
684 js_IsXMLName(JSContext *cx, jsval v)
685 0 {
686 0     JSClass *clasp;
687 0     JSXMLQName *qn;
688 0     JSString *name;
689 0     JSErrorReporter older;
690
691     /*
692      * Inline specialization of the QName constructor called with v passed as
693      * the only argument, to compute the localName for the constructed qname,
694      * without actually allocating the object or computing its uri and prefix.
695      * See ECMA-357 13.1.2.1 step 1 and 13.3.2.
696      */
697 0     if (!JSVAL_IS_PRIMITIVE(v) &&
698         (clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)),
699          clasp == &js_QNameClass.base ||
700          clasp == &js_AttributeNameClass ||
701          clasp == &js_AnyNameClass)) {
702 0         qn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
703 0         name = qn->localName;
704     } else {
705 0         older = JS_SetErrorReporter(cx, NULL);
706 0         name = js_ValueToString(cx, v);
707 0         JS_SetErrorReporter(cx, older);
708 0         if (!name) {
709 0             JS_ClearPendingException(cx);
710 0             return JS_FALSE;
711         }
712     }
713
714 0     return IsXMLName(JSSTRING_CHARS(name), JSSTRING_LENGTH(name));
715 }
716
717 static JSBool
718 Namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
719 48 {
720 48     jsval urival, prefixval;
721 48     JSObject *uriobj;
722 48     JSBool isNamespace, isQName;
723 48     JSClass *clasp;
724 48     JSString *empty, *prefix;
725 48     JSXMLNamespace *ns, *ns2;
726 48     JSXMLQName *qn;
727
728 48     urival = argv[argc > 1];
729 48     isNamespace = isQName = JS_FALSE;
730 48     if (!JSVAL_IS_PRIMITIVE(urival)) {
731 0         uriobj = JSVAL_TO_OBJECT(urival);
732 0         clasp = OBJ_GET_CLASS(cx, uriobj);
733 0         isNamespace = (clasp == &js_NamespaceClass.base);
734 0         isQName = (clasp == &js_QNameClass.base);
735     }
736 #ifdef __GNUC__         /* suppress bogus gcc warnings */
737 48     else uriobj = NULL;
738 #endif
739
740 48     if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
741         /* Namespace called as function. */
742 0         if (argc == 1 && isNamespace) {
743             /* Namespace called with one Namespace argument is identity. */
744 0             *rval = urival;
745 0             return JS_TRUE;
746         }
747
748         /* Create and return a new QName object exactly as if constructed. */
749 0         obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
750 0         if (!obj)
751 0             return JS_FALSE;
752 0         *rval = OBJECT_TO_JSVAL(obj);
753     }
754     METER(xml_stats.namespaceobj);
755     METER(xml_stats.livenamespaceobj);
756
757     /*
758      * Create and connect private data to rooted obj early, so we don't have
759      * to worry about rooting string newborns hanging off of the private data
760      * further below.
761      */
762 48     empty = cx->runtime->emptyString;
763 48     ns = js_NewXMLNamespace(cx, empty, empty, JS_FALSE);
764 48     if (!ns)
765 0         return JS_FALSE;
766 48     if (!JS_SetPrivate(cx, obj, ns))
767 0         return JS_FALSE;
768 48     ns->object = obj;
769
770 48     if (argc == 1) {
771 0         if (isNamespace) {
772 0             ns2 = (JSXMLNamespace *) JS_GetPrivate(cx, uriobj);
773 0             ns->uri = ns2->uri;
774 0             ns->prefix = ns2->prefix;
775 0         } else if (isQName &&
776                    (qn = (JSXMLQName *) JS_GetPrivate(cx, uriobj))->uri) {
777 0             ns->uri = qn->uri;
778 0             ns->prefix = qn->prefix;
779         } else {
780 0             ns->uri = js_ValueToString(cx, urival);
781 0             if (!ns->uri)
782 0                 return JS_FALSE;
783
784             /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
785 0             if (!IS_EMPTY(ns->uri))
786 0                 ns->prefix = NULL;
787         }
788 48     } else if (argc == 2) {
789 0         if (isQName &&
790             (qn = (JSXMLQName *) JS_GetPrivate(cx, uriobj))->uri) {
791 0             ns->uri = qn->uri;
792         } else {
793 0             ns->uri = js_ValueToString(cx, urival);
794 0             if (!ns->uri)
795 0                 return JS_FALSE;
796         }
797
798 0         prefixval = argv[0];
799 0         if (IS_EMPTY(ns->uri)) {
800 0             if (!JSVAL_IS_VOID(prefixval)) {
801 0                 prefix = js_ValueToString(cx, prefixval);
802 0                 if (!prefix)
803 0                     return JS_FALSE;
804 0                 if (!IS_EMPTY(prefix)) {
805 0                     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
806                                          JSMSG_BAD_XML_NAMESPACE,
807                                          js_ValueToPrintableString(cx,
808                                              STRING_TO_JSVAL(prefix)));
809 0                     return JS_FALSE;
810                 }
811             }
812 0         } else if (JSVAL_IS_VOID(prefixval) || !js_IsXMLName(cx, prefixval)) {
813             /* NULL here represents *undefined* in ECMA-357 13.2.2 4(d) etc. */
814 0             ns->prefix = NULL;
815         } else {
816 0             prefix = js_ValueToString(cx, prefixval);
817 0             if (!prefix)
818 0                 return JS_FALSE;
819 0             ns->prefix = prefix;
820         }
821     }
822
823 48     return JS_TRUE;
824 }
825
826 static JSBool
827 QName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
828 32 {
829 32     jsval nameval, nsval;
830 32     JSBool isQName, isNamespace;
831 32     JSXMLQName *qn;
832 32     JSString *uri, *prefix, *name;
833 32     JSObject *nsobj;
834 32     JSClass *clasp;
835 32     JSXMLNamespace *ns;
836
837 32     nameval = argv[argc > 1];
838 32     isQName =
839         !JSVAL_IS_PRIMITIVE(nameval) &&
840         OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nameval)) == &js_QNameClass.base;
841
842 32     if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
843         /* QName called as function. */
844 0         if (argc == 1 && isQName) {
845             /* QName called with one QName argument is identity. */
846 0             *rval = nameval;
847 0             return JS_TRUE;
848         }
849
850         /*
851          * Create and return a new QName object exactly as if constructed.
852          * Use the constructor's clasp so we can be shared by AttributeName
853          * (see below after this function).
854          */
855 0         obj = js_NewObject(cx,
856                            argv
857                            ? JS_ValueToFunction(cx, argv[-2])->clasp
858                            : &js_QNameClass.base,
859                            NULL, NULL);
860 0         if (!obj)
861 0             return JS_FALSE;
862 0         *rval = OBJECT_TO_JSVAL(obj);
863     }
864     METER(xml_stats.qnameobj);
865     METER(xml_stats.liveqnameobj);
866
867 32     if (isQName) {
868         /* If namespace is not specified and name is a QName, clone it. */
869 0         qn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nameval));
870 0         if (argc == 1) {
871 0             uri = qn->uri;
872 0             prefix = qn->prefix;
873 0             name = qn->localName;
874 0             goto out;
875         }
876
877         /* Namespace and qname were passed -- use the qname's localName. */
878 0         nameval = STRING_TO_JSVAL(qn->localName);
879     }
880
881 32     if (argc == 0) {
882 32         name = cx->runtime->emptyString;
883     } else {
884 0         name = js_ValueToString(cx, nameval);
885 0         if (!name)
886 0             return JS_FALSE;
887
888         /* Use argv[1] as a local root for name, even if it was not passed. */
889 0         argv[1] = STRING_TO_JSVAL(name);
890     }
891
892 32     nsval = argv[0];
893 32     if (argc == 1 || JSVAL_IS_VOID(nsval)) {
894 32         if (IS_STAR(name)) {
895 0             nsval = JSVAL_NULL;
896         } else {
897 32             if (!js_GetDefaultXMLNamespace(cx, &nsval))
898 0                 return JS_FALSE;
899         }
900     }
901
902 32     if (JSVAL_IS_NULL(nsval)) {
903         /* NULL prefix represents *undefined* in ECMA-357 13.3.2 5(a). */
904 0         uri = prefix = NULL;
905     } else {
906         /*
907          * Inline specialization of the Namespace constructor called with
908          * nsval passed as the only argument, to compute the uri and prefix
909          * for the constructed namespace, without actually allocating the
910          * object or computing other members.  See ECMA-357 13.3.2 6(a) and
911          * 13.2.2.
912          */
913 32         isNamespace = isQName = JS_FALSE;
914 32         if (!JSVAL_IS_PRIMITIVE(nsval)) {
915 32             nsobj = JSVAL_TO_OBJECT(nsval);
916 32             clasp = OBJ_GET_CLASS(cx, nsobj);
917 32             isNamespace = (clasp == &js_NamespaceClass.base);
918 32             isQName = (clasp == &js_QNameClass.base);
919         }
920 #ifdef __GNUC__         /* suppress bogus gcc warnings */
921 0         else nsobj = NULL;
922 #endif
923
924 32         if (isNamespace) {
925 32             ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
926 32             uri = ns->uri;
927 32             prefix = ns->prefix;
928 0         } else if (isQName &&
929                    (qn = (JSXMLQName *) JS_GetPrivate(cx, nsobj))->uri) {
930 0             uri = qn->uri;
931 0             prefix = qn->prefix;
932         } else {
933 0             uri = js_ValueToString(cx, nsval);
934 0             if (!uri)
935 0                 return JS_FALSE;
936 0             argv[0] = STRING_TO_JSVAL(uri);     /* local root */
937
938             /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
939 0             prefix = IS_EMPTY(uri) ? cx->runtime->emptyString : NULL;
940         }
941     }
942
943 out:
944 32     qn = js_NewXMLQName(cx, uri, prefix, name);
945 32     if (!qn)
946 0         return JS_FALSE;
947 32     if (!JS_SetPrivate(cx, obj, qn))
948 0         return JS_FALSE;
949 32     qn->object = obj;
950 32     return JS_TRUE;
951 }
952
953 static JSBool
954 AttributeName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
955               jsval *rval)
956 16 {
957     /*
958      * Since js_AttributeNameClass was initialized, obj will have that as its
959      * class, not js_QNameClass.
960      */
961 16     return QName(cx, obj, argc, argv, rval);
962 }
963
964 /*
965  * XMLArray library functions.
966  */
967 static JSBool
968 namespace_identity(const void *a, const void *b)
969 0 {
970 0     const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
971 0     const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
972
973 0     if (nsa->prefix && nsb->prefix) {
974 0         if (js_CompareStrings(nsa->prefix, nsb->prefix))
975 0             return JS_FALSE;
976     } else {
977 0         if (nsa->prefix || nsb->prefix)
978 0             return JS_FALSE;
979     }
980 0     return !js_CompareStrings(nsa->uri, nsb->uri);
981 }
982
983 static JSBool
984 attr_identity(const void *a, const void *b)
985 0 {
986 0     const JSXML *xmla = (const JSXML *) a;
987 0     const JSXML *xmlb = (const JSXML *) b;
988
989 0     return qname_identity(xmla->name, xmlb->name);
990 }
991
992 static void
993 XMLArrayCursorInit(JSXMLArrayCursor *cursor, JSXMLArray *array)
994 0 {
995 0     JSXMLArrayCursor *next;
996
997 0     cursor->array = array;
998 0     cursor->index = 0;
999 0     next = cursor->next = array->cursors;
1000 0     if (next)
1001 0         next->prevp = &cursor->next;
1002 0     cursor->prevp = &array->cursors;
1003 0     array->cursors = cursor;
1004 0     cursor->root = NULL;
1005 }
1006
1007 static void
1008 XMLArrayCursorFinish(JSXMLArrayCursor *cursor)
1009 0 {
1010 0     JSXMLArrayCursor *next;
1011
1012 0     if (!cursor->array)
1013 0         return;
1014 0     next = cursor->next;
1015 0     if (next)
1016 0         next->prevp = cursor->prevp;
1017 0     *cursor->prevp = next;
1018 0     cursor->array = NULL;
1019 }
1020
1021 static void *
1022 XMLArrayCursorNext(JSXMLArrayCursor *cursor)
1023 0 {
1024 0     JSXMLArray *array;
1025
1026 0     array = cursor->array;
1027 0     if (!array || cursor->index >= array->length)
1028 0         return NULL;
1029 0     return cursor->root = array->vector[cursor->index++];
1030 }
1031
1032 static void *
1033 XMLArrayCursorItem(JSXMLArrayCursor *cursor)
1034 0 {
1035 0     JSXMLArray *array;
1036
1037 0     array = cursor->array;
1038 0     if (!array || cursor->index >= array->length)
1039 0         return NULL;
1040 0     return cursor->root = array->vector[cursor->index];
1041 }
1042
1043 static void
1044 XMLArrayCursorMark(JSContext *cx, JSXMLArrayCursor *cursor)
1045 0 {
1046 0     while (cursor) {
1047 0         GC_MARK(cx, cursor->root, "cursor->root", NULL);
1048 0         cursor = cursor->next;
1049     }
1050 }
1051
1052 /* NB: called with null cx from the GC, via xml_mark => XMLArrayTrim. */
1053 static JSBool
1054 XMLArraySetCapacity(JSContext *cx, JSXMLArray *array, uint32 capacity)
1055 0 {
1056 0     void **vector;
1057
1058 0     if (capacity == 0) {
1059         /* We could let realloc(p, 0) free this, but purify gets confused. */
1060 0         if (array->vector)
1061 0             free(array->vector);
1062 0         vector = NULL;
1063     } else {
1064 0         if ((size_t)capacity > ~(size_t)0 / sizeof(void *) ||
1065             !(vector = (void **)
1066                        realloc(array->vector, capacity * sizeof(void *)))) {
1067 0             if (cx)
1068 0                 JS_ReportOutOfMemory(cx);
1069 0             return JS_FALSE;
1070         }
1071     }
1072 0     array->capacity = JSXML_PRESET_CAPACITY | capacity;
1073 0     array->vector = vector;
1074 0     return JS_TRUE;
1075 }
1076
1077 static void
1078 XMLArrayTrim(JSXMLArray *array)
1079 0 {
1080 0     if (array->capacity & JSXML_PRESET_CAPACITY)
1081 0         return;
1082 0     if (array->length < array->capacity)
1083 0         XMLArraySetCapacity(NULL, array, array->length);
1084 }
1085
1086 static JSBool
1087 XMLArrayInit(JSContext *cx, JSXMLArray *array, uint32 capacity)
1088 0 {
1089 0     array->length = array->capacity = 0;
1090 0     array->vector = NULL;
1091 0     array->cursors = NULL;
1092 0     return capacity == 0 || XMLArraySetCapacity(cx, array, capacity);
1093 }
1094
1095 static void
1096 XMLArrayFinish(JSContext *cx, JSXMLArray *array)
1097 0 {
1098 0     JSXMLArrayCursor *cursor;
1099
1100 0     JS_free(cx, array->vector);
1101
1102 0     while ((cursor = array->cursors) != NULL)
1103 0         XMLArrayCursorFinish(cursor);
1104
1105 #ifdef DEBUG
1106     memset(array, 0xd5, sizeof *array);
1107 #endif
1108 }
1109
1110 #define XML_NOT_FOUND   ((uint32) -1)
1111
1112 static uint32
1113 XMLArrayFindMember(const JSXMLArray *array, void *elt, JSIdentityOp identity)
1114 0 {
1115 0     void **vector;
1116 0     uint32 i, n;
1117
1118     /* The identity op must not reallocate array->vector. */
1119 0     vector = array->vector;
1120 0     if (identity) {
1121 0         for (i = 0, n = array->length; i < n; i++) {
1122 0             if (identity(vector[i], elt))
1123 0                 return i;
1124         }
1125     } else {
1126 0         for (i = 0, n = array->length; i < n; i++) {
1127 0             if (vector[i] == elt)
1128 0                 return i;
1129         }
1130     }
1131 0     return XML_NOT_FOUND;
1132 }
1133
1134 /*
1135  * Grow array vector capacity by powers of two to LINEAR_THRESHOLD, and after
1136  * that, grow by LINEAR_INCREMENT.  Both must be powers of two, and threshold
1137  * should be greater than increment.
1138  */
1139 #define LINEAR_THRESHOLD        256
1140 #define LINEAR_INCREMENT        32
1141
1142 static JSBool
1143 XMLArrayAddMember(JSContext *cx, JSXMLArray *array, uint32 index, void *elt)
1144 0 {
1145 0     uint32 capacity, i;
1146 0     int log2;
1147 0     void **vector;
1148
1149 0     if (index >= array->length) {
1150 0         if (index >= JSXML_CAPACITY(array)) {
1151             /* Arrange to clear JSXML_PRESET_CAPACITY from array->capacity. */
1152 0             capacity = index + 1;
1153 0             if (index >= LINEAR_THRESHOLD) {
1154 0                 capacity = JS_ROUNDUP(capacity, LINEAR_INCREMENT);
1155             } else {
1156 0                 JS_CEILING_LOG2(log2, capacity);
1157 0                 capacity = JS_BIT(log2);
1158             }
1159 0             if ((size_t)capacity > ~(size_t)0 / sizeof(void *) ||
1160                 !(vector = (void **)
1161                            realloc(array->vector, capacity * sizeof(void *)))) {
1162 0                 JS_ReportOutOfMemory(cx);
1163 0                 return JS_FALSE;
1164             }
1165 0             array->capacity = capacity;
1166 0             array->vector = vector;
1167 0             for (i = array->length; i < index; i++)
1168 0                 vector[i] = NULL;
1169         }
1170 0         array->length = index + 1;
1171     }
1172
1173 0     array->vector[index] = elt;
1174 0     return JS_TRUE;
1175 }
1176
1177 static JSBool
1178 XMLArrayInsert(JSContext *cx, JSXMLArray *array, uint32 i, uint32 n)
1179 0 {
1180 0     uint32 j;
1181 0     JSXMLArrayCursor *cursor;
1182
1183 0     j = array->length;
1184 0     JS_ASSERT(i <= j);
1185 0     if (!XMLArraySetCapacity(cx, array, j + n))
1186 0         return JS_FALSE;
1187
1188 0     array->length = j + n;
1189 0     JS_ASSERT(n != (uint32)-1);
1190 0     while (j != i) {
1191 0         --j;
1192 0         array->vector[j + n] = array->vector[j];
1193     }
1194
1195 0     for (cursor = array->cursors; cursor; cursor = cursor->next) {
1196 0         if (cursor->index > i)
1197 0             cursor->index += n;
1198     }
1199 0     return JS_TRUE;
1200 }
1201
1202 static void *
1203 XMLArrayDelete(JSContext *cx, JSXMLArray *array, uint32 index, JSBool compress)
1204 0 {
1205 0     uint32 length;
1206 0     void **vector, *elt;
1207 0     JSXMLArrayCursor *cursor;
1208
1209 0     length = array->length;
1210 0     if (index >= length)
1211 0         return NULL;
1212
1213 0     vector = array->vector;
1214 0     elt = vector[index];
1215 0     if (compress) {
1216 0         while (++index < length)
1217 0             vector[index-1] = vector[index];
1218 0         array->length = length - 1;
1219 0         array->capacity = JSXML_CAPACITY(array);
1220     } else {
1221 0         vector[index] = NULL;
1222     }
1223
1224 0     for (cursor = array->cursors; cursor; cursor = cursor->next) {
1225 0         if (cursor->index > index)
1226 0             --cursor->index;
1227     }
1228 0     return elt;
1229 }
1230
1231 static void
1232 XMLArrayTruncate(JSContext *cx, JSXMLArray *array, uint32 length)
1233 0 {
1234 0     void **vector;
1235
1236 0     JS_ASSERT(!array->cursors);
1237 0     if (length >= array->length)
1238 0         return;
1239
1240 0     if (length == 0) {
1241 0         if (array->vector)
1242 0             free(array->vector);
1243 0         vector = NULL;
1244     } else {
1245 0         vector = realloc(array->vector, length * sizeof(void *));
1246 0         if (!vector)
1247 0             return;
1248     }
1249
1250 0     if (array->length > length)
1251 0         array->length = length;
1252 0     array->capacity = length;
1253 0     array->vector = vector;
1254 }
1255
1256 #define XMLARRAY_FIND_MEMBER(a,e,f) XMLArrayFindMember(a, (void *)(e), f)
1257 #define XMLARRAY_HAS_MEMBER(a,e,f)  (XMLArrayFindMember(a, (void *)(e), f) != \
1258                                      XML_NOT_FOUND)
1259 #define XMLARRAY_MEMBER(a,i,t)      (((i) < (a)->length)                      \
1260                                      ? (t *) (a)->vector[i]                   \
1261                                      : NULL)
1262 #define XMLARRAY_SET_MEMBER(a,i,e)  JS_BEGIN_MACRO                            \
1263                                         if ((a)->length <= (i))               \
1264                                             (a)->length = (i) + 1;            \
1265                                         ((a)->vector[i] = (void *)(e));       \
1266                                     JS_END_MACRO
1267 #define XMLARRAY_ADD_MEMBER(x,a,i,e)XMLArrayAddMember(x, a, i, (void *)(e))
1268 #define XMLARRAY_INSERT(x,a,i,n)    XMLArrayInsert(x, a, i, n)
1269 #define XMLARRAY_APPEND(x,a,e)      XMLARRAY_ADD_MEMBER(x, a, (a)->length, (e))
1270 #define XMLARRAY_DELETE(x,a,i,c,t)  ((t *) XMLArrayDelete(x, a, i, c))
1271 #define XMLARRAY_TRUNCATE(x,a,n)    XMLArrayTruncate(x, a, n)
1272
1273 /*
1274  * Define XML setting property strings and constants early, so everyone can
1275  * use the same names and their magic numbers (tinyids, flags).
1276  */
1277 static const char js_ignoreComments_str[]   = "ignoreComments";
1278 static const char js_ignoreProcessingInstructions_str[]
1279                                             = "ignoreProcessingInstructions";
1280 static const char js_ignoreWhitespace_str[] = "ignoreWhitespace";
1281 static const char js_prettyPrinting_str[]   = "prettyPrinting";
1282 static const char js_prettyIndent_str[]     = "prettyIndent";
1283
1284 /*
1285  * NB: These XML static property tinyids must
1286  * (a) not collide with the generic negative tinyids at the top of jsfun.c;
1287  * (b) index their corresponding xml_static_props array elements.
1288  * Don't change 'em!
1289  */
1290 enum xml_static_tinyid {
1291     XML_IGNORE_COMMENTS,
1292     XML_IGNORE_PROCESSING_INSTRUCTIONS,
1293     XML_IGNORE_WHITESPACE,
1294     XML_PRETTY_PRINTING,
1295     XML_PRETTY_INDENT
1296 };
1297
1298 static JSBool
1299 xml_setting_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
1300 0 {
1301 0     return JS_TRUE;
1302 }
1303
1304 static JSBool
1305 xml_setting_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
1306 64 {
1307 64     JSBool b;
1308 64     uint8 flag;
1309
1310 64     JS_ASSERT(JSVAL_IS_INT(id));
1311 64     if (!js_ValueToBoolean(cx, *vp, &b))
1312 0         return JS_FALSE;
1313
1314 64     flag = JS_BIT(JSVAL_TO_INT(id));
1315 64     if (b)
1316 64         cx->xmlSettingFlags |= flag;
1317     else
1318 0         cx->xmlSettingFlags &= ~flag;
1319 64     return JS_TRUE;
1320 }
1321
1322 static JSPropertySpec xml_static_props[] = {
1323     {js_ignoreComments_str,     XML_IGNORE_COMMENTS,   JSPROP_PERMANENT,
1324                                 xml_setting_getter, xml_setting_setter},
1325     {js_ignoreProcessingInstructions_str,
1326                    XML_IGNORE_PROCESSING_INSTRUCTIONS, JSPROP_PERMANENT,
1327                                 xml_setting_getter, xml_setting_setter},
1328     {js_ignoreWhitespace_str,   XML_IGNORE_WHITESPACE, JSPROP_PERMANENT,
1329                                 xml_setting_getter, xml_setting_setter},
1330     {js_prettyPrinting_str,     XML_PRETTY_PRINTING,   JSPROP_PERMANENT,
1331                                 xml_setting_getter, xml_setting_setter},
1332     {js_prettyIndent_str,       XML_PRETTY_INDENT,     JSPROP_PERMANENT,
1333                                 xml_setting_getter, NULL},
1334     {0,0,0,0,0}
1335 };
1336
1337 /* Derive cx->xmlSettingFlags bits from xml_static_props tinyids. */
1338 #define XSF_IGNORE_COMMENTS     JS_BIT(XML_IGNORE_COMMENTS)
1339 #define XSF_IGNORE_PROCESSING_INSTRUCTIONS                                    \
1340                                 JS_BIT(XML_IGNORE_PROCESSING_INSTRUCTIONS)
1341 #define XSF_IGNORE_WHITESPACE   JS_BIT(XML_IGNORE_WHITESPACE)
1342 #define XSF_PRETTY_PRINTING     JS_BIT(XML_PRETTY_PRINTING)
1343 #define XSF_CACHE_VALID         JS_BIT(XML_PRETTY_INDENT)
1344
1345 /*
1346  * Extra, unrelated but necessarily disjoint flag used by ParseNodeToXML.
1347  * This flag means a couple of things:
1348  *
1349  * - The top JSXML created for a parse tree must have an object owning it.
1350  *
1351  * - That the default namespace normally inherited from the temporary
1352  *   <parent xmlns='...'> tag that wraps a runtime-concatenated XML source
1353  *   string must, in the case of a precompiled XML object tree, inherit via
1354  *   ad-hoc code in ParseNodeToXML.
1355  *
1356  * Because of the second purpose, we name this flag XSF_PRECOMPILED_ROOT.
1357  */
1358 #define XSF_PRECOMPILED_ROOT    (XSF_CACHE_VALID << 1)
1359
1360 /* Macros for special-casing xml:, xmlns= and xmlns:foo= in ParseNodeToQName. */
1361 #define IS_XML(str)                                                           \
1362     (JSSTRING_LENGTH(str) == 3 && IS_XML_CHARS(JSSTRING_CHARS(str)))
1363
1364 #define IS_XMLNS(str)                                                         \
1365     (JSSTRING_LENGTH(str) == 5 && IS_XMLNS_CHARS(JSSTRING_CHARS(str)))
1366
1367 #define IS_XML_CHARS(chars)                                                   \
1368     (JS_TOLOWER((chars)[0]) == 'x' &&                                         \
1369      JS_TOLOWER((chars)[1]) == 'm' &&                                         \
1370      JS_TOLOWER((chars)[2]) == 'l')
1371
1372 #define HAS_NS_AFTER_XML(chars)                                               \
1373     (JS_TOLOWER((chars)[3]) == 'n' &&                                         \
1374      JS_TOLOWER((chars)[4]) == 's')
1375
1376 #define IS_XMLNS_CHARS(chars)                                                 \
1377     (IS_XML_CHARS(chars) && HAS_NS_AFTER_XML(chars))
1378
1379 #define STARTS_WITH_XML(chars,length)                                         \
1380     (length >= 3 && IS_XML_CHARS(chars))
1381
1382 static const char xml_namespace_str[] = "http://www.w3.org/XML/1998/namespace";
1383 static const char xmlns_namespace_str[] = "http://www.w3.org/2000/xmlns/";
1384
1385 static JSXMLQName *
1386 ParseNodeToQName(JSContext *cx, JSParseNode *pn, JSXMLArray *inScopeNSes,
1387                  JSBool isAttributeName)
1388 0 {
1389 0     JSString *str, *uri, *prefix, *localName;
1390 0     size_t length, offset;
1391 0     const jschar *start, *limit, *colon;
1392 0     uint32 n;
1393 0     JSXMLNamespace *ns;
1394
1395 0     JS_ASSERT(pn->pn_arity == PN_NULLARY);
1396 0     str = ATOM_TO_STRING(pn->pn_atom);
1397 0     length = JSSTRING_LENGTH(str);
1398 0     start = JSSTRING_CHARS(str);
1399 0     JS_ASSERT(length != 0 && *start != '@');
1400 0     JS_ASSERT(length != 1 || *start != '*');
1401
1402 0     uri = cx->runtime->emptyString;
1403 0     limit = start + length;
1404 0     colon = js_strchr_limit(start, ':', limit);
1405 0     if (colon) {
1406 0         offset = PTRDIFF(colon, start, jschar);
1407 0         prefix = js_NewDependentString(cx, str, 0, offset, 0);
1408 0         if (!prefix)
1409 0             return NULL;
1410
1411 0         if (STARTS_WITH_XML(start, offset)) {
1412 0             if (offset == 3) {
1413 0                 uri = JS_InternString(cx, xml_namespace_str);
1414 0                 if (!uri)
1415 0                     return NULL;
1416 0             } else if (offset == 5 && HAS_NS_AFTER_XML(start)) {
1417 0                 uri = JS_InternString(cx, xmlns_namespace_str);
1418 0                 if (!uri)
1419 0                     return NULL;
1420             } else {
1421 0                 uri = NULL;
1422             }
1423         } else {
1424 0             uri = NULL;
1425 0             n = inScopeNSes->length;
1426 0             while (n != 0) {
1427 0                 --n;
1428 0                 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSXMLNamespace);
1429 0                 if (ns->prefix && !js_CompareStrings(ns->prefix, prefix)) {
1430 0                     uri = ns->uri;
1431                     break;
1432                 }
1433             }
1434         }
1435
1436 0         if (!uri) {
1437 0             js_ReportCompileErrorNumber(cx, pn,
1438                                         JSREPORT_PN | JSREPORT_ERROR,
1439                                         JSMSG_BAD_XML_NAMESPACE,
1440                                         js_ValueToPrintableString(cx,
1441                                             STRING_TO_JSVAL(prefix)));
1442 0             return NULL;
1443         }
1444
1445 0         localName = js_NewStringCopyN(cx, colon + 1, length - (offset + 1), 0);
1446 0         if (!localName)
1447 0             return NULL;
1448     } else {
1449 0         if (isAttributeName) {
1450             /*
1451              * An unprefixed attribute is not in any namespace, so set prefix
1452              * as well as uri to the empty string.
1453              */
1454 0             prefix = uri;
1455         } else {
1456             /*
1457              * Loop from back to front looking for the closest declared default
1458              * namespace.
1459              */
1460 0             n = inScopeNSes->length;
1461 0             while (n != 0) {
1462 0                 --n;
1463 0                 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSXMLNamespace);
1464 0                 if (!ns->prefix || IS_EMPTY(ns->prefix)) {
1465 0                     uri = ns->uri;
1466                     break;
1467                 }
1468             }
1469 0             prefix = NULL;
1470         }
1471 0         localName = str;
1472     }
1473
1474 0     return js_NewXMLQName(cx, uri, prefix, localName);
1475 }
1476
1477 static JSString *
1478 ChompXMLWhitespace(JSContext *cx, JSString *str)
1479 0 {
1480 0     size_t length, newlength, offset;
1481 0     const jschar *cp, *start, *end;
1482 0     jschar c;
1483
1484 0     length = JSSTRING_LENGTH(str);
1485 0     for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
1486 0         c = *cp;
1487 0         if (!JS_ISXMLSPACE(c))
1488 0             break;
1489     }
1490 0     while (end > cp) {
1491 0         c = end[-1];
1492 0         if (!JS_ISXMLSPACE(c))
1493 0             break;
1494 0         --end;
1495     }
1496 0     newlength = PTRDIFF(end, cp, jschar);
1497 0     if (newlength == length)
1498 0         return str;
1499 0     offset = PTRDIFF(cp, start, jschar);
1500 0     return js_NewDependentString(cx, str, offset, newlength, 0);
1501 }
1502
1503 static JSXML *
1504 ParseNodeToXML(JSContext *cx, JSParseNode *pn, JSXMLArray *inScopeNSes,
1505                uintN flags)
1506 0 {
1507 0     JSXML *xml, *kid, *attr, *attrj;
1508 0     JSString *str;
1509 0     uint32 length, n, i, j;
1510 0     JSParseNode *pn2, *pn3, *head, **pnp;
1511 0     JSXMLNamespace *ns;
1512 0     JSXMLQName *qn, *attrjqn;
1513 0     JSXMLClass xml_class;
1514
1515 #define PN2X_SKIP_CHILD ((JSXML *) 1)
1516
1517     /*
1518      * Cases return early to avoid common code that gets an outermost xml's
1519      * object, which protects GC-things owned by xml and its descendants from
1520      * garbage collection.
1521      */
1522 0     xml = NULL;
1523 0     if (!JS_EnterLocalRootScope(cx))
1524 0         return NULL;
1525 0     switch (pn->pn_type) {
1526       case TOK_XMLELEM:
1527 0         length = inScopeNSes->length;
1528 0         pn2 = pn->pn_head;
1529 0         xml = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1530 0         if (!xml)
1531 0             goto fail;
1532 0         if (js_PushLocalRoot(cx, cx->localRootStack, (jsval)xml) < 0)
1533 0             goto fail;
1534
1535 0         flags &= ~XSF_PRECOMPILED_ROOT;
1536 0         n = pn->pn_count;
1537 0         JS_ASSERT(n >= 2);
1538 0         n -= 2;
1539 0         if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
1540 0             goto fail;
1541
1542 0         i = 0;
1543 0         while ((pn2 = pn2->pn_next) != NULL) {
1544 0             if (!pn2->pn_next) {
1545                 /* Don't append the end tag! */
1546 0                 JS_ASSERT(pn2->pn_type == TOK_XMLETAGO);
1547 0                 break;
1548             }
1549
1550 0             if ((flags & XSF_IGNORE_WHITESPACE) &&
1551                 n > 1 && pn2->pn_type == TOK_XMLSPACE) {
1552 0                 --n;
1553 0                 continue;
1554             }
1555
1556 0             kid = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1557 0             if (kid == PN2X_SKIP_CHILD) {
1558 0                 --n;
1559 0                 continue;
1560             }
1561
1562 0             if (!kid)
1563 0                 goto fail;
1564
1565             /* Store kid in xml right away, to protect it from GC. */
1566 0             XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1567 0             kid->parent = xml;
1568 0             ++i;
1569
1570             /* XXX where is this documented in an XML spec, or in E4X? */
1571 0             if ((flags & XSF_IGNORE_WHITESPACE) &&
1572                 n > 1 && kid->xml_class == JSXML_CLASS_TEXT) {
1573 0                 str = ChompXMLWhitespace(cx, kid->xml_value);
1574 0                 if (!str)
1575 0                     goto fail;
1576 0                 kid->xml_value = str;
1577             }
1578         }
1579
1580 0         JS_ASSERT(i == n);
1581 0         if (n < pn->pn_count - 2)
1582 0             XMLArrayTrim(&xml->xml_kids);
1583 0         XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1584 0         break;
1585
1586       case TOK_XMLLIST:
1587 0         xml = js_NewXML(cx, JSXML_CLASS_LIST);
1588 0         if (!xml)
1589 0             goto fail;
1590
1591 0         n = pn->pn_count;
1592 0         if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
1593 0             goto fail;
1594
1595 0         i = 0;
1596 0         for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
1597             /*
1598              * Always ignore insignificant whitespace in lists -- we shouldn't
1599              * condition this on an XML.ignoreWhitespace setting when the list
1600              * constructor is XMLList (note XML/XMLList unification hazard).
1601              */
1602 0             if (pn2->pn_type == TOK_XMLSPACE) {
1603 0                 --n;
1604 0                 continue;
1605             }
1606
1607 0             kid = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1608 0             if (kid == PN2X_SKIP_CHILD) {
1609 0                 --n;
1610 0                 continue;
1611             }
1612
1613 0             if (!kid)
1614 0                 goto fail;
1615
1616 0             XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1617 0             ++i;
1618         }
1619
1620 0         if (n < pn->pn_count)
1621 0             XMLArrayTrim(&xml->xml_kids);
1622 0         break;
1623
1624       case TOK_XMLSTAGO:
1625       case TOK_XMLPTAGC:
1626 0         length = inScopeNSes->length;
1627 0         pn2 = pn->pn_head;
1628 0         JS_ASSERT(pn2->pn_type == TOK_XMLNAME);
1629 0         if (pn2->pn_arity == PN_LIST)
1630 0             goto syntax;
1631
1632 0         xml = js_NewXML(cx, JSXML_CLASS_ELEMENT);
1633 0         if (!xml)
1634 0             goto fail;
1635
1636         /* First pass: check syntax and process namespace declarations. */
1637 0         JS_ASSERT(pn->pn_count >= 1);
1638 0         n = pn->pn_count - 1;
1639 0         pnp = &pn2->pn_next;
1640 0         head = *pnp;
1641 0         while ((pn2 = *pnp) != NULL) {
1642 0             size_t length;
1643 0             const jschar *chars;
1644
1645 0             if (pn2->pn_type != TOK_XMLNAME || pn2->pn_arity != PN_NULLARY)
1646 0                 goto syntax;
1647
1648             /* Enforce "Well-formedness constraint: Unique Att Spec". */
1649 0             for (pn3 = head; pn3 != pn2; pn3 = pn3->pn_next->pn_next) {
1650 0                 if (pn3->pn_atom == pn2->pn_atom) {
1651 0                     js_ReportCompileErrorNumber(cx, pn2,
1652                                                 JSREPORT_PN | JSREPORT_ERROR,
1653                                                 JSMSG_DUPLICATE_XML_ATTR,
1654                                                 js_ValueToPrintableString(cx,
1655                                                     ATOM_KEY(pn2->pn_atom)));
1656 0                     goto fail;
1657                 }
1658             }
1659
1660 0             str = ATOM_TO_STRING(pn2->pn_atom);
1661 0             pn2 = pn2->pn_next;
1662 0             JS_ASSERT(pn2);
1663 0             if (pn2->pn_type != TOK_XMLATTR)
1664 0                 goto syntax;
1665
1666 0             length = JSSTRING_LENGTH(str);
1667 0             chars = JSSTRING_CHARS(str);
1668 0             if (length >= 5 &&
1669                 IS_XMLNS_CHARS(chars) &&
1670                 (length == 5 || chars[5] == ':')) {
1671 0                 JSString *uri, *prefix;
1672
1673 0                 uri = ATOM_TO_STRING(pn2->pn_atom);
1674 0                 if (length == 5) {
1675                     /* 10.3.2.1. Step 6(h)(i)(1)(a). */
1676 0                     prefix = cx->runtime->emptyString;
1677                 } else {
1678 0                     prefix = js_NewStringCopyN(cx, chars + 6, length - 6, 0);
1679 0                     if (!prefix)
1680 0                         goto fail;
1681                 }
1682
1683                 /*
1684                  * Once the new ns is appended to xml->xml_namespaces, it is
1685                  * protected from GC by the object that owns xml -- which is
1686                  * either xml->object if outermost, or the object owning xml's
1687                  * oldest ancestor if !outermost.
1688                  */
1689 0                 ns = js_NewXMLNamespace(cx, prefix, uri, JS_TRUE);
1690 0                 if (!ns)
1691 0                     goto fail;
1692
1693                 /*
1694                  * Don't add a namespace that's already in scope.  If someone
1695                  * extracts a child property from its parent via [[Get]], then
1696                  * we enforce the invariant, noted many times in ECMA-357, that
1697                  * the child's namespaces form a possibly-improper superset of
1698                  * its ancestors' namespaces.
1699                  */
1700 0                 if (!XMLARRAY_HAS_MEMBER(inScopeNSes, ns, namespace_identity)) {
1701 0                     if (!XMLARRAY_APPEND(cx, inScopeNSes, ns) ||
1702                         !XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns)) {
1703 0                         goto fail;
1704                     }
1705                 }
1706
1707 0                 JS_ASSERT(n >= 2);
1708 0                 n -= 2;
1709 0                 *pnp = pn2->pn_next;
1710                 /* XXXbe recycle pn2 */
1711 0                 continue;
1712             }
1713
1714 0             pnp = &pn2->pn_next;
1715         }
1716
1717         /*
1718          * If called from js_ParseNodeToXMLObject, emulate the effect of the
1719          * <parent xmlns='%s'>...</parent> wrapping done by "ToXML Applied to
1720          * the String Type" (ECMA-357 10.3.1).
1721          */
1722 0         if (flags & XSF_PRECOMPILED_ROOT) {
1723 0             JS_ASSERT(length >= 1);
1724 0             ns = XMLARRAY_MEMBER(inScopeNSes, 0, JSXMLNamespace);
1725 0             JS_ASSERT(!XMLARRAY_HAS_MEMBER(&xml->xml_namespaces, ns,
1726                                            namespace_identity));
1727 0             ns = js_NewXMLNamespace(cx, ns->prefix, ns->uri, JS_FALSE);
1728 0             if (!ns)
1729 0                 goto fail;
1730 0             if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
1731 0                 goto fail;
1732         }
1733 0         XMLArrayTrim(&xml->xml_namespaces);
1734
1735         /* Second pass: process tag name and attributes, using namespaces. */
1736 0         pn2 = pn->pn_head;
1737 0         qn = ParseNodeToQName(cx, pn2, inScopeNSes, JS_FALSE);
1738 0         if (!qn)
1739 0             goto fail;
1740 0         xml->name = qn;
1741
1742 0         JS_ASSERT((n & 1) == 0);
1743 0         n >>= 1;
1744 0         if (!XMLArraySetCapacity(cx, &xml->xml_attrs, n))
1745 0             goto fail;
1746
1747 0         for (i = 0; (pn2 = pn2->pn_next) != NULL; i++) {
1748 0             qn = ParseNodeToQName(cx, pn2, inScopeNSes, JS_TRUE);
1749 0             if (!qn) {
1750 0                 xml->xml_attrs.length = i;
1751 0                 goto fail;
1752             }
1753
1754             /*
1755              * Enforce "Well-formedness constraint: Unique Att Spec", part 2:
1756              * this time checking local name and namespace URI.
1757              */
1758 0             for (j = 0; j < i; j++) {
1759 0                 attrj = XMLARRAY_MEMBER(&xml->xml_attrs, j, JSXML);
1760 0                 attrjqn = attrj->name;
1761 0                 if (!js_CompareStrings(attrjqn->uri, qn->uri) &&
1762                     !js_CompareStrings(attrjqn->localName, qn->localName)) {
1763 0                     js_ReportCompileErrorNumber(cx, pn2,
1764                                                 JSREPORT_PN | JSREPORT_ERROR,
1765                                                 JSMSG_DUPLICATE_XML_ATTR,
1766                                                 js_ValueToPrintableString(cx,
1767                                                     ATOM_KEY(pn2->pn_atom)));
1768 0                     goto fail;
1769                 }
1770             }
1771
1772 0             pn2 = pn2->pn_next;
1773 0             JS_ASSERT(pn2);
1774 0             JS_ASSERT(pn2->pn_type == TOK_XMLATTR);
1775
1776 0             attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
1777 0             if (!attr)
1778 0                 goto fail;
1779
1780 0             XMLARRAY_SET_MEMBER(&xml->xml_attrs, i, attr);
1781 0             attr->parent = xml;
1782 0             attr->name = qn;
1783 0             attr->xml_value = ATOM_TO_STRING(pn2->pn_atom);
1784         }
1785
1786         /* Point tag closes its own namespace scope. */
1787 0         if (pn->pn_type == TOK_XMLPTAGC)
1788 0             XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1789 0         break;
1790
1791       case TOK_XMLSPACE:
1792       case TOK_XMLTEXT:
1793       case TOK_XMLCDATA:
1794       case TOK_XMLCOMMENT:
1795       case TOK_XMLPI:
1796 0         str = ATOM_TO_STRING(pn->pn_atom);
1797 0         qn = NULL;
1798 0         if (pn->pn_type == TOK_XMLCOMMENT) {
1799 0             if (flags & XSF_IGNORE_COMMENTS)
1800 0                 goto skip_child;
1801 0             xml_class = JSXML_CLASS_COMMENT;
1802 0         } else if (pn->pn_type == TOK_XMLPI) {
1803 0             if (IS_XML(str)) {
1804 0                 js_ReportCompileErrorNumber(cx, pn,
1805                                             JSREPORT_PN | JSREPORT_ERROR,
1806                                             JSMSG_RESERVED_ID,
1807                                             js_ValueToPrintableString(cx,
1808                                                 STRING_TO_JSVAL(str)));
1809 0                 goto fail;
1810             }
1811
1812 0             if (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS)
1813 0                 goto skip_child;
1814
1815 0             qn = ParseNodeToQName(cx, pn, inScopeNSes, JS_FALSE);
1816 0             if (!qn)
1817 0                 goto fail;
1818
1819 0             str = pn->pn_atom2
1820                   ? ATOM_TO_STRING(pn->pn_atom2)
1821                   : cx->runtime->emptyString;
1822 0             xml_class = JSXML_CLASS_PROCESSING_INSTRUCTION;
1823         } else {
1824             /* CDATA section content, or element text. */
1825 0             xml_class = JSXML_CLASS_TEXT;
1826         }
1827
1828 0         xml = js_NewXML(cx, xml_class);
1829 0         if (!xml)
1830 0             goto fail;
1831 0         xml->name = qn;
1832 0         if (pn->pn_type == TOK_XMLSPACE)
1833 0             xml->xml_flags |= XMLF_WHITESPACE_TEXT;
1834 0         xml->xml_value = str;
1835         break;
1836
1837       default:
1838 0         goto syntax;
1839     }
1840
1841 0     JS_LeaveLocalRootScope(cx);
1842 0     if ((flags & XSF_PRECOMPILED_ROOT) && !js_GetXMLObject(cx, xml))
1843 0         return NULL;
1844 0     return xml;
1845
1846 skip_child:
1847 0     js_LeaveLocalRootScope(cx);
1848 0     return PN2X_SKIP_CHILD;
1849
1850 #undef PN2X_SKIP_CHILD
1851
1852 syntax:
1853 0     js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR,
1854                                 JSMSG_BAD_XML_MARKUP);
1855 fail:
1856 0     JS_LeaveLocalRootScope(cx);
1857 0     return NULL;
1858 }
1859
1860 /*
1861  * XML helper, object-ops, and library functions.  We start with the helpers,
1862  * in ECMA-357 order, but merging XML (9.1) and XMLList (9.2) helpers.
1863  */
1864 static JSBool
1865 GetXMLSetting(JSContext *cx, const char *name, jsval *vp)
1866 0 {
1867 0     jsval v;
1868
1869 0     if (!js_FindConstructor(cx, NULL, js_XML_str, &v))
1870 0         return JS_FALSE;
1871 0     if (!JSVAL_IS_FUNCTION(cx, v)) {
1872 0         *vp = JSVAL_VOID;
1873 0         return JS_TRUE;
1874     }
1875 0     return JS_GetProperty(cx, JSVAL_TO_OBJECT(v), name, vp);
1876 }
1877
1878 static JSBool
1879 FillSettingsCache(JSContext *cx)
1880 0 {
1881 0     int i;
1882 0     const char *name;
1883 0     jsval v;
1884 0     JSBool isSet;
1885
1886     /* Note: XML_PRETTY_INDENT is not a boolean setting. */
1887 0     for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
1888 0         name = xml_static_props[i].name;
1889 0         if (!GetXMLSetting(cx, name, &v) || !js_ValueToBoolean(cx, v, &isSet))
1890 0             return JS_FALSE;
1891 0         if (isSet)
1892 0             cx->xmlSettingFlags |= JS_BIT(i);
1893         else
1894 0             cx->xmlSettingFlags &= ~JS_BIT(i);
1895     }
1896
1897 0     cx->xmlSettingFlags |= XSF_CACHE_VALID;
1898 0     return JS_TRUE;
1899 }
1900
1901 static JSBool
1902 GetBooleanXMLSetting(JSContext *cx, const char *name, JSBool *bp)
1903 0 {
1904 0     int i;
1905
1906 0     if (!(cx->xmlSettingFlags & XSF_CACHE_VALID) && !FillSettingsCache(cx))
1907 0         return JS_FALSE;
1908
1909 0     for (i = 0; xml_static_props[i].name; i++) {
1910 0         if (!strcmp(xml_static_props[i].name, name)) {
1911 0             *bp = (cx->xmlSettingFlags & JS_BIT(i)) != 0;
1912 0             return JS_TRUE;
1913         }
1914     }
1915 0     *bp = JS_FALSE;
1916 0     return JS_TRUE;
1917 }
1918
1919 static JSBool
1920 GetUint32XMLSetting(JSContext *cx, const char *name, uint32 *uip)
1921 0 {
1922 0     jsval v;
1923
1924 0     return GetXMLSetting(cx, name, &v) && js_ValueToECMAUint32(cx, v, uip);
1925 }
1926
1927 static JSBool
1928 GetXMLSettingFlags(JSContext *cx, uintN *flagsp)
1929 0 {
1930 0     JSBool flag;
1931
1932     /* Just get the first flag to validate the setting flags cache. */
1933 0     if (!GetBooleanXMLSetting(cx, js_ignoreComments_str, &flag))
1934 0         return JS_FALSE;
1935 0     *flagsp = cx->xmlSettingFlags;
1936 0     return JS_TRUE;
1937 }
1938
1939 static JSXML *
1940 ParseXMLSource(JSContext *cx, JSString *src)
1941 0 {
1942 0     jsval nsval;
1943 0     JSXMLNamespace *ns;
1944 0     size_t urilen, srclen, length, offset, dstlen;
1945 0     jschar *chars;
1946 0     const jschar *srcp, *endp;
1947 0     void *mark;
1948 0     JSTokenStream *ts;
1949 0     uintN lineno;
1950 0     JSStackFrame *fp;
1951 0     JSOp op;
1952 0     JSParseNode *pn;
1953 0     JSXML *xml;
1954 0     JSXMLArray nsarray;
1955 0     uintN flags;
1956
1957 0     static const char prefix[] = "<parent xmlns='";
1958 0     static const char middle[] = "'>";
1959 0     static const char suffix[] = "</parent>";
1960
1961 #define constrlen(constr)   (sizeof(constr) - 1)
1962
1963 0     if (!js_GetDefaultXMLNamespace(cx, &nsval))
1964 0         return NULL;
1965 0     ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
1966
1967 0     urilen = JSSTRING_LENGTH(ns->uri);
1968 0     srclen = JSSTRING_LENGTH(src);
1969 0     length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
1970              constrlen(suffix);
1971
1972 0     chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
1973 0     if (!chars)
1974 0         return NULL;
1975
1976 0     dstlen = length;
1977 0     js_InflateStringToBuffer(cx, prefix, constrlen(prefix), chars, &dstlen);
1978 0     offset = dstlen;
1979 0     js_strncpy(chars + offset, JSSTRING_CHARS(ns->uri), urilen);
1980 0     offset += urilen;
1981 0     dstlen = length - offset + 1;
1982 0     js_InflateStringToBuffer(cx, middle, constrlen(middle), chars + offset, &dstlen);
1983 0     offset += dstlen;
1984 0     srcp = JSSTRING_CHARS(src);
1985 0     js_strncpy(chars + offset, srcp, srclen);
1986 0     offset += srclen;
1987 0     dstlen = length - offset + 1;
1988 0     js_InflateStringToBuffer(cx, suffix, constrlen(suffix), chars + offset, &dstlen);
1989 0     chars [offset + dstlen] = 0;
1990
1991 0     mark = JS_ARENA_MARK(&cx->tempPool);
1992 0     ts = js_NewBufferTokenStream(cx, chars, length);
1993 0     if (!ts)
1994 0         return NULL;
1995 0     for (fp = cx->fp; fp && !fp->pc; fp = fp->down)
1996 0         continue;
1997 0     if (fp) {
1998 0         op = (JSOp) *fp->pc;
1999 0         if (op == JSOP_TOXML || op == JSOP_TOXMLLIST) {
2000 0             ts->filename = fp->script->filename;
2001 0             lineno = js_PCToLineNumber(cx, fp->script, fp->pc);
2002 0             for (endp = srcp + srclen; srcp < endp; srcp++)
2003 0                 if (*srcp == '\n')
2004 0                     --lineno;
2005 0             ts->lineno = lineno;
2006         }
2007     }
2008
2009 0     JS_KEEP_ATOMS(cx->runtime);
2010 0     pn = js_ParseXMLTokenStream(cx, cx->fp->scopeChain, ts, JS_FALSE);
2011 0     xml = NULL;
2012 0     if (pn && XMLArrayInit(cx, &nsarray, 1)) {
2013 0         if (GetXMLSettingFlags(cx, &flags))
2014 0             xml = ParseNodeToXML(cx, pn, &nsarray, flags);
2015
2016 0         XMLArrayFinish(cx, &nsarray);
2017     }
2018 0     JS_UNKEEP_ATOMS(cx->runtime);
2019
2020 0     JS_ARENA_RELEASE(&cx->tempPool, mark);
2021 0     JS_free(cx, chars);
2022 0     return xml;
2023
2024 #undef constrlen
2025 }
2026
2027 /*
2028  * Errata in 10.3.1, 10.4.1, and 13.4.4.24 (at least).
2029  *
2030  * 10.3.1 Step 6(a) fails to NOTE that implementations that do not enforce
2031  * the constraint:
2032  *
2033  *     for all x belonging to XML:
2034  *         x.[[InScopeNamespaces]] >= x.[[Parent]].[[InScopeNamespaces]]
2035  *
2036  * must union x.[[InScopeNamespaces]] into x[0].[[InScopeNamespaces]] here
2037  * (in new sub-step 6(a), renumbering the others to (b) and (c)).
2038  *
2039  * Same goes for 10.4.1 Step 7(a).
2040  *
2041  * In order for XML.prototype.namespaceDeclarations() to work correctly, the
2042  * default namespace thereby unioned into x[0].[[InScopeNamespaces]] must be
2043  * flagged as not declared, so that 13.4.4.24 Step 8(a) can exclude all such
2044  * undeclared namespaces associated with x not belonging to ancestorNS.
2045  */
2046 static JSXML *
2047 OrphanXMLChild(JSContext *cx, JSXML *xml, uint32 i)
2048 0 {
2049 0     JSXMLNamespace *ns;
2050
2051 0     ns = XMLARRAY_MEMBER(&xml->xml_namespaces, 0, JSXMLNamespace);
2052 0     xml = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
2053 0     if (!ns || !xml)
2054 0         return xml;
2055 0     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
2056 0         if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
2057 0             return NULL;
2058 0         ns->declared = JS_FALSE;
2059     }
2060 0     xml->parent = NULL;
2061 0     return xml;
2062 }
2063
2064 static JSObject *
2065 ToXML(JSContext *cx, jsval v)
2066 0 {
2067 0     JSObject *obj;
2068 0     JSXML *xml;
2069 0     JSClass *clasp;
2070 0     JSString *str;
2071 0     uint32 length;
2072
2073 0     if (JSVAL_IS_PRIMITIVE(v)) {
2074 0         if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
2075 0             goto bad;
2076     } else {
2077 0         obj = JSVAL_TO_OBJECT(v);
2078 0         if (OBJECT_IS_XML(cx, obj)) {
2079 0             xml = (JSXML *) JS_GetPrivate(cx, obj);
2080 0             if (xml->xml_class == JSXML_CLASS_LIST) {
2081 0                 if (xml->xml_kids.length != 1)
2082 0                     goto bad;
2083 0                 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
2084 0                 if (xml) {
2085 0                     JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
2086 0                     return js_GetXMLObject(cx, xml);
2087                 }
2088             }
2089 0             return obj;
2090         }
2091
2092 0         clasp = OBJ_GET_CLASS(cx, obj);
2093 0         if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
2094 0             JS_ASSERT(0);
2095         }
2096
2097 0         if (clasp != &js_StringClass &&
2098             clasp != &js_NumberClass &&
2099             clasp != &js_BooleanClass) {
2100 0             goto bad;
2101         }
2102     }
2103
2104 0     str = js_ValueToString(cx, v);
2105 0     if (!str)
2106 0         return NULL;
2107 0     if (IS_EMPTY(str)) {
2108 0         length = 0;
2109 #ifdef __GNUC__         /* suppress bogus gcc warnings */
2110 0         xml = NULL;
2111 #endif
2112     } else {
2113 0         xml = ParseXMLSource(cx, str);
2114 0         if (!xml)
2115 0             return NULL;
2116 0         length = JSXML_LENGTH(xml);
2117     }
2118
2119 0     if (length == 0) {
2120 0         obj = js_NewXMLObject(cx, JSXML_CLASS_TEXT);
2121 0         if (!obj)
2122 0             return NULL;
2123 0     } else if (length == 1) {
2124 0         xml = OrphanXMLChild(cx, xml, 0);
2125 0         if (!xml)
2126 0             return NULL;
2127 0         obj = js_GetXMLObject(cx, xml);
2128 0         if (!obj)
2129 0             return NULL;
2130     } else {
2131 0         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SYNTAX_ERROR);
2132 0         return NULL;
2133     }
2134 0     return obj;
2135
2136 bad:
2137 0     str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
2138 0     if (str) {
2139 0         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2140                              JSMSG_BAD_XML_CONVERSION,
2141                              JS_GetStringBytes(str));
2142     }
2143 0     return NULL;
2144 }
2145
2146 static JSBool
2147 Append(JSContext *cx, JSXML *list, JSXML *kid);
2148
2149 static JSObject *
2150 ToXMLList(JSContext *cx, jsval v)
2151 0 {
2152 0     JSObject *obj, *listobj;
2153 0     JSXML *xml, *list, *kid;
2154 0     JSClass *clasp;
2155 0     JSString *str;
2156 0     uint32 i, length;
2157
2158 0     if (JSVAL_IS_PRIMITIVE(v)) {
2159 0         if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
2160 0             goto bad;
2161     } else {
2162 0         obj = JSVAL_TO_OBJECT(v);
2163 0         if (OBJECT_IS_XML(cx, obj)) {
2164 0             xml = (JSXML *) JS_GetPrivate(cx, obj);
2165 0             if (xml->xml_class != JSXML_CLASS_LIST) {
2166 0                 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2167 0                 if (!listobj)
2168 0                     return NULL;
2169 0                 list = (JSXML *) JS_GetPrivate(cx, listobj);
2170 0                 if (!Append(cx, list, xml))
2171 0                     return NULL;
2172 0                 return listobj;
2173             }
2174 0             return obj;
2175         }
2176
2177 0         clasp = OBJ_GET_CLASS(cx, obj);
2178 0         if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
2179 0             JS_ASSERT(0);
2180         }
2181
2182 0         if (clasp != &js_StringClass &&
2183             clasp != &js_NumberClass &&
2184             clasp != &js_BooleanClass) {
2185 0             goto bad;
2186         }
2187     }
2188
2189 0     str = js_ValueToString(cx, v);
2190 0     if (!str)
2191 0         return NULL;
2192 0     if (IS_EMPTY(str)) {
2193 0         xml = NULL;
2194 0         length = 0;
2195     } else {
2196 0         if (!JS_EnterLocalRootScope(cx))
2197 0             return NULL;
2198 0         xml = ParseXMLSource(cx, str);
2199 0         if (!xml) {
2200 0             JS_LeaveLocalRootScope(cx);
2201 0             return NULL;
2202         }
2203 0         length = JSXML_LENGTH(xml);
2204     }
2205
2206 0     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2207 0     if (listobj) {
2208 0         list = (JSXML *) JS_GetPrivate(cx, listobj);
2209 0         for (i = 0; i < length; i++) {
2210 0             kid = OrphanXMLChild(cx, xml, i);
2211 0             if (!kid)
2212 0                 return NULL;
2213 0             if (!Append(cx, list, kid)) {
2214 0                 listobj = NULL;
2215 0                 break;
2216             }
2217         }
2218     }
2219
2220 0     if (xml)
2221 0         JS_LeaveLocalRootScope(cx);
2222 0     return listobj;
2223
2224 bad:
2225 0     str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
2226 0     if (str) {
2227 0         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2228                              JSMSG_BAD_XMLLIST_CONVERSION,
2229                              JS_GetStringBytes(str));
2230     }
2231 0     return NULL;
2232 }
2233
2234 /*
2235  * ECMA-357 10.2.1 Steps 5-7 pulled out as common subroutines of XMLToXMLString
2236  * and their library-public js_* counterparts.  The guts of MakeXMLCDataString,
2237  * MakeXMLCommentString, and MakeXMLPIString are further factored into a common
2238  * MakeXMLSpecialString subroutine.
2239  *
2240  * These functions take ownership of sb->base, if sb is non-null, in all cases
2241  * of success or failure.
2242  */
2243 static JSString *
2244 MakeXMLSpecialString(JSContext *cx, JSStringBuffer *sb,
2245                      JSString *str, JSString *str2,
2246                      const jschar *prefix, size_t prefixlength,
2247                      const jschar *suffix, size_t suffixlength)
2248 0 {
2249 0     JSStringBuffer localSB;
2250 0     size_t length, length2, newlength;
2251 0     jschar *bp, *base;
2252
2253 0     if (!sb) {
2254 0         sb = &localSB;
2255 0         js_InitStringBuffer(sb);
2256     }
2257
2258 0     length = JSSTRING_LENGTH(str);
2259 0     length2 = str2 ? JSSTRING_LENGTH(str2) : 0;
2260 0     newlength = STRING_BUFFER_OFFSET(sb) +
2261                 prefixlength + length + ((length2 != 0) ? 1 + length2 : 0) +
2262                 suffixlength;
2263 0     bp = base = (jschar *)
2264                 JS_realloc(cx, sb->base, (newlength + 1) * sizeof(jschar));
2265 0     if (!bp) {
2266 0         js_FinishStringBuffer(sb);
2267 0         return NULL;
2268     }
2269
2270 0     bp += STRING_BUFFER_OFFSET(sb);
2271 0     js_strncpy(bp, prefix, prefixlength);
2272 0     bp += prefixlength;
2273 0     js_strncpy(bp, JSSTRING_CHARS(str), length);
2274 0     bp += length;
2275 0     if (length2 != 0) {
2276 0         *bp++ = (jschar) ' ';
2277 0         js_strncpy(bp, JSSTRING_CHARS(str2), length2);
2278 0         bp += length2;
2279     }
2280 0     js_strncpy(bp, suffix, suffixlength);
2281 0     bp[suffixlength] = 0;
2282
2283 0     str = js_NewString(cx, base, newlength, 0);
2284 0     if (!str)
2285 0         free(base);
2286 0     return str;
2287 }
2288
2289 static JSString *
2290 MakeXMLCDATAString(JSContext *cx, JSStringBuffer *sb, JSString *str)
2291 0 {
2292     static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
2293                                                  'C', 'D', 'A', 'T', 'A',
2294 0                                                  '['};
2295 0     static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};
2296
2297 0     return MakeXMLSpecialString(cx, sb, str, NULL,
2298                                 cdata_prefix_ucNstr, 9,
2299                                 cdata_suffix_ucNstr, 3);
2300 }
2301
2302 static JSString *
2303 MakeXMLCommentString(JSContext *cx, JSStringBuffer *sb, JSString *str)
2304 0 {
2305 0     static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
2306 0     static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};
2307
2308 0     return MakeXMLSpecialString(cx, sb, str, NULL,
2309                                 comment_prefix_ucNstr, 4,
2310                                 comment_suffix_ucNstr, 3);
2311 }
2312
2313 static JSString *
2314 MakeXMLPIString(JSContext *cx, JSStringBuffer *sb, JSString *name,
2315                 JSString *value)
2316 0 {
2317 0     static const jschar pi_prefix_ucNstr[] = {'<', '?'};
2318 0     static const jschar pi_suffix_ucNstr[] = {'?', '>'};
2319
2320 0     return MakeXMLSpecialString(cx, sb, name, value,
2321                                 pi_prefix_ucNstr, 2,
2322                                 pi_suffix_ucNstr, 2);
2323 }
2324
2325 /*
2326  * ECMA-357 10.2.1 17(d-g) pulled out into a common subroutine that appends
2327  * equals, a double quote, an attribute value, and a closing double quote.
2328  */
2329 static void
2330 AppendAttributeValue(JSContext *cx, JSStringBuffer *sb, JSString *valstr)
2331 0 {
2332 0     js_AppendCString(sb, "=\"");
2333 0     valstr = js_EscapeAttributeValue(cx, valstr);
2334 0     if (!valstr) {
2335 0         free(sb->base);
2336 0         sb->base = STRING_BUFFER_ERROR_BASE;
2337 0         return;
2338     }
2339 0     js_AppendJSString(sb, valstr);
2340 0     js_AppendChar(sb, '"');
2341 }
2342
2343 /*
2344  * ECMA-357 10.2.1.1 EscapeElementValue helper method.
2345  *
2346  * This function takes ownership of sb->base, if sb is non-null, in all cases
2347  * of success or failure.
2348  */
2349 static JSString *
2350 EscapeElementValue(JSContext *cx, JSStringBuffer *sb, JSString *str)
2351 0 {
2352 0     size_t length, newlength;
2353 0     const jschar *cp, *start, *end;
2354 0     jschar c;
2355
2356 0     length = newlength = JSSTRING_LENGTH(str);
2357 0     for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
2358 0         c = *cp;
2359 0         if (c == '<' || c == '>')
2360 0             newlength += 3;
2361 0         else if (c == '&')
2362 0             newlength += 4;
2363
2364 0         if (newlength < length) {
2365 0             JS_ReportOutOfMemory(cx);
2366 0             return NULL;
2367         }
2368     }
2369 0     if ((sb && STRING_BUFFER_OFFSET(sb) != 0) || newlength > length) {
2370 0         JSStringBuffer localSB;
2371 0         if (!sb) {
2372 0             sb = &localSB;
2373 0             js_InitStringBuffer(sb);
2374         }
2375 0         if (!sb->grow(sb, newlength)) {
2376 0             JS_ReportOutOfMemory(cx);
2377 0             return NULL;
2378         }
2379 0         for (cp = start; cp < end; cp++) {
2380 0             c = *cp;
2381 0             if (c == '<')
2382 0                 js_AppendCString(sb, js_lt_entity_str);
2383 0             else if (c == '>')
2384 0                 js_AppendCString(sb, js_gt_entity_str);
2385 0             else if (c == '&')
2386 0                 js_AppendCString(sb, js_amp_entity_str);
2387             else
2388 0                 js_AppendChar(sb, c);
2389         }
2390 0         JS_ASSERT(STRING_BUFFER_OK(sb));
2391 0         str = js_NewString(cx, sb->base, STRING_BUFFER_OFFSET(sb), 0);
2392 0         if (!str)
2393 0             js_FinishStringBuffer(sb);
2394     }
2395 0     return str;
2396 }
2397
2398 /*
2399  * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
2400  * This function takes ownership of sb->base, if sb is non-null, in all cases.
2401  */
2402 static JSString *
2403 EscapeAttributeValue(JSContext *cx, JSStringBuffer *sb, JSString *str)
2404 0 {
2405 0     size_t length, newlength;
2406 0     const jschar *cp, *start, *end;
2407 0     jschar c;
2408
2409 0     length = newlength = JSSTRING_LENGTH(str);
2410 0     for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
2411 0         c = *cp;
2412 0         if (c == '"')
2413 0             newlength += 5;
2414 0         else if (c == '<')
2415 0             newlength += 3;
2416 0         else if (c == '&' || c == '\n' || c == '\r' || c == '\t')
2417 0             newlength += 4;
2418
2419 0         if (newlength < length) {
2420 0             JS_ReportOutOfMemory(cx);
2421 0             return NULL;
2422         }
2423     }
2424 0     if ((sb && STRING_BUFFER_OFFSET(sb) != 0) || newlength > length) {
2425 0         JSStringBuffer localSB;
2426 0         if (!sb) {
2427 0             sb = &localSB;
2428 0             js_InitStringBuffer(sb);
2429         }
2430 0         if (!sb->grow(sb, newlength)) {
2431 0             JS_ReportOutOfMemory(cx);
2432 0             return NULL;
2433         }
2434 0         for (cp = start; cp < end; cp++) {
2435 0             c = *cp;
2436 0             if (c == '"')
2437 0                 js_AppendCString(sb, js_quot_entity_str);
2438 0             else if (c == '<')
2439 0                 js_AppendCString(sb, js_lt_entity_str);
2440 0             else if (c == '&')
2441 0                 js_AppendCString(sb, js_amp_entity_str);
2442 0             else if (c == '\n')
2443 0                 js_AppendCString(sb, "&#xA;");
2444 0             else if (c == '\r')
2445 0                 js_AppendCString(sb, "&#xD;");
2446 0             else if (c == '\t')
2447 0                 js_AppendCString(sb, "&#x9;");
2448             else
2449 0                 js_AppendChar(sb, c);
2450         }
2451 0         JS_ASSERT(STRING_BUFFER_OK(sb));
2452 0         str = js_NewString(cx, sb->base, STRING_BUFFER_OFFSET(sb), 0);
2453 0         if (!str)
2454 0             js_FinishStringBuffer(sb);
2455     }
2456 0     return str;
2457 }
2458
2459 /* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
2460 static JSXMLNamespace *
2461 GetNamespace(JSContext *cx, JSXMLQName *qn, const JSXMLArray *inScopeNSes)
2462 0 {
2463 0     JSXMLNamespace *match, *ns;
2464 0     uint32 i, n;
2465 0     jsval argv[2];
2466 0     JSObject *nsobj;
2467
2468 0     JS_ASSERT(qn->uri);
2469 0     if (!qn->uri) {
2470 0         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2471                              JSMSG_BAD_XML_NAMESPACE,
2472                              qn->prefix
2473                              ? js_ValueToPrintableString(cx,
2474                                    STRING_TO_JSVAL(qn->prefix))
2475                              : js_type_str[JSTYPE_VOID]);
2476 0         return NULL;
2477     }
2478
2479     /* Look for a matching namespace in inScopeNSes, if provided. */
2480 0     match = NULL;
2481 0     if (inScopeNSes) {
2482 0         for (i = 0, n = inScopeNSes->length; i < n; i++) {
2483 0             ns = XMLARRAY_MEMBER(inScopeNSes, i, JSXMLNamespace);
2484 0             if (!ns)
2485 0                 continue;
2486
2487             /*
2488              * Erratum, very tricky, and not specified in ECMA-357 13.3.5.4:
2489              * If we preserve prefixes, we must match null qn->prefix against
2490              * an empty ns->prefix, in order to avoid generating redundant
2491              * prefixed and default namespaces for cases such as:
2492              *
2493              *   x = <t xmlns="http://foo.com"/>
2494              *   print(x.toXMLString());
2495              *
2496              * Per 10.3.2.1, the namespace attribute in t has an empty string
2497              * prefix (*not* a null prefix), per 10.3.2.1 Step 6(h)(i)(1):
2498              *
2499              *   1. If the [local name] property of a is "xmlns"
2500              *      a. Map ns.prefix to the empty string
2501              *
2502              * But t's name has a null prefix in this implementation, meaning
2503              * *undefined*, per 10.3.2.1 Step 6(c)'s NOTE (which refers to
2504              * the http://www.w3.org/TR/xml-infoset/ spec, item 2.2.3, without
2505              * saying how "no value" maps to an ECMA-357 value -- but it must
2506              * map to the *undefined* prefix value).
2507              *
2508              * Since "" != undefined (or null, in the current implementation)
2509              * the ECMA-357 spec will fail to match in [[GetNamespace]] called
2510              * on t with argument {} U {(prefix="", uri="http://foo.com")}.
2511              * This spec bug leads to ToXMLString results that duplicate the
2512              * declared namespace.
2513              */
2514 0             if (!js_CompareStrings(ns->uri, qn->uri) &&
2515                 (ns->prefix == qn->prefix ||
2516                  ((ns->prefix && qn->prefix)
2517                   ? !js_CompareStrings(ns->prefix, qn->prefix)
2518                   : IS_EMPTY(ns->prefix ? ns->prefix : qn->prefix)))) {
2519 0                 match = ns;
2520 0                 break;
2521             }
2522         }
2523     }
2524
2525     /* If we didn't match, make a new namespace from qn. */
2526 0     if (!match) {
2527 0         argv[0] = qn->prefix ? STRING_TO_JSVAL(qn->prefix) : JSVAL_VOID;
2528 0         argv[1] = STRING_TO_JSVAL(qn->uri);
2529 0         nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL,
2530                                    2, argv);
2531 0         if (!nsobj)
2532 0             return NULL;
2533 0         match = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
2534     }
2535 0     return match;
2536 }
2537
2538 static JSString *
2539 GeneratePrefix(JSContext *cx, JSString *uri, JSXMLArray *decls)
2540 0 {
2541 0     const jschar *cp, *start, *end;
2542 0     size_t length, newlength, offset;
2543 0     uint32 i, n, m, serial;
2544 0     jschar *bp, *dp;
2545 0     JSBool done;
2546 0     JSXMLNamespace *ns;
2547 0     JSString *prefix;
2548
2549 0     JS_ASSERT(!IS_EMPTY(uri));
2550
2551     /*
2552      * Try peeling off the last filename suffix or pathname component till
2553      * we have a valid XML name.  This heuristic will prefer "xul" given
2554      * ".../there.is.only.xul", "xbl" given ".../xbl", and "xbl2" given any
2555      * likely URI of the form ".../xbl2/2005".
2556      */
2557 0     start = JSSTRING_CHARS(uri);
2558 0     cp = end = start + JSSTRING_LENGTH(uri);
2559 0     while (--cp > start) {
2560 0         if (*cp == '.' || *cp == '/' || *cp == ':') {
2561 0             ++cp;
2562 0             if (IsXMLName(cp, PTRDIFF(end, cp, jschar)))
2563 0                 break;
2564 0             end = --cp;
2565         }
2566     }
2567 0     length = PTRDIFF(end, cp, jschar);
2568
2569     /*
2570      * Now search through decls looking for a collision.  If we collide with
2571      * an existing prefix, start tacking on a hyphen and a serial number.
2572      */
2573 0     serial = 0;
2574 0     bp = NULL;
2575 #ifdef __GNUC__         /* suppress bogus gcc warnings */
2576 0     newlength = 0;
2577 #endif
2578 0     do {
2579 0         done = JS_TRUE;
2580 0         for (i = 0, n = decls->length; i < n; i++) {
2581 0             ns = XMLARRAY_MEMBER(decls, i, JSXMLNamespace);
2582 0             if (ns && ns->prefix &&
2583                 JSSTRING_LENGTH(ns->prefix) == length &&
2584                 !memcmp(JSSTRING_CHARS(ns->prefix), cp,
2585                         length * sizeof(jschar))) {
2586 0                 if (!bp) {
2587 0                     newlength = length + 2 + (size_t) log10(n);
2588 0                     bp = (jschar *)
2589                          JS_malloc(cx, (newlength + 1) * sizeof(jschar));
2590 0                     if (!bp)
2591 0                         return NULL;
2592 0                     js_strncpy(bp, cp, length);
2593                 }
2594
2595 0                 ++serial;
2596 0                 JS_ASSERT(serial <= n);
2597 0                 dp = bp + length + 2 + (size_t) log10(serial);
2598 0                 *dp = 0;
2599 0                 for (m = serial; m != 0; m /= 10)
2600 0                     *--dp = (jschar)('0' + m % 10);
2601 0                 *--dp = '-';
2602 0                 JS_ASSERT(dp == bp + length);
2603
2604 0                 done = JS_FALSE;
2605 0                 break;
2606             }
2607         }
2608 0     } while (!done);
2609
2610 0     if (!bp) {
2611 0         offset = PTRDIFF(cp, start, jschar);
2612 0         prefix = js_NewDependentString(cx, uri, offset, length, 0);
2613     } else {
2614 0         prefix = js_NewString(cx, bp, newlength, 0);
2615 0         if (!prefix)
2616 0             JS_free(cx, bp);
2617     }
2618 0     return prefix;
2619 }
2620
2621 static JSBool
2622 namespace_match(const void *a, const void *b)
2623 0 {
2624 0     const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
2625 0     const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
2626
2627 0     if (nsb->prefix)
2628 0         return nsa->prefix && !js_CompareStrings(nsa->prefix, nsb->prefix);
2629 0     return !js_CompareStrings(nsa->uri, nsb->uri);
2630 }
2631
2632 /* ECMA-357 10.2.1 and 10.2.2 */
2633 static JSString *
2634 XMLToXMLString(JSContext *cx, JSXML *xml, const JSXMLArray *ancestorNSes,
2635                uintN indentLevel)
2636 0 {
2637 0     JSBool pretty, indentKids;
2638 0     JSStringBuffer sb;
2639 0     JSString *str, *prefix, *kidstr;
2640 0     JSXMLArrayCursor cursor;
2641 0     uint32 i, n;
2642 0     JSXMLArray empty, decls, ancdecls;
2643 0     JSXMLNamespace *ns, *ns2;
2644 0     uintN nextIndentLevel;
2645 0     JSXML *attr, *kid;
2646
2647 0     if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
2648 0         return NULL;
2649
2650 0     js_InitStringBuffer(&sb);
2651 0     if (pretty)
2652 0         js_RepeatChar(&sb, ' ', indentLevel);
2653 0     str = NULL;
2654
2655 0     switch (xml->xml_class) {
2656       case JSXML_CLASS_TEXT:
2657         /* Step 4. */
2658 0         if (pretty) {
2659 0             str = ChompXMLWhitespace(cx, xml->xml_value);
2660 0             if (!str)
2661 0                 return NULL;
2662         } else {
2663 0             str = xml->xml_value;
2664         }
2665 0         return EscapeElementValue(cx, &sb, str);
2666
2667       case JSXML_CLASS_ATTRIBUTE:
2668         /* Step 5. */
2669 0         return EscapeAttributeValue(cx, &sb, xml->xml_value);
2670
2671       case JSXML_CLASS_COMMENT:
2672         /* Step 6. */
2673 0         return MakeXMLCommentString(cx, &sb, xml->xml_value);
2674
2675       case JSXML_CLASS_PROCESSING_INSTRUCTION:
2676         /* Step 7. */
2677 0         return MakeXMLPIString(cx, &sb, xml->name->localName, xml->xml_value);
2678
2679       case JSXML_CLASS_LIST:
2680         /* ECMA-357 10.2.2. */
2681 0         XMLArrayCursorInit(&cursor, &xml->xml_kids);
2682 0         i = 0;
2683 0         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2684 0             if (pretty && i != 0)
2685 0                 js_AppendChar(&sb, '\n');
2686
2687 0             kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel);
2688 0             if (!kidstr)
2689 0                 break;
2690
2691 0             js_AppendJSString(&sb, kidstr);
2692 0             ++i;
2693         }
2694 0         XMLArrayCursorFinish(&cursor);
2695 0         if (kid)
2696 0             goto list_out;
2697
2698 0         if (!sb.base) {
2699 0             if (!STRING_BUFFER_OK(&sb)) {
2700 0                 JS_ReportOutOfMemory(cx);
2701 0                 return NULL;
2702             }
2703 0             return cx->runtime->emptyString;
2704         }
2705
2706 0         str = js_NewString(cx, sb.base, STRING_BUFFER_OFFSET(&sb), 0);
2707       list_out:
2708 0         if (!str)
2709 0             js_FinishStringBuffer(&sb);
2710 0         return str;
2711
2712       default:;
2713     }
2714
2715     /* After this point, control must flow through label out: to exit. */
2716 0     if (!JS_EnterLocalRootScope(cx))
2717 0         return NULL;
2718
2719     /* ECMA-357 10.2.1 step 8 onward: handle ToXMLString on an XML element. */
2720 0     if (!ancestorNSes) {
2721 0         XMLArrayInit(cx, &empty, 0);
2722 0         ancestorNSes = &empty;
2723     }
2724 0     XMLArrayInit(cx, &decls, 0);
2725 0     ancdecls.capacity = 0;
2726
2727     /* Clone in-scope namespaces not in ancestorNSes into decls. */
2728 0     XMLArrayCursorInit(&cursor, &xml->xml_namespaces);
2729 0     while ((ns = (JSXMLNamespace *) XMLArrayCursorNext(&cursor)) != NULL) {
2730 0         if (!ns->declared)
2731 0             continue;
2732 0         if (!XMLARRAY_HAS_MEMBER(ancestorNSes, ns, namespace_identity)) {
2733             /* NOTE: may want to exclude unused namespaces here. */
2734 0             ns2 = js_NewXMLNamespace(cx, ns->prefix, ns->uri, JS_TRUE);
2735 0             if (!ns2 || !XMLARRAY_APPEND(cx, &decls, ns2))
2736                 break;
2737         }
2738     }
2739 0     XMLArrayCursorFinish(&cursor);
2740 0     if (ns)
2741 0         goto out;
2742
2743     /*
2744      * Union ancestorNSes and decls into ancdecls.  Note that ancdecls does
2745      * not own its member references.  In the spec, ancdecls has no name, but
2746      * is always written out as (AncestorNamespaces U namespaceDeclarations).
2747      */
2748 0     if (!XMLArrayInit(cx, &ancdecls, ancestorNSes->length + decls.length))
2749 0         goto out;
2750 0     for (i = 0, n = ancestorNSes->length; i < n; i++) {
2751 0         ns2 = XMLARRAY_MEMBER(ancestorNSes, i, JSXMLNamespace);
2752 0         if (!ns2)
2753 0             continue;
2754 0         JS_ASSERT(!XMLARRAY_HAS_MEMBER(&decls, ns2, namespace_identity));
2755 0         if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
2756 0             goto out;
2757     }
2758 0     for (i = 0, n = decls.length; i < n; i++) {
2759 0         ns2 = XMLARRAY_MEMBER(&decls, i, JSXMLNamespace);
2760 0         if (!ns2)
2761 0             continue;
2762 0         JS_ASSERT(!XMLARRAY_HAS_MEMBER(&ancdecls, ns2, namespace_identity));
2763 0         if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
2764 0             goto out;
2765     }
2766
2767     /* Step 11, except we don't clone ns unless its prefix is undefined. */
2768 0     ns = GetNamespace(cx, xml->name, &ancdecls);
2769 0     if (!ns)
2770 0         goto out;
2771
2772     /* Step 12 (NULL means *undefined* here), plus the deferred ns cloning. */
2773 0     if (!ns->prefix) {
2774         /*
2775          * Create a namespace prefix that isn't used by any member of decls.
2776          * Assign the new prefix to a copy of ns.  Flag this namespace as if
2777          * it were declared, for assertion-testing's sake later below.
2778          *
2779          * Erratum: if ns->prefix and xml->name are both null (*undefined* in
2780          * ECMA-357), we know that xml was named using the default namespace
2781          * (proof: see GetNamespace and the Namespace constructor called with
2782          * two arguments).  So we ought not generate a new prefix here, when
2783          * we can declare ns as the default namespace for xml.
2784          *
2785          * This helps descendants inherit the namespace instead of redundantly
2786          * redeclaring it with generated prefixes in each descendant.
2787          */
2788 0         if (!xml->name->prefix) {
2789 0             prefix = cx->runtime->emptyString;
2790         } else {
2791 0             prefix = GeneratePrefix(cx, ns->uri, &ancdecls);
2792 0             if (!prefix)
2793 0                 goto out;
2794         }
2795 0         ns = js_NewXMLNamespace(cx, prefix, ns->uri, JS_TRUE);
2796 0         if (!ns)
2797 0             goto out;
2798
2799         /*
2800          * If the xml->name was unprefixed, we must remove any declared default
2801          * namespace from decls before appending ns.  How can you get a default
2802          * namespace in decls that doesn't match the one from name?  Apparently
2803          * by calling x.setNamespace(ns) where ns has no prefix.  The other way
2804          * to fix this is to update x's in-scope namespaces when setNamespace
2805          * is called, but that's not specified by ECMA-357.
2806          *
2807          * Likely Erratum here, depending on whether the lack of update to x's
2808          * in-scope namespace in XML.prototype.setNamespace (13.4.4.36) is an
2809          * erratum or not.  Note that changing setNamespace to update the list
2810          * of in-scope namespaces will change x.namespaceDeclarations().
2811          */
2812 0         if (IS_EMPTY(prefix)) {
2813 0             i = XMLArrayFindMember(&decls, ns, namespace_match);
2814 0             if (i != XML_NOT_FOUND)
2815 0                 XMLArrayDelete(cx, &decls, i, JS_TRUE);
2816         }
2817
2818         /*
2819          * In the spec, ancdecls has no name, but is always written out as
2820          * (AncestorNamespaces U namespaceDeclarations).  Since we compute
2821          * that union in ancdecls, any time we append a namespace strong
2822          * ref to decls, we must also append a weak ref to ancdecls.  Order
2823          * matters here: code at label out: releases strong refs in decls.
2824          */
2825 0         if (!XMLARRAY_APPEND(cx, &ancdecls, ns) ||
2826             !XMLARRAY_APPEND(cx, &decls, ns)) {
2827 0             goto out;
2828         }
2829     }
2830
2831     /* Format the element or point-tag into sb. */
2832 0     js_AppendChar(&sb, '<');
2833
2834 0     if (ns->prefix && !IS_EMPTY(ns->prefix)) {
2835 0         js_AppendJSString(&sb, ns->prefix);
2836 0         js_AppendChar(&sb, ':');
2837     }
2838 0     js_AppendJSString(&sb, xml->name->localName);
2839
2840     /*
2841      * Step 16 makes a union to avoid writing two loops in step 17, to share
2842      * common attribute value appending spec-code.  We prefer two loops for
2843      * faster code and less data overhead.
2844      */
2845
2846     /* Step 17(c): append XML namespace declarations. */
2847 0     XMLArrayCursorInit(&cursor, &decls);
2848 0     while ((ns2 = (JSXMLNamespace *) XMLArrayCursorNext(&cursor)) != NULL) {
2849 0         JS_ASSERT(ns2->declared);
2850
2851 0         js_AppendCString(&sb, " xmlns");
2852
2853         /* 17(c)(ii): NULL means *undefined* here. */
2854 0         if (!ns2->prefix) {
2855 0             prefix = GeneratePrefix(cx, ns2->uri, &ancdecls);
2856 0             if (!prefix)
2857 0                 break;
2858 0             ns2->prefix = prefix;
2859         }
2860
2861         /* 17(c)(iii). */
2862 0         if (!IS_EMPTY(ns2->prefix)) {
2863 0             js_AppendChar(&sb, ':');
2864 0             js_AppendJSString(&sb, ns2->prefix);
2865         }
2866
2867         /* 17(d-g). */
2868 0         AppendAttributeValue(cx, &sb, ns2->uri);
2869     }
2870 0     XMLArrayCursorFinish(&cursor);
2871 0     if (ns2)
2872 0         goto out;
2873
2874     /* Step 17(b): append attributes. */
2875 0     XMLArrayCursorInit(&cursor, &xml->xml_attrs);
2876 0     while ((attr = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2877 0         js_AppendChar(&sb, ' ');
2878 0         ns2 = GetNamespace(cx, attr->name, &ancdecls);
2879 0         if (!ns2)
2880 0             break;
2881
2882         /* 17(b)(ii): NULL means *undefined* here. */
2883 0         if (!ns2->prefix) {
2884 0             prefix = GeneratePrefix(cx, ns2->uri, &ancdecls);
2885 0             if (!prefix)
2886 0                 break;
2887
2888             /* Again, we avoid copying ns2 until we know it's prefix-less. */
2889 0             ns2 = js_NewXMLNamespace(cx, prefix, ns2->uri, JS_TRUE);
2890 0             if (!ns2)
2891 0                 break;
2892
2893             /*
2894              * In the spec, ancdecls has no name, but is always written out as
2895              * (AncestorNamespaces U namespaceDeclarations).  Since we compute
2896              * that union in ancdecls, any time we append a namespace strong
2897              * ref to decls, we must also append a weak ref to ancdecls.  Order
2898              * matters here: code at label out: releases strong refs in decls.
2899              */
2900 0             if (!XMLARRAY_APPEND(cx, &ancdecls, ns2) ||
2901                 !XMLARRAY_APPEND(cx, &decls, ns2)) {
2902 0                 break;
2903             }
2904         }
2905
2906         /* 17(b)(iii). */
2907 0         if (!IS_EMPTY(ns2->prefix)) {
2908 0             js_AppendJSString(&sb, ns2->prefix);
2909 0             js_AppendChar(&sb, ':');
2910         }
2911
2912         /* 17(b)(iv). */
2913 0         js_AppendJSString(&sb, attr->name->localName);
2914
2915         /* 17(d-g). */
2916 0         AppendAttributeValue(cx, &sb, attr->xml_value);
2917     }
2918 0     XMLArrayCursorFinish(&cursor);
2919 0     if (attr)
2920 0         goto out;
2921
2922     /* Step 18: handle point tags. */
2923 0     n = xml->xml_kids.length;
2924 0     if (n == 0) {
2925 0         js_AppendCString(&sb, "/>");
2926     } else {
2927         /* Steps 19 through 25: handle element content, and open the end-tag. */
2928 0         js_AppendChar(&sb, '>');
2929 0         indentKids = n > 1 ||
2930                      (n == 1 &&
2931                       (kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
2932                       kid->xml_class != JSXML_CLASS_TEXT);
2933
2934 0         if (pretty && indentKids) {
2935 0             if (!GetUint32XMLSetting(cx, js_prettyIndent_str, &i))
2936 0                 goto out;
2937 0             nextIndentLevel = indentLevel + i;
2938         } else {
2939 0             nextIndentLevel = 0;
2940         }
2941
2942 0         XMLArrayCursorInit(&cursor, &xml->xml_kids);
2943 0         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2944 0             if (pretty && indentKids)
2945 0                 js_AppendChar(&sb, '\n');
2946
2947 0             kidstr = XMLToXMLString(cx, kid, &ancdecls, nextIndentLevel);
2948 0             if (!kidstr)
2949 0                 break;
2950
2951 0             js_AppendJSString(&sb, kidstr);
2952         }
2953 0         XMLArrayCursorFinish(&cursor);
2954 0         if (kid)
2955 0             goto out;
2956
2957 0         if (pretty && indentKids) {
2958 0             js_AppendChar(&sb, '\n');
2959 0             js_RepeatChar(&sb, ' ', indentLevel);
2960         }
2961 0         js_AppendCString(&sb, "</");
2962
2963         /* Step 26. */
2964 0         if (ns->prefix && !IS_EMPTY(ns->prefix)) {
2965 0             js_AppendJSString(&sb, ns->prefix);
2966 0             js_AppendChar(&sb, ':');
2967         }
2968
2969         /* Step 27. */
2970 0         js_AppendJSString(&sb, xml->name->localName);
2971 0         js_AppendChar(&sb, '>');
2972     }
2973
2974 0     if (!STRING_BUFFER_OK(&sb)) {
2975 0         JS_ReportOutOfMemory(cx);
2976 0         goto out;
2977     }
2978
2979 0     str = js_NewString(cx, sb.base, STRING_BUFFER_OFFSET(&sb), 0);
2980 out:
2981 0     JS_LeaveLocalRootScope(cx);
2982 0     if (!str && STRING_BUFFER_OK(&sb))
2983 0         js_FinishStringBuffer(&sb);
2984 0     XMLArrayFinish(cx, &decls);
2985 0     if (ancdecls.capacity != 0)
2986 0         XMLArrayFinish(cx, &ancdecls);
2987 0     return str;
2988 }
2989
2990 /* ECMA-357 10.2 */
2991 static JSString *
2992 ToXMLString(JSContext *cx, jsval v)
2993 0 {
2994 0     JSObject *obj;
2995 0     JSString *str;
2996 0     JSXML *xml;
2997
2998 0     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
2999 0         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3000                              JSMSG_BAD_XML_CONVERSION,
3001                              js_type_str[JSVAL_IS_NULL(v)
3002                                          ? JSTYPE_NULL
3003                                          : JSTYPE_VOID]);
3004 0         return NULL;
3005     }
3006
3007 0     if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
3008 0         return js_ValueToString(cx, v);
3009
3010 0     if (JSVAL_IS_STRING(v))
3011 0         return EscapeElementValue(cx, NULL, JSVAL_TO_STRING(v));
3012
3013 0     obj = JSVAL_TO_OBJECT(v);
3014 0     if (!OBJECT_IS_XML(cx, obj)) {
3015 0         if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, &v))
3016 0             return NULL;
3017 0         str = js_ValueToString(cx, v);
3018 0         if (!str)
3019 0             return NULL;
3020 0         return EscapeElementValue(cx, NULL, str);
3021     }
3022
3023     /* Handle non-element cases in this switch, returning from each case. */
3024 0     xml = (JSXML *) JS_GetPrivate(cx, obj);
3025 0     return XMLToXMLString(cx, xml, NULL, 0);
3026 }
3027
3028 static JSXMLQName *
3029 ToAttributeName(JSContext *cx, jsval v)
3030 0 {
3031 0     JSString *name, *uri, *prefix;
3032 0     JSObject *obj;
3033 0     JSClass *clasp;
3034 0     JSXMLQName *qn;
3035 0     JSTempValueRooter tvr;
3036
3037 0     if (JSVAL_IS_STRING(v)) {
3038 0         name = JSVAL_TO_STRING(v);
3039 0         uri = prefix = cx->runtime->emptyString;
3040     } else {
3041 0         if (JSVAL_IS_PRIMITIVE(v)) {
3042 0             name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
3043 0             if (name) {
3044 0                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3045                                      JSMSG_BAD_XML_ATTR_NAME,
3046                                      JS_GetStringBytes(name));
3047             }
3048 0             return NULL;
3049         }
3050
3051 0         obj = JSVAL_TO_OBJECT(v);
3052 0         clasp = OBJ_GET_CLASS(cx, obj);
3053 0         if (clasp == &js_AttributeNameClass)
3054 0             return (JSXMLQName *) JS_GetPrivate(cx, obj);
3055
3056 0         if (clasp == &js_QNameClass.base) {
3057 0             qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
3058 0             uri = qn->uri;
3059 0             prefix = qn->prefix;
3060 0             name = qn->localName;
3061         } else {
3062 0             if (clasp == &js_AnyNameClass) {
3063 0                 name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
3064             } else {
3065 0                 name = js_ValueToString(cx, v);
3066 0                 if (!name)
3067 0                     return NULL;
3068             }
3069 0             uri = prefix = cx->runtime->emptyString;
3070         }
3071     }
3072
3073 0     qn = js_NewXMLQName(cx, uri, prefix, name);
3074 0     if (!qn)
3075 0         return NULL;
3076
3077     /*
3078      * Temp and local root scope APIs take GC-thing pointers tagged as jsvals
3079      * and blindly untag.  Since qn is a GC-thing pointer, we can treat it as
3080      * an object pointer.
3081      */
3082 0     JS_PUSH_SINGLE_TEMP_ROOT(cx, OBJECT_TO_JSVAL(qn), &tvr);
3083 0     obj = js_GetAttributeNameObject(cx, qn);
3084 0     JS_POP_TEMP_ROOT(cx, &tvr);
3085 0     if (!obj)
3086 0         return NULL;
3087 0     return qn;
3088 }
3089
3090 static JSXMLQName *
3091 ToXMLName(JSContext *cx, jsval v, jsid *funidp)
3092 0 {
3093 0     JSString *name;
3094 0     JSObject *obj;
3095 0     JSClass *clasp;
3096 0     uint32 index;
3097 0     JSXMLQName *qn;
3098 0     JSAtom *atom;
3099
3100 0     if (JSVAL_IS_STRING(v)) {
3101 0         name = JSVAL_TO_STRING(v);
3102     } else {
3103 0         if (JSVAL_IS_PRIMITIVE(v)) {
3104 0             name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
3105 0             if (name)
3106 0                 goto bad;
3107 0             return NULL;
3108         }
3109
3110 0         obj = JSVAL_TO_OBJECT(v);
3111 0         clasp = OBJ_GET_CLASS(cx, obj);
3112 0         if (clasp == &js_AttributeNameClass || clasp == &js_QNameClass.base)
3113 0             goto out;
3114 0         if (clasp == &js_AnyNameClass) {
3115 0             name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
3116 0             goto construct;
3117         }
3118 0         name = js_ValueToString(cx, v);
3119 0         if (!name)
3120 0             return NULL;
3121     }
3122
3123     /*
3124      * ECMA-357 10.6.1 step 1 seems to be incorrect.  The spec says:
3125      *
3126      * 1. If ToString(ToNumber(P)) == ToString(P), throw a TypeError exception
3127      *
3128      * First, _P_ should be _s_, to refer to the given string.
3129      *
3130      * Second, why does ToXMLName applied to the string type throw TypeError
3131      * only for numeric literals without any leading or trailing whitespace?
3132      *
3133      * If the idea is to reject uint32 property names, then the check needs to
3134      * be stricter, to exclude hexadecimal and floating point literals.
3135      */
3136 0     if (js_IdIsIndex(STRING_TO_JSVAL(name), &index))
3137 0         goto bad;
3138
3139 0     if (*JSSTRING_CHARS(name) == '@') {
3140 0         name = js_NewDependentString(cx, name, 1, JSSTRING_LENGTH(name) - 1, 0);
3141 0         if (!name)
3142 0             return NULL;
3143 0         *funidp = 0;
3144 0         return ToAttributeName(cx, STRING_TO_JSVAL(name));
3145     }
3146
3147 construct:
3148 0     v = STRING_TO_JSVAL(name);
3149 0     obj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &v);
3150 0     if (!obj)
3151 0         return NULL;
3152
3153 out:
3154 0     qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
3155 0     atom = cx->runtime->atomState.lazy.functionNamespaceURIAtom;
3156 0     if (qn->uri && atom &&
3157         (qn->uri == ATOM_TO_STRING(atom) ||
3158          !js_CompareStrings(qn->uri, ATOM_TO_STRING(atom)))) {
3159 0         if (!JS_ValueToId(cx, STRING_TO_JSVAL(qn->localName), funidp))
3160 0             return NULL;
3161     } else {
3162 0         *funidp = 0;
3163     }
3164 0     return qn;
3165
3166 bad:
3167 0     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3168                          JSMSG_BAD_XML_NAME,
3169                          js_ValueToPrintableString(cx, STRING_TO_JSVAL(name)));
3170 0     return NULL;
3171 }
3172
3173 /* ECMA-357 9.1.1.13 XML [[AddInScopeNamespace]]. */
3174 static JSBool
3175 AddInScopeNamespace(JSContext *cx, JSXML *xml, JSXMLNamespace *ns)
3176 0 {
3177 0     JSXMLNamespace *match, *ns2;
3178 0     uint32 i, n, m;
3179
3180 0     if (xml->xml_class != JSXML_CLASS_ELEMENT)
3181 0         return JS_TRUE;
3182
3183     /* NULL means *undefined* here -- see ECMA-357 9.1.1.13 step 2. */
3184 0     if (!ns->prefix) {
3185 0         match = NULL;
3186 0         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3187 0             ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3188 0             if (ns2 && !js_CompareStrings(ns2->uri, ns->uri)) {
3189 0                 match = ns2;
3190 0                 break;
3191             }
3192         }
3193 0         if (!match && !XMLARRAY_ADD_MEMBER(cx, &xml->xml_namespaces, n, ns))
3194 0             return JS_FALSE;
3195     } else {
3196 0         if (IS_EMPTY(ns->prefix) && IS_EMPTY(xml->name->uri))
3197 0             return JS_TRUE;
3198 0         match = NULL;
3199 #ifdef __GNUC__         /* suppress bogus gcc warnings */
3200 0         m = XML_NOT_FOUND;
3201 #endif
3202 0         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3203 0             ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3204 0             if (ns2 && ns2->prefix &&
3205                 !js_CompareStrings(ns2->prefix, ns->prefix)) {
3206 0                 match = ns2;
3207 0                 m = i;
3208 0                 break;
3209             }
3210         }
3211 0         if (match && js_CompareStrings(match->uri, ns->uri)) {
3212 0             ns2 = XMLARRAY_DELETE(cx, &xml->xml_namespaces, m, JS_TRUE,
3213                                   JSXMLNamespace);
3214 0             JS_ASSERT(ns2 == match);
3215 0             match->prefix = NULL;
3216 0             if (!AddInScopeNamespace(cx, xml, match))
3217 0                 return JS_FALSE;
3218         }
3219 0         if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
3220 0             return JS_FALSE;
3221     }
3222
3223     /* OPTION: enforce that descendants have superset namespaces. */
3224 0     return JS_TRUE;
3225 }
3226
3227 /* ECMA-357 9.2.1.6 XMLList [[Append]]. */
3228 static JSBool
3229 Append(JSContext *cx, JSXML *list, JSXML *xml)
3230 0 {
3231 0     uint32 i, j, k, n;
3232 0     JSXML *kid;
3233
3234 0     JS_ASSERT(list->xml_class == JSXML_CLASS_LIST);
3235 0     i = list->xml_kids.length;
3236 0     n = 1;
3237 0     if (xml->xml_class == JSXML_CLASS_LIST) {
3238 0         list->xml_target = xml->xml_target;
3239 0         list->xml_targetprop = xml->xml_targetprop;
3240 0         n = JSXML_LENGTH(xml);
3241 0         k = i + n;
3242 0         if (!XMLArraySetCapacity(cx, &list->xml_kids, k))
3243 0             return JS_FALSE;
3244 0         for (j = 0; j < n; j++) {
3245 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, j, JSXML);
3246 0             if (kid)
3247 0                 XMLARRAY_SET_MEMBER(&list->xml_kids, i + j, kid);
3248         }
3249 0         return JS_TRUE;
3250     }
3251
3252 0     list->xml_target = xml->parent;
3253 0     if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
3254 0         list->xml_targetprop = NULL;
3255     else
3256 0         list->xml_targetprop = xml->name;
3257 0     if (!XMLARRAY_ADD_MEMBER(cx, &list->xml_kids, i, xml))
3258 0         return JS_FALSE;
3259 0     return JS_TRUE;
3260 }
3261
3262 /* ECMA-357 9.1.1.7 XML [[DeepCopy]] and 9.2.1.7 XMLList [[DeepCopy]]. */
3263 static JSXML *
3264 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags);
3265
3266 static JSXML *
3267 DeepCopy(JSContext *cx, JSXML *xml, JSObject *obj, uintN flags)
3268 0 {
3269 0     JSXML *copy;
3270 0     JSBool ok;
3271
3272     /* Our caller may not be protecting newborns with a local root scope. */
3273 0     if (!JS_EnterLocalRootScope(cx))
3274 0         return NULL;
3275 0     copy = DeepCopyInLRS(cx, xml, flags);
3276 0     if (copy) {
3277 0         if (obj) {
3278             /* Caller provided the object for this copy, hook 'em up. */
3279 0             ok = JS_SetPrivate(cx, obj, copy);
3280 0             if (ok)
3281 0                 copy->object = obj;
3282         } else {
3283 0             ok = js_GetXMLObject(cx, copy) != NULL;
3284         }
3285 0         if (!ok)
3286 0             copy = NULL;
3287     }
3288 0     JS_LeaveLocalRootScope(cx);
3289 0     return copy;
3290 }
3291
3292 /*
3293  * (i) We must be in a local root scope (InLRS).
3294  * (ii) parent must have a rooted object.
3295  * (iii) from's owning object must be locked if not thread-local.
3296  */
3297 static JSBool
3298 DeepCopySetInLRS(JSContext *cx, JSXMLArray *from, JSXMLArray *to, JSXML *parent,
3299                  uintN flags)
3300 0 {
3301 0     uint32 j, n;
3302 0     JSXMLArrayCursor cursor;
3303 0     JSBool ok;
3304 0     JSXML *kid, *kid2;
3305 0     JSString *str;
3306
3307 0     JS_ASSERT(cx->localRootStack);
3308
3309 0     n = from->length;
3310 0     if (!XMLArraySetCapacity(cx, to, n))
3311 0         return JS_FALSE;
3312
3313 0     XMLArrayCursorInit(&cursor, from);
3314 0     j = 0;
3315 0     ok = JS_TRUE;
3316 0     while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
3317 0         if ((flags & XSF_IGNORE_COMMENTS) &&
3318             kid->xml_class == JSXML_CLASS_COMMENT) {
3319 0             continue;
3320         }
3321 0         if ((flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS) &&
3322             kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
3323 0             continue;
3324         }
3325 0         if ((flags & XSF_IGNORE_WHITESPACE) &&
3326             (kid->xml_flags & XMLF_WHITESPACE_TEXT)) {
3327 0             continue;
3328         }
3329 0         kid2 = DeepCopyInLRS(cx, kid, flags);
3330 0         if (!kid2) {
3331 0             to->length = j;
3332 0             ok = JS_FALSE;
3333 0             break;
3334         }
3335
3336 0         if ((flags & XSF_IGNORE_WHITESPACE) &&
3337             n > 1 && kid2->xml_class == JSXML_CLASS_TEXT) {
3338 0             str = ChompXMLWhitespace(cx, kid2->xml_value);
3339 0             if (!str) {
3340 0                 to->length = j;
3341 0                 ok = JS_FALSE;
3342 0                 break;
3343             }
3344 0             kid2->xml_value = str;
3345         }
3346
3347 0         XMLARRAY_SET_MEMBER(to, j, kid2);
3348 0         ++j;
3349 0         if (parent->xml_class != JSXML_CLASS_LIST)
3350 0             kid2->parent = parent;
3351     }
3352 0     XMLArrayCursorFinish(&cursor);
3353 0     if (!ok)
3354 0         return JS_FALSE;
3355
3356 0     if (j < n)
3357 0         XMLArrayTrim(to);
3358 0     return JS_TRUE;
3359 }
3360
3361 static JSXML *
3362 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags)
3363 0 {
3364 0     JSXML *copy;
3365 0     JSXMLQName *qn;
3366 0     JSBool ok;
3367 0     uint32 i, n;
3368 0     JSXMLNamespace *ns, *ns2;
3369
3370     /* Our caller must be protecting newborn objects. */
3371 0     JS_ASSERT(cx->localRootStack);
3372
3373 0     copy = js_NewXML(cx, xml->xml_class);
3374 0     if (!copy)
3375 0         return NULL;
3376 0     qn = xml->name;
3377 0     if (qn) {
3378 0         qn = js_NewXMLQName(cx, qn->uri, qn->prefix, qn->localName);
3379 0         if (!qn) {
3380 0             ok = JS_FALSE;
3381 0             goto out;
3382         }
3383     }
3384 0     copy->name = qn;
3385 0     copy->xml_flags = xml->xml_flags;
3386
3387 0     if (JSXML_HAS_VALUE(xml)) {
3388 0         copy->xml_value = xml->xml_value;
3389 0         ok = JS_TRUE;
3390     } else {
3391 0         ok = DeepCopySetInLRS(cx, &xml->xml_kids, &copy->xml_kids, copy, flags);
3392 0         if (!ok)
3393 0             goto out;
3394
3395 0         if (xml->xml_class == JSXML_CLASS_LIST) {
3396 0             copy->xml_target = xml->xml_target;
3397 0             copy->xml_targetprop = xml->xml_targetprop;
3398         } else {
3399 0             n = xml->xml_namespaces.length;
3400 0             ok = XMLArraySetCapacity(cx, &copy->xml_namespaces, n);
3401 0             if (!ok)
3402 0                 goto out;
3403 0             for (i = 0; i < n; i++) {
3404 0                 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3405 0                 if (!ns)
3406 0                     continue;
3407 0                 ns2 = js_NewXMLNamespace(cx, ns->prefix, ns->uri, ns->declared);
3408 0                 if (!ns2) {
3409 0                     copy->xml_namespaces.length = i;
3410 0                     ok = JS_FALSE;
3411 0                     goto out;
3412                 }
3413 0                 XMLARRAY_SET_MEMBER(&copy->xml_namespaces, i, ns2);
3414             }
3415
3416 0             ok = DeepCopySetInLRS(cx, &xml->xml_attrs, &copy->xml_attrs, copy,
3417                                   0);
3418 0             if (!ok)
3419 0                 goto out;
3420         }
3421     }
3422
3423 out:
3424 0     if (!ok)
3425 0         return NULL;
3426 0     return copy;
3427 }
3428
3429 static void
3430 ReportBadXMLName(JSContext *cx, jsval id)
3431 0 {
3432 0     JSString *name;
3433
3434 0     name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, id, NULL);
3435 0     if (name) {
3436 0         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3437                              JSMSG_BAD_XML_NAME,
3438                              JS_GetStringBytes(name));
3439     }
3440 }
3441
3442 /* ECMA-357 9.1.1.4 XML [[DeleteByIndex]]. */
3443 static JSBool
3444 DeleteByIndex(JSContext *cx, JSXML *xml, jsval id, jsval *vp)
3445 0 {
3446 0     uint32 index;
3447 0     JSXML *kid;
3448
3449 0     if (!js_IdIsIndex(id, &index)) {
3450 0         ReportBadXMLName(cx, id);
3451 0         return JS_FALSE;
3452     }
3453
3454 0     if (JSXML_HAS_KIDS(xml) && index < xml->xml_kids.length) {
3455 0         kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3456 0         if (kid)
3457 0             kid->parent = NULL;
3458 0         XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
3459     }
3460
3461 0     *vp = JSVAL_TRUE;
3462 0     return JS_TRUE;
3463 }
3464
3465 typedef JSBool (*JSXMLNameMatcher)(JSXMLQName *nameqn, JSXML *xml);
3466
3467 static JSBool
3468 MatchAttrName(JSXMLQName *nameqn, JSXML *attr)
3469 0 {
3470 0     JSXMLQName *attrqn = attr->name;
3471
3472 0     return (IS_STAR(nameqn->localName) ||
3473             !js_CompareStrings(attrqn->localName, nameqn->localName)) &&
3474            (!nameqn->uri ||
3475             !js_CompareStrings(attrqn->uri, nameqn->uri));
3476 }
3477
3478 static JSBool
3479 MatchElemName(JSXMLQName *nameqn, JSXML *elem)
3480 0 {
3481 0     return (IS_STAR(nameqn->localName) ||
3482             (elem->xml_class == JSXML_CLASS_ELEMENT &&
3483              !js_CompareStrings(elem->name->localName, nameqn->localName))) &&
3484            (!nameqn->uri ||
3485             (elem->xml_class == JSXML_CLASS_ELEMENT &&
3486              !js_CompareStrings(elem->name->uri, nameqn->uri)));
3487 }
3488
3489 /* ECMA-357 9.1.1.8 XML [[Descendants]] and 9.2.1.8 XMLList [[Descendants]]. */
3490 static JSBool
3491 DescendantsHelper(JSContext *cx, JSXML *xml, JSXMLQName *nameqn, JSXML *list)
3492 0 {
3493 0     uint32 i, n;
3494 0     JSXML *attr, *kid;
3495
3496 0     if (xml->xml_class == JSXML_CLASS_ELEMENT &&
3497         OBJ_GET_CLASS(cx, nameqn->object) == &js_AttributeNameClass) {
3498 0         for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
3499 0             attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3500 0             if (attr && MatchAttrName(nameqn, attr)) {
3501 0                 if (!Append(cx, list, attr))
3502 0                     return JS_FALSE;
3503             }
3504         }
3505     }
3506
3507 0     for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
3508 0         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3509 0         if (!kid)
3510 0             continue;
3511 0         if (OBJ_GET_CLASS(cx, nameqn->object) != &js_AttributeNameClass &&
3512             MatchElemName(nameqn, kid)) {
3513 0             if (!Append(cx, list, kid))
3514 0                 return JS_FALSE;
3515         }
3516 0         if (!DescendantsHelper(cx, kid, nameqn, list))
3517 0             return JS_FALSE;
3518     }
3519 0     return JS_TRUE;
3520 }
3521
3522 static JSXML *
3523 Descendants(JSContext *cx, JSXML *xml, jsval id)
3524 0 {
3525 0     jsid funid;
3526 0     JSXMLQName *nameqn;
3527 0     JSObject *listobj;
3528 0     JSXML *list, *kid;
3529 0     uint32 i, n;
3530 0     JSBool ok;
3531
3532 0     nameqn = ToXMLName(cx, id, &funid);
3533 0     if (!nameqn)
3534 0         return NULL;
3535
3536 0     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
3537 0     if (!listobj)
3538 0         return NULL;
3539 0     list = (JSXML *) JS_GetPrivate(cx, listobj);
3540 0     if (funid)
3541 0         return list;
3542
3543     /*
3544      * Protect nameqn's object and strings from GC by linking list to it
3545      * temporarily.  The cx->newborn[GCX_OBJECT] GC root protects listobj,
3546      * which protects list.  Any other object allocations occuring beneath
3547      * DescendantsHelper use local roots.
3548      */
3549 0     list->name = nameqn;
3550 0     if (!JS_EnterLocalRootScope(cx))
3551 0         return NULL;
3552 0     if (xml->xml_class == JSXML_CLASS_LIST) {
3553 0         ok = JS_TRUE;
3554 0         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
3555 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3556 0             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
3557 0                 ok = DescendantsHelper(cx, kid, nameqn, list);
3558 0                 if (!ok)
3559 0                     break;
3560             }
3561         }
3562     } else {
3563 0         ok = DescendantsHelper(cx, xml, nameqn, list);
3564     }
3565 0     JS_LeaveLocalRootScope(cx);
3566 0     if (!ok)
3567 0         return NULL;
3568 0     list->name = NULL;
3569 0     return list;
3570 }
3571
3572 static JSBool
3573 xml_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
3574
3575 /* Recursive (JSXML *) parameterized version of Equals. */
3576 static JSBool
3577 XMLEquals(JSContext *cx, JSXML *xml, JSXML *vxml, JSBool *bp)
3578 0 {
3579 0     JSXMLQName *qn, *vqn;
3580 0     uint32 i, j, n;
3581 0     JSXMLArrayCursor cursor, vcursor;
3582 0     JSXML *kid, *vkid, *attr, *vattr;
3583 0     JSBool ok;
3584 0     JSObject *xobj, *vobj;
3585
3586 retry:
3587 0     if (xml->xml_class != vxml->xml_class) {
3588 0         if (xml->xml_class == JSXML_CLASS_LIST && xml->xml_kids.length == 1) {
3589 0             xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3590 0             if (xml)
3591 0                 goto retry;
3592         }
3593 0         if (vxml->xml_class == JSXML_CLASS_LIST && vxml->xml_kids.length == 1) {
3594 0             vxml = XMLARRAY_MEMBER(&vxml->xml_kids, 0, JSXML);
3595 0             if (vxml)
3596 0                 goto retry;
3597         }
3598 0         *bp = JS_FALSE;
3599 0         return JS_TRUE;
3600     }
3601
3602 0     qn = xml->name;
3603 0     vqn = vxml->name;
3604 0     if (qn) {
3605 0         *bp = vqn &&
3606               !js_CompareStrings(qn->localName, vqn->localName) &&
3607               !js_CompareStrings(qn->uri, vqn->uri);
3608     } else {
3609 0         *bp = vqn == NULL;
3610     }
3611 0     if (!*bp)
3612 0         return JS_TRUE;
3613
3614 0     if (JSXML_HAS_VALUE(xml)) {
3615 0         *bp = !js_CompareStrings(xml->xml_value, vxml->xml_value);
3616 0     } else if (xml->xml_kids.length != vxml->xml_kids.length) {
3617 0         *bp = JS_FALSE;
3618     } else {
3619 0         XMLArrayCursorInit(&cursor, &xml->xml_kids);
3620 0         XMLArrayCursorInit(&vcursor, &vxml->xml_kids);
3621 0         for (;;) {
3622 0             kid = (JSXML *) XMLArrayCursorNext(&cursor);
3623 0             vkid = (JSXML *) XMLArrayCursorNext(&vcursor);
3624 0             if (!kid || !vkid) {
3625 0                 *bp = !kid && !vkid;
3626 0                 ok = JS_TRUE;
3627 0                 break;
3628             }
3629 0             xobj = js_GetXMLObject(cx, kid);
3630 0             vobj = js_GetXMLObject(cx, vkid);
3631 0             ok = xobj && vobj &&
3632                  xml_equality(cx, xobj, OBJECT_TO_JSVAL(vobj), bp);
3633 0             if (!ok || !*bp)
3634                 break;
3635         }
3636 0         XMLArrayCursorFinish(&vcursor);
3637 0         XMLArrayCursorFinish(&cursor);
3638 0         if (!ok)
3639 0             return JS_FALSE;
3640
3641 0         if (*bp && xml->xml_class == JSXML_CLASS_ELEMENT) {
3642 0             n = xml->xml_attrs.length;
3643 0             if (n != vxml->xml_attrs.length)
3644 0                 *bp = JS_FALSE;
3645 0             for (i = 0; *bp && i < n; i++) {
3646 0                 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3647 0                 if (!attr)
3648 0                     continue;
3649 0                 j = XMLARRAY_FIND_MEMBER(&vxml->xml_attrs, attr, attr_identity);
3650 0                 if (j == XML_NOT_FOUND) {
3651 0                     *bp = JS_FALSE;
3652 0                     break;
3653                 }
3654 0                 vattr = XMLARRAY_MEMBER(&vxml->xml_attrs, j, JSXML);
3655 0                 if (!vattr)
3656 0                     continue;
3657 0                 *bp = !js_CompareStrings(attr->xml_value, vattr->xml_value);
3658             }
3659         }
3660     }
3661
3662 0     return JS_TRUE;
3663 }
3664
3665 /* ECMA-357 9.1.1.9 XML [[Equals]] and 9.2.1.9 XMLList [[Equals]]. */
3666 static JSBool
3667 Equals(JSContext *cx, JSXML *xml, jsval v, JSBool *bp)
3668 0 {
3669 0     JSObject *vobj;
3670 0     JSXML *vxml;
3671
3672 0     if (JSVAL_IS_PRIMITIVE(v)) {
3673 0         *bp = JS_FALSE;
3674 0         if (xml->xml_class == JSXML_CLASS_LIST) {
3675 0             if (xml->xml_kids.length == 1) {
3676 0                 vxml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3677 0                 if (!vxml)
3678 0                     return JS_TRUE;
3679 0                 vobj = js_GetXMLObject(cx, vxml);
3680 0                 if (!vobj)
3681 0                     return JS_FALSE;
3682 0                 return js_XMLObjectOps.equality(cx, vobj, v, bp);
3683             }
3684 0             if (JSVAL_IS_VOID(v) && xml->xml_kids.length == 0)
3685 0                 *bp = JS_TRUE;
3686         }
3687     } else {
3688 0         vobj = JSVAL_TO_OBJECT(v);
3689 0         if (!OBJECT_IS_XML(cx, vobj)) {
3690 0             *bp = JS_FALSE;
3691         } else {
3692 0             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3693 0             if (!XMLEquals(cx, xml, vxml, bp))
3694 0                 return JS_FALSE;
3695         }
3696     }
3697 0     return JS_TRUE;
3698 }
3699
3700 static JSBool
3701 CheckCycle(JSContext *cx, JSXML *xml, JSXML *kid)
3702 0 {
3703 0     JS_ASSERT(kid->xml_class != JSXML_CLASS_LIST);
3704
3705 0     do {
3706 0         if (xml == kid) {
3707 0             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3708                                  JSMSG_CYCLIC_VALUE, js_XML_str);
3709 0             return JS_FALSE;
3710         }
3711 0     } while ((xml = xml->parent) != NULL);
3712
3713 0     return JS_TRUE;
3714 }
3715
3716 /* ECMA-357 9.1.1.11 XML [[Insert]]. */
3717 static JSBool
3718 Insert(JSContext *cx, JSXML *xml, uint32 i, jsval v)
3719 0 {
3720 0     uint32 j, n;
3721 0     JSXML *vxml, *kid;
3722 0     JSObject *vobj;
3723 0     JSString *str;
3724
3725 0     if (!JSXML_HAS_KIDS(xml))
3726 0         return JS_TRUE;
3727
3728 0     n = 1;
3729 0     vxml = NULL;
3730 0     if (!JSVAL_IS_PRIMITIVE(v)) {
3731 0         vobj = JSVAL_TO_OBJECT(v);
3732 0         if (OBJECT_IS_XML(cx, vobj)) {
3733 0             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3734 0             if (vxml->xml_class == JSXML_CLASS_LIST) {
3735 0                 n = vxml->xml_kids.length;
3736 0                 if (n == 0)
3737 0                     return JS_TRUE;
3738 0                 for (j = 0; j < n; j++) {
3739 0                     kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3740 0                     if (!kid)
3741 0                         continue;
3742 0                     if (!CheckCycle(cx, xml, kid))
3743 0                         return JS_FALSE;
3744                 }
3745 0             } else if (vxml->xml_class == JSXML_CLASS_ELEMENT) {
3746                 /* OPTION: enforce that descendants have superset namespaces. */
3747 0                 if (!CheckCycle(cx, xml, vxml))
3748 0                     return JS_FALSE;
3749             }
3750         }
3751     }
3752 0     if (!vxml) {
3753 0         str = js_ValueToString(cx, v);
3754 0         if (!str)
3755 0             return JS_FALSE;
3756
3757 0         vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3758 0         if (!vxml)
3759 0             return JS_FALSE;
3760 0         vxml->xml_value = str;
3761     }
3762
3763 0     if (i > xml->xml_kids.length)
3764 0         i = xml->xml_kids.length;
3765
3766 0     if (!XMLArrayInsert(cx, &xml->xml_kids, i, n))
3767 0         return JS_FALSE;
3768
3769 0     if (vxml->xml_class == JSXML_CLASS_LIST) {
3770 0         for (j = 0; j < n; j++) {
3771 0             kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3772 0             if (!kid)
3773 0                 continue;
3774 0             kid->parent = xml;
3775 0             XMLARRAY_SET_MEMBER(&xml->xml_kids, i + j, kid);
3776
3777             /* OPTION: enforce that descendants have superset namespaces. */
3778         }
3779     } else {
3780 0         vxml->parent = xml;
3781 0         XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
3782     }
3783 0     return JS_TRUE;
3784 }
3785
3786 static JSBool
3787 IndexToIdVal(JSContext *cx, uint32 index, jsval *idvp)
3788 0 {
3789 0     JSString *str;
3790
3791 0     if (index <= JSVAL_INT_MAX) {
3792 0         *idvp = INT_TO_JSVAL(index);
3793     } else {
3794 0         str = js_NumberToString(cx, (jsdouble) index);
3795 0         if (!str)
3796 0             return JS_FALSE;
3797 0         *idvp = STRING_TO_JSVAL(str);
3798     }
3799 0     return JS_TRUE;
3800 }
3801
3802 /* ECMA-357 9.1.1.12 XML [[Replace]]. */
3803 static JSBool
3804 Replace(JSContext *cx, JSXML *xml, jsval id, jsval v)
3805 0 {
3806 0     uint32 i, n;
3807 0     JSXML *vxml, *kid;
3808 0     JSObject *vobj;
3809 0     jsval junk;
3810 0     JSString *str;
3811
3812 0     if (!JSXML_HAS_KIDS(xml))
3813 0         return JS_TRUE;
3814
3815 0     if (!js_IdIsIndex(id, &i)) {
3816 0         ReportBadXMLName(cx, id);
3817 0         return JS_FALSE;
3818     }
3819
3820     /*
3821      * 9.1.1.12
3822      * [[Replace]] handles _i >= x.[[Length]]_ by incrementing _x.[[Length]_.
3823      * It should therefore constrain callers to pass in _i <= x.[[Length]]_.
3824      */
3825 0     n = xml->xml_kids.length;
3826 0     if (i >= n) {
3827 0         if (!IndexToIdVal(cx, n, &id))
3828 0             return JS_FALSE;
3829 0         i = n;
3830     }
3831
3832 0     vxml = NULL;
3833 0     if (!JSVAL_IS_PRIMITIVE(v)) {
3834 0         vobj = JSVAL_TO_OBJECT(v);
3835 0         if (OBJECT_IS_XML(cx, vobj))
3836 0             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3837     }
3838
3839 0     switch (vxml ? vxml->xml_class : JSXML_CLASS_LIMIT) {
3840       case JSXML_CLASS_ELEMENT:
3841         /* OPTION: enforce that descendants have superset namespaces. */
3842 0         if (!CheckCycle(cx, xml, vxml))
3843 0             return JS_FALSE;
3844       case JSXML_CLASS_COMMENT:
3845       case JSXML_CLASS_PROCESSING_INSTRUCTION:
3846       case JSXML_CLASS_TEXT:
3847 0         goto do_replace;
3848
3849       case JSXML_CLASS_LIST:
3850 0         if (i < n && !DeleteByIndex(cx, xml, id, &junk))
3851 0             return JS_FALSE;
3852 0         if (!Insert(cx, xml, i, v))
3853 0             return JS_FALSE;
3854 0         break;
3855
3856       default:
3857 0         str = js_ValueToString(cx, v);
3858 0         if (!str)
3859 0             return JS_FALSE;
3860
3861 0         vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3862 0         if (!vxml)
3863 0             return JS_FALSE;
3864 0         vxml->xml_value = str;
3865
3866       do_replace:
3867 0         vxml->parent = xml;
3868 0         if (i < n) {
3869 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3870 0             if (kid)
3871 0                 kid->parent = NULL;
3872         }
3873 0         if (!XMLARRAY_ADD_MEMBER(cx, &xml->xml_kids, i, vxml))
3874 0             return JS_FALSE;
3875 0         break;
3876     }
3877
3878 0     return JS_TRUE;
3879 }
3880
3881 /* Forward declared -- its implementation uses other statics that call it. */
3882 static JSBool
3883 ResolveValue(JSContext *cx, JSXML *list, JSXML **result);
3884
3885 /* ECMA-357 9.1.1.3 XML [[Delete]], 9.2.1.3 XML [[Delete]]. */
3886 static JSBool
3887 DeleteProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
3888 0 {
3889 0     JSXML *xml, *kid, *parent;
3890 0     JSBool isIndex;
3891 0     JSXMLArray *array;
3892 0     uint32 length, index, deleteCount;
3893 0     JSXMLQName *nameqn;
3894 0     jsid funid;
3895 0     JSObject *nameobj, *kidobj;
3896 0     JSXMLNameMatcher matcher;
3897
3898 0     xml = (JSXML *) JS_GetPrivate(cx, obj);
3899 0     isIndex = js_IdIsIndex(id, &index);
3900 0     if (JSXML_HAS_KIDS(xml)) {
3901 0         array = &xml->xml_kids;
3902 0         length = array->length;
3903     } else {
3904 0         array = NULL;
3905 0         length = 0;
3906     }
3907
3908 0     if (xml->xml_class == JSXML_CLASS_LIST) {
3909         /* ECMA-357 9.2.1.3. */
3910 0         if (isIndex && index < length) {
3911 0             kid = XMLARRAY_MEMBER(array, index, JSXML);
3912 0             if (!kid)
3913 0                 goto out;
3914 0             parent = kid->parent;
3915 0             if (parent) {
3916 0                 JS_ASSERT(parent != xml);
3917 0                 JS_ASSERT(JSXML_HAS_KIDS(parent));
3918
3919 0                 if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
3920 0                     nameqn = kid->name;
3921 0                     nameobj = js_GetAttributeNameObject(cx, nameqn);
3922 0                     if (!nameobj || !js_GetXMLObject(cx, parent))
3923 0                         return JS_FALSE;
3924
3925 0                     id = OBJECT_TO_JSVAL(nameobj);
3926 0                     if (!DeleteProperty(cx, parent->object, id, vp))
3927 0                         return JS_FALSE;
3928                 } else {
3929 0                     index = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
3930 0                     JS_ASSERT(index != XML_NOT_FOUND);
3931 0                     if (!IndexToIdVal(cx, index, &id))
3932 0                         return JS_FALSE;
3933 0                     if (!DeleteByIndex(cx, parent, id, vp))
3934 0                         return JS_FALSE;
3935                 }
3936             }
3937
3938 0             XMLArrayDelete(cx, array, index, JS_TRUE);
3939         } else {
3940 0             for (index = 0; index < length; index++) {
3941 0                 kid = XMLARRAY_MEMBER(array, index, JSXML);
3942 0                 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
3943 0                     kidobj = js_GetXMLObject(cx, kid);
3944 0                     if (!kidobj || !DeleteProperty(cx, kidobj, id, vp))
3945 0                         return JS_FALSE;
3946                 }
3947             }
3948         }
3949     } else {
3950         /* ECMA-357 9.1.1.3. */
3951 0         if (isIndex) {
3952             /* See NOTE in spec: this variation is reserved for future use. */
3953 0             ReportBadXMLName(cx, id);
3954 0             return JS_FALSE;
3955         }
3956
3957 0         nameqn = ToXMLName(cx, id, &funid);
3958 0         if (!nameqn)
3959 0             return JS_FALSE;
3960 0         if (funid)
3961 0             goto out;
3962 0         nameobj = nameqn->object;
3963
3964 0         if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
3965 0             if (xml->xml_class != JSXML_CLASS_ELEMENT)
3966 0                 goto out;
3967 0             array = &xml->xml_attrs;
3968 0             length = array->length;
3969 0             matcher = MatchAttrName;
3970         } else {
3971 0             matcher = MatchElemName;
3972         }
3973 0         if (length != 0) {
3974 0             deleteCount = 0;
3975 0             for (index = 0; index < length; index++) {
3976 0                 kid = XMLARRAY_MEMBER(array, index, JSXML);
3977 0                 if (kid && matcher(nameqn, kid)) {
3978 0                     kid->parent = NULL;
3979 0                     XMLArrayDelete(cx, array, index, JS_FALSE);
3980 0                     ++deleteCount;
3981 0                 } else if (deleteCount != 0) {
3982 0                     XMLARRAY_SET_MEMBER(array,
3983                                         index - deleteCount,
3984                                         array->vector[index]);
3985                 }
3986             }
3987 0             array->length -= deleteCount;
3988         }
3989     }
3990
3991 out:
3992 0     *vp = JSVAL_TRUE;
3993 0     return JS_TRUE;
3994 }
3995
3996 /*
3997  * Class compatibility mask flag bits stored in xml_methods[i].extra.  If XML
3998  * and XMLList are unified (an incompatible change to ECMA-357), then we don't
3999  * need any of this.
4000  */
4001 #define XML_MASK                0x1
4002 #define XMLLIST_MASK            0x2
4003 #define GENERIC_MASK            (XML_MASK | XMLLIST_MASK)
4004 #define CLASS_TO_MASK(c)        (1 + ((c) == JSXML_CLASS_LIST))
4005
4006 static JSBool
4007 GetFunction(JSContext *cx, JSObject *obj, JSXML *xml, jsid id, jsval *vp)
4008 0 {
4009 0     JSFunction *fun;
4010
4011 0     do {
4012         /* XXXbe really want a separate scope for function::*. */
4013 0         if (!js_GetProperty(cx, obj, id, vp))
4014 0             return JS_FALSE;
4015 0         if (JSVAL_IS_FUNCTION(cx, *vp)) {
4016 0             if (xml && OBJECT_IS_XML(cx, obj)) {
4017 0                 fun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*vp));
4018 0                 if (fun->spare &&
4019                     (fun->spare & CLASS_TO_MASK(xml->xml_class)) == 0) {
4020                     /* XML method called on XMLList or vice versa. */
4021 0                     *vp = JSVAL_VOID;
4022                 }
4023             }
4024 0             break;
4025         }
4026 0     } while ((obj = OBJ_GET_PROTO(cx, obj)) != NULL);
4027 0     return JS_TRUE;
4028 }
4029
4030 static JSBool
4031 SyncInScopeNamespaces(JSContext *cx, JSXML *xml)
4032 0 {
4033 0     JSXMLArray *nsarray;
4034 0     uint32 i, n;
4035 0     JSXMLNamespace *ns;
4036
4037 0     nsarray = &xml->xml_namespaces;
4038 0     while ((xml = xml->parent) != NULL) {
4039 0         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
4040 0             ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
4041 0             if (ns && !XMLARRAY_HAS_MEMBER(nsarray, ns, namespace_identity)) {
4042 0                 if (!XMLARRAY_APPEND(cx, nsarray, ns))
4043 0                     return JS_FALSE;
4044             }
4045         }
4046     }
4047 0     return JS_TRUE;
4048 }
4049
4050 /* ECMA-357 9.1.1.1 XML [[Get]] and 9.2.1.1 XMLList [[Get]]. */
4051 static JSBool
4052 GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
4053 0 {
4054 0     JSXML *xml, *list, *kid;
4055 0     uint32 index;
4056 0     JSObject *kidobj, *listobj, *nameobj;
4057 0     JSXMLQName *nameqn;
4058 0     jsid funid;
4059 0     JSBool ok;
4060 0     JSXMLArrayCursor cursor;
4061 0     jsval kidval;
4062 0     JSXMLArray *array;
4063 0     JSXMLNameMatcher matcher;
4064
4065 0     xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
4066 0     if (!xml)
4067 0         return JS_TRUE;
4068
4069 #ifdef __GNUC__
4070 0     list = NULL;    /* quell GCC overwarning */
4071 #endif
4072
4073 retry:
4074 0     if (xml->xml_class == JSXML_CLASS_LIST) {
4075         /* ECMA-357 9.2.1.1 starts here. */
4076 0         if (js_IdIsIndex(id, &index)) {
4077             /*
4078              * Erratum: 9.2 is not completely clear that indexed properties
4079              * correspond to kids, but that's what it seems to say, and it's
4080              * what any sane user would want.
4081              */
4082 0             if (index < xml->xml_kids.length) {
4083 0                 kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
4084 0                 if (!kid) {
4085 0                     *vp = JSVAL_VOID;
4086 0                     return JS_TRUE;
4087                 }
4088 0                 kidobj = js_GetXMLObject(cx, kid);
4089 0                 if (!kidobj)
4090 0                     return JS_FALSE;
4091
4092 0                 *vp = OBJECT_TO_JSVAL(kidobj);
4093             } else {
4094 0                 *vp = JSVAL_VOID;
4095             }
4096 0             return JS_TRUE;
4097         }
4098
4099 0         nameqn = ToXMLName(cx, id, &funid);
4100 0         if (!nameqn)
4101 0             return JS_FALSE;
4102 0         if (funid)
4103 0             return GetFunction(cx, obj, xml, funid, vp);
4104
4105         /*
4106          * Recursion through GetProperty may allocate more list objects, so
4107          * we make use of local root scopes here.  Each new allocation will
4108          * push the newborn onto the local root stack.
4109          */
4110 0         ok = JS_EnterLocalRootScope(cx);
4111 0         if (!ok)
4112 0             return JS_FALSE;
4113
4114         /*
4115          * NB: nameqn is already protected from GC by cx->newborn[GCX_OBJECT]
4116          * until listobj is created.  After that, a local root keeps listobj
4117          * alive, and listobj's private keeps nameqn alive via targetprop.
4118          */
4119 0         listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4120 0         if (!listobj) {
4121 0             ok = JS_FALSE;
4122         } else {
4123 0             list = (JSXML *) JS_GetPrivate(cx, listobj);
4124 0             list->xml_target = xml;
4125
4126 0             XMLArrayCursorInit(&cursor, &xml->xml_kids);
4127 0             while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
4128 0                 if (kid->xml_class == JSXML_CLASS_ELEMENT) {
4129 0                     kidobj = js_GetXMLObject(cx, kid);
4130 0                     if (!kidobj) {
4131 0                         ok = JS_FALSE;
4132 0                         break;
4133                     }
4134 0                     ok = GetProperty(cx, kidobj, id, &kidval);
4135 0                     if (!ok)
4136 0                         break;
4137 0                     kidobj = JSVAL_TO_OBJECT(kidval);
4138 0                     kid = (JSXML *) JS_GetPrivate(cx, kidobj);
4139 0                     if (JSXML_LENGTH(kid) > 0) {
4140 0                         ok = Append(cx, list, kid);
4141 0                         if (!ok)
4142 0                             break;
4143                     }
4144                 }
4145             }
4146 0             XMLArrayCursorFinish(&cursor);
4147         }
4148     } else {
4149         /* ECMA-357 9.1.1.1 starts here. */
4150 0         if (js_IdIsIndex(id, &index)) {
4151 0             obj = ToXMLList(cx, OBJECT_TO_JSVAL(obj));
4152 0             if (!obj)
4153 0                 return JS_FALSE;
4154 0             xml = (JSXML *) JS_GetPrivate(cx, obj);
4155 0             goto retry;
4156         }
4157
4158 0         nameqn = ToXMLName(cx, id, &funid);
4159 0         if (!nameqn)
4160 0             return JS_FALSE;
4161 0         if (funid)
4162 0             return GetFunction(cx, obj, xml, funid, vp);
4163 0         nameobj = nameqn->object;
4164
4165         /*
4166          * Recursion through GetProperty may allocate more list objects, so
4167          * we make use of local root scopes here.  Each new allocation will
4168          * push the newborn onto the local root stack.
4169          */
4170 0         ok = JS_EnterLocalRootScope(cx);
4171 0         if (!ok)
4172 0             return JS_FALSE;
4173
4174 0         listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4175 0         if (!listobj) {
4176 0             ok = JS_FALSE;
4177         } else {
4178 0             list = (JSXML *) JS_GetPrivate(cx, listobj);
4179 0             list->xml_target = xml;
4180
4181 0             if (JSXML_HAS_KIDS(xml)) {
4182 0                 if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
4183 0                     array = &xml->xml_attrs;
4184 0                     matcher = MatchAttrName;
4185                 } else {
4186 0                     array = &xml->xml_kids;
4187 0                     matcher = MatchElemName;
4188                 }
4189 0                 XMLArrayCursorInit(&cursor, array);
4190 0                 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
4191 0                     if (matcher(nameqn, kid)) {
4192 0                         if (array == &xml->xml_kids &&
4193                             kid->xml_class == JSXML_CLASS_ELEMENT) {
4194 0                             ok = SyncInScopeNamespaces(cx, kid);
4195 0                             if (!ok)
4196 0                                 break;
4197                         }
4198 0                         ok = Append(cx, list, kid);
4199 0                         if (!ok)
4200 0                             break;
4201                     }
4202                 }
4203 0                 XMLArrayCursorFinish(&cursor);
4204             }
4205         }
4206     }
4207
4208     /* Common tail code for list and non-list cases. */
4209 0     JS_LeaveLocalRootScope(cx);
4210 0     if (!ok)
4211 0         return JS_FALSE;
4212
4213     /*
4214      * Erratum: ECMA-357 9.1.1.1 misses that [[Append]] sets the given list's
4215      * [[TargetProperty]] to the property that is being appended. This means
4216      * that any use of the internal [[Get]] property returns a list which,
4217      * when used by e.g. [[Insert]] duplicates the last element matched by id.
4218      * See bug 336921.
4219      */
4220 0     list->xml_targetprop = nameqn;
4221 0     *vp = OBJECT_TO_JSVAL(listobj);
4222 0     return JS_TRUE;
4223 }
4224
4225 static JSXML *
4226 CopyOnWrite(JSContext *cx, JSXML *xml, JSObject *obj)
4227 0 {
4228 0     JS_ASSERT(xml->object != obj);
4229
4230 0     xml = DeepCopy(cx, xml, obj, 0);
4231 0     if (!xml)
4232 0         return NULL;
4233
4234 0     JS_ASSERT(xml->object == obj);
4235 0     return xml;
4236 }
4237
4238 #define CHECK_COPY_ON_WRITE(cx,xml,obj)                                       \
4239     (xml->object == obj ? xml : CopyOnWrite(cx, xml, obj))
4240
4241 static JSString *
4242 KidToString(JSContext *cx, JSXML *xml, uint32 index)
4243 0 {
4244 0     JSXML *kid;
4245 0     JSObject *kidobj;
4246
4247 0     kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
4248 0     if (!kid)
4249 0         return cx->runtime->emptyString;
4250 0     kidobj = js_GetXMLObject(cx, kid);
4251 0     if (!kidobj)
4252 0         return NULL;
4253 0     return js_ValueToString(cx, OBJECT_TO_JSVAL(kidobj));
4254 }
4255
4256 /* ECMA-357 9.1.1.2 XML [[Put]] and 9.2.1.2 XMLList [[Put]]. */
4257 static JSBool
4258 PutProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
4259 0 {
4260 0     JSBool ok, primitiveAssign;
4261     enum { OBJ_ROOT, ID_ROOT, VAL_ROOT };
4262 0     jsval roots[3];
4263 0     JSTempValueRooter tvr;
4264 0     JSXML *xml, *vxml, *rxml, *kid, *attr, *parent, *copy, *kid2, *match;
4265 0     JSObject *vobj, *nameobj, *attrobj, *parentobj, *kidobj, *copyobj;
4266 0     JSXMLQName *targetprop, *nameqn, *attrqn;
4267 0     uint32 index, i, j, k, n, q;
4268 0     jsval attrval, nsval, junk;
4269 0     jsid funid;
4270 0     JSString *left, *right, *space;
4271 0     JSXMLNamespace *ns;
4272
4273 0     xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
4274 0     if (!xml)
4275 0         return JS_TRUE;
4276
4277 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
4278 0     if (!xml)
4279 0         return JS_FALSE;
4280
4281     /* Precompute vxml for 9.2.1.2 2(c)(vii)(2-3) and 2(d) and 9.1.1.2 1. */
4282 0     vxml = NULL;
4283 0     if (!JSVAL_IS_PRIMITIVE(*vp)) {
4284 0         vobj = JSVAL_TO_OBJECT(*vp);
4285 0         if (OBJECT_IS_XML(cx, vobj))
4286 0             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4287     }
4288
4289     /* Control flow after here must exit via label out. */
4290 0     ok = JS_EnterLocalRootScope(cx);
4291 0     if (!ok)
4292 0         return JS_FALSE;
4293 0     roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
4294 0     roots[ID_ROOT] = id;
4295 0     roots[VAL_ROOT] = *vp;
4296 0     JS_PUSH_TEMP_ROOT(cx, 3, roots, &tvr);
4297
4298 0     if (xml->xml_class == JSXML_CLASS_LIST) {
4299         /* ECMA-357 9.2.1.2. */
4300 0         if (js_IdIsIndex(id, &index)) {
4301             /* Step 1 sets i to the property index. */
4302 0             i = index;
4303
4304             /* 2(a-b). */
4305 0             if (xml->xml_target) {
4306 0                 ok = ResolveValue(cx, xml->xml_target, &rxml);
4307 0                 if (!ok)
4308 0                     goto out;
4309 0                 if (!rxml)
4310 0                     goto out;
4311 0                 JS_ASSERT(rxml->object);
4312             } else {
4313 0                 rxml = NULL;
4314             }
4315
4316             /* 2(c). */
4317 0             if (index >= xml->xml_kids.length) {
4318                 /* 2(c)(i). */
4319 0                 if (rxml) {
4320 0                     if (rxml->xml_class == JSXML_CLASS_LIST) {
4321 0                         if (rxml->xml_kids.length != 1)
4322 0                             goto out;
4323 0                         rxml = XMLARRAY_MEMBER(&rxml->xml_kids, 0, JSXML);
4324 0                         if (!rxml)
4325 0                             goto out;
4326 0                         ok = js_GetXMLObject(cx, rxml) != NULL;
4327 0                         if (!ok)
4328 0                             goto out;
4329                     }
4330
4331                     /*
4332                      * Erratum: ECMA-357 9.2.1.2 step 2(c)(ii) sets
4333                      * _y.[[Parent]] = r_ where _r_ is the result of
4334                      * [[ResolveValue]] called on _x.[[TargetObject]] in
4335                      * 2(a)(i).  This can result in text parenting text:
4336                      *
4337                      *    var MYXML = new XML();
4338                      *    MYXML.appendChild(new XML("<TEAM>Giants</TEAM>"));
4339                      *
4340                      * (testcase from Werner Sharp <wsharp@macromedia.com>).
4341                      *
4342                      * To match insertChildAfter, insertChildBefore,
4343                      * prependChild, and setChildren, we should silently
4344                      * do nothing in this case.
4345                      */
4346 0                     if (!JSXML_HAS_KIDS(rxml))
4347 0                         goto out;
4348                 }
4349
4350                 /* 2(c)(ii) is distributed below as several js_NewXML calls. */
4351 0                 targetprop = xml->xml_targetprop;
4352 0                 if (!targetprop || IS_STAR(targetprop->localName)) {
4353                     /* 2(c)(iv)(1-2), out of order w.r.t. 2(c)(iii). */
4354 0                     kid = js_NewXML(cx, JSXML_CLASS_TEXT);
4355 0                     if (!kid)
4356 0                         goto bad;
4357                 } else {
4358 0                     nameobj = js_GetXMLQNameObject(cx, targetprop);
4359 0                     if (!nameobj)
4360 0                         goto bad;
4361 0                     if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
4362                         /*
4363                          * 2(c)(iii)(1-3).
4364                          * Note that rxml can't be null here, because target
4365                          * and targetprop are non-null.
4366                          */
4367 0                         ok = GetProperty(cx, rxml->object, id, &attrval);
4368 0                         if (!ok)
4369 0                             goto out;
4370 0                         attrobj = JSVAL_TO_OBJECT(attrval);
4371 0                         attr = (JSXML *) JS_GetPrivate(cx, attrobj);
4372 0                         if (JSXML_LENGTH(attr) != 0)
4373 0                             goto out;
4374
4375 0                         kid = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4376                     } else {
4377                         /* 2(c)(v). */
4378 0                         kid = js_NewXML(cx, JSXML_CLASS_ELEMENT);
4379                     }
4380 0                     if (!kid)
4381 0                         goto bad;
4382
4383                     /* An important bit of 2(c)(ii). */
4384 0                     kid->name = targetprop;
4385                 }
4386
4387                 /* Final important bit of 2(c)(ii). */
4388 0                 kid->parent = rxml;
4389
4390                 /* 2(c)(vi-vii). */
4391 0                 i = xml->xml_kids.length;
4392 0                 if (kid->xml_class != JSXML_CLASS_ATTRIBUTE) {
4393                     /*
4394                      * 2(c)(vii)(1) tests whether _y.[[Parent]]_ is not null.
4395                      * y.[[Parent]] is here called kid->parent, which we know
4396                      * from 2(c)(ii) is _r_, here called rxml.  So let's just
4397                      * test that!  Erratum, the spec should be simpler here.
4398                      */
4399 0                     if (rxml) {
4400 0                         JS_ASSERT(JSXML_HAS_KIDS(rxml));
4401 0                         n = rxml->xml_kids.length;
4402 0                         j = n - 1;
4403 0                         if (n != 0 && i != 0) {
4404 0                             for (n = j, j = 0; j < n; j++) {
4405 0                                 if (rxml->xml_kids.vector[j] ==
4406                                     xml->xml_kids.vector[i-1]) {
4407 0                                     break;
4408                                 }
4409                             }
4410                         }
4411
4412 0                         kidobj = js_GetXMLObject(cx, kid);
4413 0                         if (!kidobj)
4414 0                             goto bad;
4415 0                         ok = Insert(cx, rxml, j + 1, OBJECT_TO_JSVAL(kidobj));
4416 0                         if (!ok)
4417 0                             goto out;
4418                     }
4419
4420                     /*
4421                      * 2(c)(vii)(2-3).
4422                      * Erratum: [[PropertyName]] in 2(c)(vii)(3) must be a
4423                      * typo for [[TargetProperty]].
4424                      */
4425 0                     if (vxml) {
4426 0                         kid->name = (vxml->xml_class == JSXML_CLASS_LIST)
4427                                     ? vxml->xml_targetprop
4428                                     : vxml->name;
4429                     }
4430                 }
4431
4432                 /* 2(c)(viii). */
4433 0                 ok = Append(cx, xml, kid);
4434 0                 if (!ok)
4435 0                     goto out;
4436             }
4437
4438             /* 2(d). */
4439 0             if (!vxml ||
4440                 vxml->xml_class == JSXML_CLASS_TEXT ||
4441                 vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4442 0                 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4443 0                 if (!ok)
4444 0                     goto out;
4445 0                 roots[VAL_ROOT] = *vp;
4446             }
4447
4448             /* 2(e). */
4449 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
4450 0             if (!kid)
4451 0                 goto out;
4452 0             parent = kid->parent;
4453 0             if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
4454 0                 nameobj = js_GetAttributeNameObject(cx, kid->name);
4455 0                 if (!nameobj)
4456 0                     goto bad;
4457 0                 id = OBJECT_TO_JSVAL(nameobj);
4458
4459 0                 if (parent) {
4460                     /* 2(e)(i). */
4461 0                     parentobj = parent->object;
4462 0                     ok = PutProperty(cx, parentobj, id, vp);
4463 0                     if (!ok)
4464 0                         goto out;
4465
4466                     /* 2(e)(ii). */
4467 0                     ok = GetProperty(cx, parentobj, id, vp);
4468 0                     if (!ok)
4469 0                         goto out;
4470 0                     attr = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*vp));
4471
4472                     /* 2(e)(iii). */
4473 0                     xml->xml_kids.vector[i] = attr->xml_kids.vector[0];
4474                 }
4475             }
4476
4477             /* 2(f). */
4478 0             else if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4479                 /* 2(f)(i) Create a shallow copy _c_ of _V_. */
4480 0                 copyobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4481 0                 if (!copyobj)
4482 0                     goto bad;
4483 0                 copy = (JSXML *) JS_GetPrivate(cx, copyobj);
4484 0                 n = vxml->xml_kids.length;
4485 0                 ok = XMLArraySetCapacity(cx, &copy->xml_kids, n);
4486 0                 if (!ok)
4487 0                     goto out;
4488 0                 for (k = 0; k < n; k++) {
4489 0                     kid2 = XMLARRAY_MEMBER(&vxml->xml_kids, k, JSXML);
4490 0                     XMLARRAY_SET_MEMBER(&copy->xml_kids, k, kid2);
4491                 }
4492
4493 0                 JS_ASSERT(parent != xml);
4494 0                 if (parent) {
4495 0                     q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
4496 0                     JS_ASSERT(q != XML_NOT_FOUND);
4497
4498 0                     ok = IndexToIdVal(cx, q, &id);
4499 0                     if (!ok)
4500 0                         goto out;
4501 0                     ok = Replace(cx, parent, id, OBJECT_TO_JSVAL(copyobj));
4502 0                     if (!ok)
4503 0                         goto out;
4504
4505 #ifdef DEBUG
4506                     /* Erratum: this loop in the spec is useless. */
4507                     for (j = 0, n = copy->xml_kids.length; j < n; j++) {
4508                         kid2 = XMLARRAY_MEMBER(&parent->xml_kids, q + j, JSXML);
4509                         JS_ASSERT(XMLARRAY_MEMBER(&copy->xml_kids, j, JSXML)
4510                                   == kid2);
4511                     }
4512 #endif
4513                 }
4514
4515                 /*
4516                  * 2(f)(iv-vi).
4517                  * Erratum: notice the unhandled zero-length V basis case and
4518                  * the off-by-one errors for the n != 0 cases in the spec.
4519                  */
4520 0                 if (n == 0) {
4521 0                     XMLArrayDelete(cx, &xml->xml_kids, i, JS_TRUE);
4522                 } else {
4523 0                     ok = XMLArrayInsert(cx, &xml->xml_kids, i + 1, n - 1);
4524 0                     if (!ok)
4525 0                         goto out;
4526
4527 0                     for (j = 0; j < n; j++)
4528 0                         xml->xml_kids.vector[i + j] = copy->xml_kids.vector[j];
4529                 }
4530             }
4531
4532             /* 2(g). */
4533 0             else if (vxml || JSXML_HAS_VALUE(kid)) {
4534 0                 if (parent) {
4535 0                     q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
4536 0                     JS_ASSERT(q != XML_NOT_FOUND);
4537
4538 0                     ok = IndexToIdVal(cx, q, &id);
4539 0                     if (!ok)
4540 0                         goto out;
4541 0                     ok = Replace(cx, parent, id, *vp);
4542 0                     if (!ok)
4543 0                         goto out;
4544
4545 0                     vxml = XMLARRAY_MEMBER(&parent->xml_kids, q, JSXML);
4546 0                     if (!vxml)
4547 0                         goto out;
4548 0                     roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vxml->object);
4549                 }
4550
4551                 /*
4552                  * 2(g)(iii).
4553                  * Erratum: _V_ may not be of type XML, but all index-named
4554                  * properties _x[i]_ in an XMLList _x_ must be of type XML,
4555                  * according to 9.2.1.1 Overview and other places in the spec.
4556                  *
4557                  * Thanks to 2(d), we know _V_ (*vp here) is either a string
4558                  * or an XML/XMLList object.  If *vp is a string, call ToXML
4559                  * on it to satisfy the constraint.
4560                  */
4561 0                 if (!vxml) {
4562 0                     JS_ASSERT(JSVAL_IS_STRING(*vp));
4563 0                     vobj = ToXML(cx, *vp);
4564 0                     if (!vobj)
4565 0                         goto bad;
4566 0                     roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vobj);
4567 0                     vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4568                 }
4569 0                 XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
4570             }
4571
4572             /* 2(h). */
4573             else {
4574 0                 kidobj = js_GetXMLObject(cx, kid);
4575 0                 if (!kidobj)
4576 0                     goto bad;
4577 0                 id = ATOM_KEY(cx->runtime->atomState.starAtom);
4578 0                 ok = PutProperty(cx, kidobj, id, vp);
4579 0                 if (!ok)
4580 0                     goto out;
4581             }
4582         } else {
4583             /*
4584              * 3.
4585              * Erratum: if x.[[Length]] > 1 or [[ResolveValue]] returns null
4586              * or an r with r.[[Length]] != 1, throw TypeError.
4587              */
4588 0             n = JSXML_LENGTH(xml);
4589 0             if (n > 1)
4590 0                 goto type_error;
4591 0             if (n == 0) {
4592 0                 ok = ResolveValue(cx, xml, &rxml);
4593 0                 if (!ok)
4594 0                     goto out;
4595 0                 if (!rxml || JSXML_LENGTH(rxml) != 1)
4596 0                     goto type_error;
4597 0                 ok = Append(cx, xml, rxml);
4598 0                 if (!ok)
4599 0                     goto out;
4600             }
4601 0             JS_ASSERT(JSXML_LENGTH(xml) == 1);
4602 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
4603 0             if (!kid)
4604 0                 goto out;
4605 0             kidobj = js_GetXMLObject(cx, kid);
4606 0             if (!kidobj)
4607 0                 goto bad;
4608 0             ok = PutProperty(cx, kidobj, id, vp);
4609 0             if (!ok)
4610 0                 goto out;
4611         }
4612     } else {
4613         /*
4614          * ECMA-357 9.1.1.2.
4615          * Erratum: move steps 3 and 4 to before 1 and 2, to avoid wasted
4616          * effort in ToString or [[DeepCopy]].
4617          */
4618 0         if (js_IdIsIndex(id, &index)) {
4619             /* See NOTE in spec: this variation is reserved for future use. */
4620 0             ReportBadXMLName(cx, id);
4621 0             goto bad;
4622         }
4623
4624 0         nameqn = ToXMLName(cx, id, &funid);
4625 0         if (!nameqn)
4626 0             goto bad;
4627 0         if (funid) {
4628 0             ok = js_SetProperty(cx, obj, funid, vp);
4629 0             goto out;
4630         }
4631 0         nameobj = nameqn->object;
4632
4633 0         if (JSXML_HAS_VALUE(xml))
4634 0             goto out;
4635
4636 0         if (!vxml ||
4637             vxml->xml_class == JSXML_CLASS_TEXT ||
4638             vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4639 0             ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4640 0             if (!ok)
4641 0                 goto out;
4642         } else {
4643 0             rxml = DeepCopyInLRS(cx, vxml, 0);
4644 0             if (!rxml || !js_GetXMLObject(cx, rxml))
4645 0                 goto bad;
4646 0             vxml = rxml;
4647 0             *vp = OBJECT_TO_JSVAL(vxml->object);
4648         }
4649 0         roots[VAL_ROOT] = *vp;
4650
4651         /*
4652          * 6.
4653          * Erratum: why is this done here, so early? use is way later....
4654          */
4655 0         ok = js_GetDefaultXMLNamespace(cx, &nsval);
4656 0         if (!ok)
4657 0             goto out;
4658
4659 0         if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
4660             /* 7(a). */
4661 0             if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)))
4662 0                 goto out;
4663
4664             /* 7(b-c). */
4665 0             if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4666 0                 n = vxml->xml_kids.length;
4667 0                 if (n == 0) {
4668 0                     *vp = STRING_TO_JSVAL(cx->runtime->emptyString);
4669                 } else {
4670 0                     left = KidToString(cx, vxml, 0);
4671 0                     if (!left)
4672 0                         goto bad;
4673
4674 0                     space = ATOM_TO_STRING(cx->runtime->atomState.spaceAtom);
4675 0                     for (i = 1; i < n; i++) {
4676 0                         left = js_ConcatStrings(cx, left, space);
4677 0                         if (!left)
4678 0                             goto bad;
4679 0                         right = KidToString(cx, vxml, i);
4680 0                         if (!right)
4681 0                             goto bad;
4682 0                         left = js_ConcatStrings(cx, left, right);
4683 0                         if (!left)
4684 0                             goto bad;
4685                     }
4686
4687 0                     roots[VAL_ROOT] = *vp = STRING_TO_JSVAL(left);
4688                 }
4689             } else {
4690 0                 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4691 0                 if (!ok)
4692 0                     goto out;
4693 0                 roots[VAL_ROOT] = *vp;
4694             }
4695
4696             /* 7(d-e). */
4697 0             match = NULL;
4698 0             for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
4699 0                 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
4700 0                 if (!attr)
4701 0                     continue;
4702 0                 attrqn = attr->name;
4703 0                 if (!js_CompareStrings(attrqn->localName, nameqn->localName) &&
4704                     (!nameqn->uri ||
4705                      !js_CompareStrings(attrqn->uri, nameqn->uri))) {
4706 0                     if (!match) {
4707 0                         match = attr;
4708                     } else {
4709 0                         nameobj = js_GetAttributeNameObject(cx, attrqn);
4710 0                         if (!nameobj)
4711 0                             goto bad;
4712
4713 0                         id = OBJECT_TO_JSVAL(nameobj);
4714 0                         ok = DeleteProperty(cx, obj, id, &junk);
4715 0                         if (!ok)
4716 0                             goto out;
4717 0                         --i;
4718                     }
4719                 }
4720             }
4721
4722             /* 7(f). */
4723 0             attr = match;
4724 0             if (!attr) {
4725                 /* 7(f)(i-ii). */
4726 0                 if (!nameqn->uri) {
4727 0                     left = right = cx->runtime->emptyString;
4728                 } else {
4729 0                     left = nameqn->uri;
4730 0                     right = nameqn->prefix;
4731                 }
4732 0                 nameqn = js_NewXMLQName(cx, left, right, nameqn->localName);
4733 0                 if (!nameqn)
4734 0                     goto bad;
4735
4736                 /* 7(f)(iii). */
4737 0                 attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4738 0                 if (!attr)
4739 0                     goto bad;
4740 0                 attr->parent = xml;
4741 0                 attr->name = nameqn;
4742
4743                 /* 7(f)(iv). */
4744 0                 ok = XMLARRAY_ADD_MEMBER(cx, &xml->xml_attrs, n, attr);
4745 0                 if (!ok)
4746 0                     goto out;
4747
4748                 /* 7(f)(v-vi). */
4749 0                 ns = GetNamespace(cx, nameqn, NULL);
4750 0                 if (!ns)
4751 0                     goto bad;
4752 0                 ok = AddInScopeNamespace(cx, xml, ns);
4753 0                 if (!ok)
4754 0                     goto out;
4755             }
4756
4757             /* 7(g). */
4758 0             attr->xml_value = JSVAL_TO_STRING(*vp);
4759 0             goto out;
4760         }
4761
4762         /* 8-9. */
4763 0         if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)) &&
4764             !IS_STAR(nameqn->localName)) {
4765 0             goto out;
4766         }
4767
4768         /* 10-11. */
4769 0         id = JSVAL_VOID;
4770 0         primitiveAssign = !vxml && !IS_STAR(nameqn->localName);
4771
4772         /* 12. */
4773 0         k = n = xml->xml_kids.length;
4774 0         kid2 = NULL;
4775 0         while (k != 0) {
4776 0             --k;
4777 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, k, JSXML);
4778 0             if (kid && MatchElemName(nameqn, kid)) {
4779 0                 if (!JSVAL_IS_VOID(id)) {
4780 0                     ok = DeleteByIndex(cx, xml, id, &junk);
4781 0                     if (!ok)
4782 0                         goto out;
4783                 }
4784 0                 ok = IndexToIdVal(cx, k, &id);
4785 0                 if (!ok)
4786 0                     goto out;
4787 0                 kid2 = kid;
4788             }
4789         }
4790
4791         /*
4792          * Erratum: ECMA-357 specified child insertion inconsistently:
4793          * insertChildBefore and insertChildAfter insert an arbitrary XML
4794          * instance, and therefore can create cycles, but appendChild as
4795          * specified by the "Overview" of 13.4.4.3 calls [[DeepCopy]] on
4796          * its argument.  But the "Semantics" in 13.4.4.3 do not include
4797          * any [[DeepCopy]] call.
4798          *
4799          * Fixing this (https://bugzilla.mozilla.org/show_bug.cgi?id=312692)
4800          * required adding cycle detection, and allowing duplicate kids to
4801          * be created (see comment 6 in the bug).  Allowing duplicate kid
4802          * references means the loop above will delete all but the lowest
4803          * indexed reference, and each [[DeleteByIndex]] nulls the kid's
4804          * parent.  Thus the need to restore parent here.  This is covered
4805          * by https://bugzilla.mozilla.org/show_bug.cgi?id=327564.
4806          */
4807 0         if (kid2) {
4808 0             JS_ASSERT(kid2->parent == xml || !kid2->parent);
4809 0             if (!kid2->parent)
4810 0                 kid2->parent = xml;
4811         }
4812
4813         /* 13. */
4814 0         if (JSVAL_IS_VOID(id)) {
4815             /* 13(a). */
4816 0             ok = IndexToIdVal(cx, n, &id);
4817 0             if (!ok)
4818 0                 goto out;
4819
4820             /* 13(b). */
4821 0             if (primitiveAssign) {
4822 0                 if (!nameqn->uri) {
4823 0                     ns = (JSXMLNamespace *)
4824                          JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
4825 0                     left = ns->uri;
4826 0                     right = ns->prefix;
4827                 } else {
4828 0                     left = nameqn->uri;
4829 0                     right = nameqn->prefix;
4830                 }
4831 0                 nameqn = js_NewXMLQName(cx, left, right, nameqn->localName);
4832 0                 if (!nameqn)
4833 0                     goto bad;
4834
4835                 /* 13(b)(iii). */
4836 0                 vobj = js_NewXMLObject(cx, JSXML_CLASS_ELEMENT);
4837 0                 if (!vobj)
4838 0                     goto bad;
4839 0                 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4840 0                 vxml->parent = xml;
4841 0                 vxml->name = nameqn;
4842
4843                 /* 13(b)(iv-vi). */
4844 0                 ns = GetNamespace(cx, nameqn, NULL);
4845 0                 if (!ns)
4846 0                     goto bad;
4847 0                 ok = Replace(cx, xml, id, OBJECT_TO_JSVAL(vobj));
4848 0                 if (!ok)
4849 0                     goto out;
4850 0                 ok = AddInScopeNamespace(cx, vxml, ns);
4851 0                 if (!ok)
4852 0                     goto out;
4853             }
4854         }
4855
4856         /* 14. */
4857 0         if (primitiveAssign) {
4858 0             JSXMLArrayCursor cursor;
4859
4860 0             js_IdIsIndex(id, &index);
4861 0             XMLArrayCursorInit(&cursor, &xml->xml_kids);
4862 0             cursor.index = index;
4863 0             kid = (JSXML *) XMLArrayCursorItem(&cursor);
4864 0             if (JSXML_HAS_KIDS(kid)) {
4865 0                 XMLArrayFinish(cx, &kid->xml_kids);
4866 0                 ok = XMLArrayInit(cx, &kid->xml_kids, 1);
4867             }
4868
4869             /* 14(b-c). */
4870             /* XXXbe Erratum? redundant w.r.t. 7(b-c) else clause above */
4871 0             if (ok) {
4872 0                 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4873 0                 if (ok && !IS_EMPTY(JSVAL_TO_STRING(*vp))) {
4874 0                     roots[VAL_ROOT] = *vp;
4875 0                     if ((JSXML *) XMLArrayCursorItem(&cursor) == kid)
4876 0                         ok = Replace(cx, kid, JSVAL_ZERO, *vp);
4877                 }
4878             }
4879 0             XMLArrayCursorFinish(&cursor);
4880         } else {
4881             /* 15(a). */
4882 0             ok = Replace(cx, xml, id, *vp);
4883         }
4884     }
4885
4886 out:
4887 0     JS_POP_TEMP_ROOT(cx, &tvr);
4888 0     JS_LeaveLocalRootScope(cx);
4889 0     return ok;
4890
4891 type_error:
4892 0     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4893                          JSMSG_BAD_XMLLIST_PUT,
4894                          js_ValueToPrintableString(cx, id));
4895 bad:
4896 0     ok = JS_FALSE;
4897 0     goto out;
4898 }
4899
4900 /* ECMA-357 9.1.1.10 XML [[ResolveValue]], 9.2.1.10 XMLList [[ResolveValue]]. */
4901 static JSBool
4902 ResolveValue(JSContext *cx, JSXML *list, JSXML **result)
4903 0 {
4904 0     JSXML *target, *base;
4905 0     JSXMLQName *targetprop;
4906 0     JSObject *targetpropobj;
4907 0     jsval id, tv;
4908
4909     /* Our caller must be protecting newborn objects. */
4910 0     JS_ASSERT(cx->localRootStack);
4911
4912 0     if (list->xml_class != JSXML_CLASS_LIST || list->xml_kids.length != 0) {
4913 0         if (!js_GetXMLObject(cx, list))
4914 0             return JS_FALSE;
4915 0         *result = list;
4916 0         return JS_TRUE;
4917     }
4918
4919 0     target = list->xml_target;
4920 0     targetprop = list->xml_targetprop;
4921 0     if (!target || !targetprop || IS_STAR(targetprop->localName)) {
4922 0         *result = NULL;
4923 0         return JS_TRUE;
4924     }
4925
4926 0     targetpropobj = js_GetXMLQNameObject(cx, targetprop);
4927 0     if (!targetpropobj)
4928 0         return JS_FALSE;
4929 0     if (OBJ_GET_CLASS(cx, targetpropobj) == &js_AttributeNameClass) {
4930 0         *result = NULL;
4931 0         return JS_TRUE;
4932     }
4933
4934 0     if (!ResolveValue(cx, target, &base))
4935 0         return JS_FALSE;
4936 0     if (!base) {
4937 0         *result = NULL;
4938 0         return JS_TRUE;
4939     }
4940 0     if (!js_GetXMLObject(cx, base))
4941 0         return JS_FALSE;
4942
4943 0     id = OBJECT_TO_JSVAL(targetpropobj);
4944 0     if (!GetProperty(cx, base->object, id, &tv))
4945 0         return JS_FALSE;
4946 0     target = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(tv));
4947
4948 0     if (JSXML_LENGTH(target) == 0) {
4949 0         if (base->xml_class == JSXML_CLASS_LIST && JSXML_LENGTH(base) > 1) {
4950 0             *result = NULL;
4951 0             return JS_TRUE;
4952         }
4953 0         tv = STRING_TO_JSVAL(cx->runtime->emptyString);
4954 0         if (!PutProperty(cx, base->object, id, &tv))
4955 0             return JS_FALSE;
4956 0         if (!GetProperty(cx, base->object, id, &tv))
4957 0             return JS_FALSE;
4958 0         target = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(tv));
4959     }
4960
4961 0     *result = target;
4962 0     return JS_TRUE;
4963 }
4964
4965 /*
4966  * HasProperty must be able to return a found JSProperty and the object in
4967  * which it was found, if id is of the form function::name.  For other ids,
4968  * if they index or name an XML child, we return FOUND_XML_PROPERTY in *propp
4969  * and null in *objp.
4970  *
4971  * DROP_PROPERTY helps HasProperty callers drop function properties without
4972  * trying to drop the magic FOUND_XML_PROPERTY cookie.
4973  */
4974 #define FOUND_XML_PROPERTY              ((JSProperty *) 1)
4975 #define DROP_PROPERTY(cx,pobj,prop)     (((prop) != FOUND_XML_PROPERTY)       \
4976                                          ? OBJ_DROP_PROPERTY(cx, pobj, prop)  \
4977                                          : (void) 0)
4978
4979 /* ECMA-357 9.1.1.6 XML [[HasProperty]] and 9.2.1.5 XMLList [[HasProperty]]. */
4980 static JSBool
4981 HasProperty(JSContext *cx, JSObject *obj, jsval id, JSObject **objp,
4982             JSProperty **propp)
4983 0 {
4984 0     JSXML *xml, *kid;
4985 0     JSXMLArrayCursor cursor;
4986 0     JSObject *kidobj;
4987 0     JSXMLQName *qn;
4988 0     jsid funid;
4989 0     JSXMLArray *array;
4990 0     JSXMLNameMatcher matcher;
4991 0     uint32 i, n;
4992
4993 0     *objp = NULL;
4994 0     *propp = NULL;
4995
4996 0     xml = (JSXML *) JS_GetPrivate(cx, obj);
4997 0     if (xml->xml_class == JSXML_CLASS_LIST) {
4998 0         n = JSXML_LENGTH(xml);
4999 0         if (js_IdIsIndex(id, &i)) {
5000 0             if (i < n)
5001 0                 *propp = FOUND_XML_PROPERTY;
5002 0             return JS_TRUE;
5003         }
5004
5005 0         XMLArrayCursorInit(&cursor, &xml->xml_kids);
5006 0         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5007 0             if (kid->xml_class == JSXML_CLASS_ELEMENT) {
5008 0                 kidobj = js_GetXMLObject(cx, kid);
5009 0                 if (!kidobj || !HasProperty(cx, kidobj, id, objp, propp))
5010 0                     break;
5011 0                 if (*propp)
5012 0                     break;
5013             }
5014         }
5015 0         XMLArrayCursorFinish(&cursor);
5016 0         if (kid)
5017 0             return *propp != NULL;
5018     } else {
5019 0         if (xml->xml_class == JSXML_CLASS_ELEMENT && js_IdIsIndex(id, &i)) {
5020 0             if (i == 0)
5021 0                 *propp = FOUND_XML_PROPERTY;
5022 0             return JS_TRUE;
5023         }
5024
5025 0         qn = ToXMLName(cx, id, &funid);
5026 0         if (!qn)
5027 0             return JS_FALSE;
5028 0         if (funid)
5029 0             return js_LookupProperty(cx, obj, funid, objp, propp);
5030
5031 0         if (xml->xml_class != JSXML_CLASS_ELEMENT)
5032 0             return JS_TRUE;
5033
5034 0         if (OBJ_GET_CLASS(cx, qn->object) == &js_AttributeNameClass) {
5035 0             array = &xml->xml_attrs;
5036 0             matcher = MatchAttrName;
5037         } else {
5038 0             array = &xml->xml_kids;
5039 0             matcher = MatchElemName;
5040         }
5041 0         for (i = 0, n = array->length; i < n; i++) {
5042 0             kid = XMLARRAY_MEMBER(array, i, JSXML);
5043 0             if (kid && matcher(qn, kid)) {
5044 0                 *propp = FOUND_XML_PROPERTY;
5045 0                 return JS_TRUE;
5046             }
5047         }
5048     }
5049
5050 0     return JS_TRUE;
5051 }
5052
5053 static void
5054 xml_finalize(JSContext *cx, JSObject *obj)
5055 16 {
5056 16     JSXML *xml;
5057
5058 16     xml = (JSXML *) JS_GetPrivate(cx, obj);
5059 16     if (!xml)
5060 0         return;
5061 16     if (xml->object == obj)
5062 16         xml->object = NULL;
5063     UNMETER(xml_stats.livexmlobj);
5064 }
5065
5066 static void
5067 xml_mark_vector(JSContext *cx, JSXML **vec, uint32 len, void *arg)
5068 0 {
5069 0     uint32 i;
5070 0     JSXML *elt;
5071
5072 0     for (i = 0; i < len; i++) {
5073 0         elt = vec[i];
5074         {
5075 #ifdef GC_MARK_DEBUG
5076             char buf[120];
5077
5078             if (elt->xml_class == JSXML_CLASS_LIST) {
5079                 strcpy(buf, js_XMLList_str);
5080             } else if (JSXML_HAS_NAME(elt)) {
5081                 JSXMLQName *qn = elt->name;
5082
5083                 JS_snprintf(buf, sizeof buf, "%s::%s",
5084                             qn->uri ? JS_GetStringBytes(qn->uri) : "*",
5085                             JS_GetStringBytes(qn->localName));
5086             } else {
5087                 JSString *str = elt->xml_value;
5088                 size_t srclen = JSSTRING_LENGTH(str);
5089                 size_t dstlen = sizeof buf;
5090
5091                 if (srclen >= sizeof buf / 6)
5092                     srclen = sizeof buf / 6 - 1;
5093                 js_DeflateStringToBuffer(cx, JSSTRING_CHARS(str), srclen,
5094                                          buf, &dstlen);
5095             }
5096 #else
5097 0             const char *buf = NULL;
5098 #endif
5099 0             JS_MarkGCThing(cx, elt, buf, arg);
5100         }
5101     }
5102 }
5103
5104 /*
5105  * js_XMLObjectOps.newObjectMap == js_NewObjectMap, so XML objects appear to
5106  * be native.  Therefore, xml_lookupProperty must return a valid JSProperty
5107  * pointer parameter via *propp to signify "property found".  Since the only
5108  * call to xml_lookupProperty is via OBJ_LOOKUP_PROPERTY, and then only from
5109  * js_FindXMLProperty (in this file) and js_FindProperty (in jsobj.c, called
5110  * from jsinterp.c), the only time we add a JSScopeProperty here is when an
5111  * unqualified name or XML name is being accessed.
5112  *
5113  * This scope property both speeds up subsequent js_Find*Property calls, and
5114  * keeps the JSOP_NAME code in js_Interpret happy by giving it an sprop with
5115  * (getter, setter) == (GetProperty, PutProperty).  We can't use that getter
5116  * and setter as js_XMLClass's getProperty and setProperty, because doing so
5117  * would break the XML methods, which are function-valued properties of the
5118  * XML.prototype object.
5119  *
5120  * NB: xml_deleteProperty must take care to remove any property added here.
5121  */
5122 static JSBool
5123 xml_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
5124                    JSProperty **propp)
5125 0 {
5126 0     JSScopeProperty *sprop;
5127
5128 0     if (!HasProperty(cx, obj, ID_TO_VALUE(id), objp, propp))
5129 0         return JS_FALSE;
5130
5131 0     if (*propp == FOUND_XML_PROPERTY) {
5132 0         sprop = js_AddNativeProperty(cx, obj, id, GetProperty, PutProperty,
5133                                      SPROP_INVALID_SLOT, JSPROP_ENUMERATE,
5134                                      0, 0);
5135 0         if (!sprop)
5136 0             return JS_FALSE;
5137
5138 0         JS_LOCK_OBJ(cx, obj);
5139 0         *objp = obj;
5140 0         *propp = (JSProperty *) sprop;
5141     }
5142 0     return JS_TRUE;
5143 }
5144
5145 static JSBool
5146 xml_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
5147                    JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
5148                    JSProperty **propp)
5149 672 {
5150 672     if (JSVAL_IS_FUNCTION(cx, value) || getter || setter ||
5151         (attrs & JSPROP_ENUMERATE) == 0 ||
5152         (attrs & (JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED))) {
5153 672         return js_DefineProperty(cx, obj, id, value, getter, setter, attrs,
5154                                  propp);
5155     }
5156
5157 0     if (!PutProperty(cx, obj, ID_TO_VALUE(id), &value))
5158 0         return JS_FALSE;
5159 0     if (propp)
5160 0         *propp = NULL;
5161 0     return JS_TRUE;
5162 }
5163
5164 static JSBool
5165 xml_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5166 0 {
5167 0     if (id == JS_DEFAULT_XML_NAMESPACE_ID) {
5168 0         *vp = JSVAL_VOID;
5169 0         return JS_TRUE;
5170     }
5171
5172 0     return GetProperty(cx, obj, ID_TO_VALUE(id), vp);
5173 }
5174
5175 static JSBool
5176 xml_setProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5177 0 {
5178 0     return PutProperty(cx, obj, ID_TO_VALUE(id), vp);
5179 }
5180
5181 static JSBool
5182 FoundProperty(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5183               JSBool *foundp)
5184 0 {
5185 0     JSObject *pobj;
5186
5187 0     if (prop) {
5188 0         *foundp = JS_TRUE;
5189     } else {
5190 0         if (!HasProperty(cx, obj, ID_TO_VALUE(id), &pobj, &prop))
5191 0             return JS_FALSE;
5192 0         if (prop)
5193 0             DROP_PROPERTY(cx, pobj, prop);
5194 0         *foundp = (prop != NULL);
5195     }
5196 0     return JS_TRUE;
5197 }
5198
5199 static JSBool
5200 xml_getAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5201                   uintN *attrsp)
5202 0 {
5203 0     JSBool found;
5204
5205 0     if (!FoundProperty(cx, obj, id, prop, &found))
5206 0         return JS_FALSE;
5207 0     *attrsp = found ? JSPROP_ENUMERATE : 0;
5208 0     return JS_TRUE;
5209 }
5210
5211 static JSBool
5212 xml_setAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5213                   uintN *attrsp)
5214 0 {
5215 0     JSBool found;
5216
5217 0     if (!FoundProperty(cx, obj, id, prop, &found))
5218 0         return JS_FALSE;
5219 0     if (found) {
5220 0         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
5221                              JSMSG_CANT_SET_XML_ATTRS);
5222     }
5223 0     return !found;
5224 }
5225
5226 static JSBool
5227 xml_deleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval)
5228 0 {
5229     /*
5230      * If this object has its own (mutable) scope, and if id isn't an index,
5231      * then we may have added a property to the scope in xml_lookupProperty
5232      * for it to return to mean "found" and to provide a handle for access
5233      * operations to call the property's getter or setter.  The property also
5234      * helps speed up unqualified accesses via the property cache, avoiding
5235      * what amount to two HasProperty searches.
5236      *
5237      * But now it's time to remove any such property, to purge the property
5238      * cache and remove the scope entry.
5239      */
5240 0     if (OBJ_SCOPE(obj)->object == obj && !JSID_IS_INT(id)) {
5241 0         if (!js_DeleteProperty(cx, obj, id, rval))
5242 0             return JS_FALSE;
5243     }
5244
5245 0     return DeleteProperty(cx, obj, ID_TO_VALUE(id), rval);
5246 }
5247
5248 static JSBool
5249 xml_defaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp)
5250 0 {
5251 0     JSXML *xml;
5252
5253 0     if (hint == JSTYPE_OBJECT) {
5254         /* Called from for..in code in js_Interpret: return an XMLList. */
5255 0         xml = (JSXML *) JS_GetPrivate(cx, obj);
5256 0         if (xml->xml_class != JSXML_CLASS_LIST) {
5257 0             obj = ToXMLList(cx, OBJECT_TO_JSVAL(obj));
5258 0             if (!obj)
5259 0                 return JS_FALSE;
5260         }
5261 0         *vp = OBJECT_TO_JSVAL(obj);
5262 0         return JS_TRUE;
5263     }
5264
5265 0     return JS_CallFunctionName(cx, obj, js_toString_str, 0, NULL, vp);
5266 }
5267
5268 static JSBool
5269 xml_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
5270               jsval *statep, jsid *idp)
5271 0 {
5272 0     JSXML *xml;
5273 0     uint32 length, index;
5274 0     JSXMLArrayCursor *cursor;
5275
5276 0     xml = (JSXML *) JS_GetPrivate(cx, obj);
5277 0     length = JSXML_LENGTH(xml);
5278
5279 0     switch (enum_op) {
5280       case JSENUMERATE_INIT:
5281 0         if (length == 0) {
5282 0             cursor = NULL;
5283         } else {
5284 0             cursor = (JSXMLArrayCursor *) JS_malloc(cx, sizeof *cursor);
5285 0             if (!cursor)
5286 0                 return JS_FALSE;
5287 0             XMLArrayCursorInit(cursor, &xml->xml_kids);
5288         }
5289 0         *statep = PRIVATE_TO_JSVAL(cursor);
5290 0         if (idp)
5291 0             *idp = INT_TO_JSID(length);
5292 0         break;
5293
5294       case JSENUMERATE_NEXT:
5295 0         cursor = JSVAL_TO_PRIVATE(*statep);
5296 0         if (cursor && cursor->array && (index = cursor->index) < length) {
5297 0             *idp = INT_TO_JSID(index);
5298 0             cursor->index = index + 1;
5299 0             break;
5300         }
5301         /* FALL THROUGH */
5302
5303       case JSENUMERATE_DESTROY:
5304 0         cursor = JSVAL_TO_PRIVATE(*statep);
5305 0         if (cursor) {
5306 0             XMLArrayCursorFinish(cursor);
5307 0             JS_free(cx, cursor);
5308         }
5309 0         *statep = JSVAL_NULL;
5310         break;
5311     }
5312 0     return JS_TRUE;
5313 }
5314
5315 static JSBool
5316 xml_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
5317 0 {
5318 0     return JS_TRUE;
5319 }
5320
5321 static uint32
5322 xml_mark(JSContext *cx, JSObject *obj, void *arg)
5323 16 {
5324 16     JSXML *xml;
5325
5326 16     xml = (JSXML *) JS_GetPrivate(cx, obj);
5327 16     JS_MarkGCThing(cx, xml, js_private_str, arg);
5328 16     return js_Mark(cx, obj, arg);
5329 }
5330
5331 static void
5332 xml_clear(JSContext *cx, JSObject *obj)
5333 0 {
5334 }
5335
5336 static JSBool
5337 HasSimpleContent(JSXML *xml)
5338 0 {
5339 0     JSXML *kid;
5340 0     JSBool simple;
5341 0     uint32 i, n;
5342
5343 again:
5344 0     switch (xml->xml_class) {
5345       case JSXML_CLASS_COMMENT:
5346       case JSXML_CLASS_PROCESSING_INSTRUCTION:
5347 0         return JS_FALSE;
5348       case JSXML_CLASS_LIST:
5349 0         if (xml->xml_kids.length == 0)
5350 0             return JS_TRUE;
5351 0         if (xml->xml_kids.length == 1) {
5352 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5353 0             if (kid) {
5354 0                 xml = kid;
5355 0                 goto again;
5356             }
5357         }
5358         /* FALL THROUGH */
5359       default:
5360 0         simple = JS_TRUE;
5361 0         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5362 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5363 0             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5364 0                 simple = JS_FALSE;
5365 0                 break;
5366             }
5367         }
5368 0         return simple;
5369     }
5370 }
5371
5372 /*
5373  * 11.2.2.1 Step 3(d) onward.
5374  */
5375 static JSObject *
5376 xml_getMethod(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5377 0 {
5378 0     JSXML *xml;
5379 0     JSTempValueRooter tvr;
5380 0     jsval roots[2];
5381     enum {
5382         FUN_ROOT = 0,
5383         OBJ_ROOT = 1
5384     };
5385
5386 0     JS_ASSERT(JS_InstanceOf(cx, obj, &js_XMLClass, NULL));
5387 0     xml = (JSXML *) JS_GetPrivate(cx, obj);
5388 0     memset(roots, 0, sizeof(roots));
5389 0     JS_PUSH_TEMP_ROOT(cx, sizeof roots / sizeof *roots, roots, &tvr);
5390
5391     /* From this point the control must flow through out: or bad: */
5392   retry:
5393 0     if (!GetFunction(cx, obj, xml, id, &roots[FUN_ROOT]))
5394 0         goto bad;
5395 0     if (JSVAL_IS_VOID(roots[FUN_ROOT]) && OBJECT_IS_XML(cx, obj)) {
5396 0         if (xml->xml_class == JSXML_CLASS_LIST) {
5397 0             if (xml->xml_kids.length == 1) {
5398 0                 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5399 0                 if (xml) {
5400 0                     obj = js_GetXMLObject(cx, xml);
5401 0                     if (!obj)
5402 0                         goto bad;
5403 0                     roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
5404 0                     goto retry;
5405                 }
5406             }
5407 0         } else if (HasSimpleContent(xml)) {
5408 0             JSString *str;
5409
5410 0             str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5411 0             if (!str)
5412 0                 goto bad;
5413 0             if (!js_ValueToObject(cx, STRING_TO_JSVAL(str), &obj))
5414 0                 goto bad;
5415 0             roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
5416 0             if (!js_GetProperty(cx, obj, id, &roots[FUN_ROOT]))
5417 0                 goto bad;
5418         }
5419     }
5420   out:
5421 0     *vp = roots[FUN_ROOT];
5422 0     if (obj) {
5423         /*
5424          * If we just POP tvr, then it is possible that nothing roots obj, see
5425          * bug 353165. To allow our callers to assume at least weakly rooting
5426          * of the result, we root obj via newborn array. Similarly we root the
5427          * value of roots[FUNCTION] since getMethod callers have a bad habit
5428          * of passing a pointer to unrooted local value as vp.
5429          */
5430 0         cx->newborn[GCX_OBJECT] = (JSGCThing *)obj;
5431 0         cx->lastInternalResult = roots[FUN_ROOT];
5432     }
5433 0     JS_POP_TEMP_ROOT(cx, &tvr);
5434 0     return obj;
5435   bad:
5436 0     obj = NULL;
5437 0     goto out;
5438 }
5439
5440 static JSBool
5441 xml_setMethod(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5442 0 {
5443 0     return js_SetProperty(cx, obj, id, vp);
5444 }
5445
5446 static JSBool
5447 xml_enumerateValues(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
5448                     jsval *statep, jsid *idp, jsval *vp)
5449 0 {
5450 0     JSXML *xml, *kid;
5451 0     uint32 length, index;
5452 0     JSXMLArrayCursor *cursor;
5453 0     JSObject *kidobj;
5454
5455 0     xml = (JSXML *) JS_GetPrivate(cx, obj);
5456 0     length = JSXML_LENGTH(xml);
5457 0     JS_ASSERT(INT_FITS_IN_JSVAL(length));
5458
5459 0     switch (enum_op) {
5460       case JSENUMERATE_INIT:
5461 0         if (length == 0) {
5462 0             cursor = NULL;
5463         } else {
5464 0             cursor = (JSXMLArrayCursor *) JS_malloc(cx, sizeof *cursor);
5465 0             if (!cursor)
5466 0                 return JS_FALSE;
5467 0             XMLArrayCursorInit(cursor, &xml->xml_kids);
5468         }
5469 0         *statep = PRIVATE_TO_JSVAL(cursor);
5470 0         if (idp)
5471 0             *idp = INT_TO_JSID(length);
5472 0         if (vp)
5473 0             *vp = JSVAL_VOID;
5474 0         break;
5475
5476       case JSENUMERATE_NEXT:
5477 0         cursor = JSVAL_TO_PRIVATE(*statep);
5478 0         if (cursor && cursor->array && (index = cursor->index) < length) {
5479 0             while (!(kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML))) {
5480 0                 if (++index == length)
5481 0                     goto destroy;
5482             }
5483 0             kidobj = js_GetXMLObject(cx, kid);
5484 0             if (!kidobj)
5485 0                 return JS_FALSE;
5486 0             JS_ASSERT(INT_FITS_IN_JSVAL(index));
5487 0             *idp = INT_TO_JSID(index);
5488 0             *vp = OBJECT_TO_JSVAL(kidobj);
5489 0             cursor->index = index + 1;
5490 0             break;
5491         }
5492         /* FALL THROUGH */
5493
5494       case JSENUMERATE_DESTROY:
5495 0         cursor = JSVAL_TO_PRIVATE(*statep);
5496 0         if (cursor) {
5497       destroy:
5498 0             XMLArrayCursorFinish(cursor);
5499 0             JS_free(cx, cursor);
5500         }
5501 0         *statep = JSVAL_NULL;
5502         break;
5503     }
5504 0     return JS_TRUE;
5505 }
5506
5507 static JSBool
5508 xml_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
5509 0 {
5510 0     JSXML *xml, *vxml;
5511 0     JSObject *vobj;
5512 0     JSBool ok;
5513 0     JSString *str, *vstr;
5514 0     jsdouble d, d2;
5515
5516 0     xml = (JSXML *) JS_GetPrivate(cx, obj);
5517 0     vxml = NULL;
5518 0     if (!JSVAL_IS_PRIMITIVE(v)) {
5519 0         vobj = JSVAL_TO_OBJECT(v);
5520 0         if (OBJECT_IS_XML(cx, vobj))
5521 0             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
5522     }
5523
5524 0     if (xml->xml_class == JSXML_CLASS_LIST) {
5525 0         ok = Equals(cx, xml, v, bp);
5526 0     } else if (vxml) {
5527 0         if (vxml->xml_class == JSXML_CLASS_LIST) {
5528 0             ok = Equals(cx, vxml, OBJECT_TO_JSVAL(obj), bp);
5529         } else {
5530 0             if (((xml->xml_class == JSXML_CLASS_TEXT ||
5531                   xml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
5532                  HasSimpleContent(vxml)) ||
5533                 ((vxml->xml_class == JSXML_CLASS_TEXT ||
5534                   vxml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
5535                  HasSimpleContent(xml))) {
5536 0                 ok = JS_EnterLocalRootScope(cx);
5537 0                 if (ok) {
5538 0                     str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5539 0                     vstr = js_ValueToString(cx, v);
5540 0                     ok = str && vstr;
5541 0                     if (ok)
5542 0                         *bp = !js_CompareStrings(str, vstr);
5543 0                     JS_LeaveLocalRootScope(cx);
5544                 }
5545             } else {
5546 0                 ok = XMLEquals(cx, xml, vxml, bp);
5547             }
5548         }
5549     } else {
5550 0         ok = JS_EnterLocalRootScope(cx);
5551 0         if (ok) {
5552 0             if (HasSimpleContent(xml)) {
5553 0                 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5554 0                 vstr = js_ValueToString(cx, v);
5555 0                 ok = str && vstr;
5556 0                 if (ok)
5557 0                     *bp = !js_CompareStrings(str, vstr);
5558 0             } else if (JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v)) {
5559 0                 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5560 0                 if (!str) {
5561 0                     ok = JS_FALSE;
5562 0                 } else if (JSVAL_IS_STRING(v)) {
5563 0                     *bp = !js_CompareStrings(str, JSVAL_TO_STRING(v));
5564                 } else {
5565 0                     ok = js_ValueToNumber(cx, STRING_TO_JSVAL(str), &d);
5566 0                     if (ok) {
5567 0                         d2 = JSVAL_IS_INT(v) ? JSVAL_TO_INT(v)
5568                                              : *JSVAL_TO_DOUBLE(v);
5569 0                         *bp = JSDOUBLE_COMPARE(d, ==, d2, JS_FALSE);
5570                     }
5571                 }
5572             } else {
5573 0                 *bp = JS_FALSE;
5574             }
5575 0             JS_LeaveLocalRootScope(cx);
5576         }
5577     }
5578 0     return ok;
5579 }
5580
5581 static JSBool
5582 xml_concatenate(JSContext *cx, JSObject *obj, jsval v, jsval *vp)
5583 0 {
5584 0     JSBool ok;
5585 0     JSObject *listobj, *robj;
5586 0     JSXML *list, *lxml, *rxml;
5587
5588 0     ok = JS_EnterLocalRootScope(cx);
5589 0     if (!ok)
5590 0         return JS_FALSE;
5591
5592 0     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5593 0     if (!listobj) {
5594 0         ok = JS_FALSE;
5595 0         goto out;
5596     }
5597
5598 0     list = (JSXML *) JS_GetPrivate(cx, listobj);
5599 0     lxml = (JSXML *) JS_GetPrivate(cx, obj);
5600 0     ok = Append(cx, list, lxml);
5601 0     if (!ok)
5602 0         goto out;
5603
5604 0     if (VALUE_IS_XML(cx, v)) {
5605 0         rxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5606     } else {
5607 0         robj = ToXML(cx, v);
5608 0         if (!robj) {
5609 0             ok = JS_FALSE;
5610 0             goto out;
5611         }
5612 0         rxml = (JSXML *) JS_GetPrivate(cx, robj);
5613     }
5614 0     ok = Append(cx, list, rxml);
5615 0     if (!ok)
5616 0         goto out;
5617
5618 0     *vp = OBJECT_TO_JSVAL(listobj);
5619 out:
5620 0     JS_LeaveLocalRootScope(cx);
5621 0     return ok;
5622 }
5623
5624 /* Use js_NewObjectMap so XML objects satisfy OBJ_IS_NATIVE tests. */
5625 JS_FRIEND_DATA(JSXMLObjectOps) js_XMLObjectOps = {
5626   { js_NewObjectMap,            js_DestroyObjectMap,
5627     xml_lookupProperty,         xml_defineProperty,
5628     xml_getProperty,            xml_setProperty,
5629     xml_getAttributes,          xml_setAttributes,
5630     xml_deleteProperty,         xml_defaultValue,
5631     xml_enumerate,              js_CheckAccess,
5632     NULL,                       NULL,
5633     NULL,                       NULL,
5634     NULL,                       xml_hasInstance,
5635     js_SetProtoOrParent,        js_SetProtoOrParent,
5636     xml_mark,                   xml_clear,
5637     NULL,                       NULL },
5638     xml_getMethod,              xml_setMethod,
5639     xml_enumerateValues,        xml_equality,
5640     xml_concatenate
5641 };
5642
5643 static JSObjectOps *
5644 xml_getObjectOps(JSContext *cx, JSClass *clasp)
5645 16 {
5646 16     return &js_XMLObjectOps.base;
5647 }
5648
5649 JS_FRIEND_DATA(JSClass) js_XMLClass = {
5650     js_XML_str,        JSCLASS_HAS_PRIVATE,
5651     JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,
5652     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    xml_finalize,
5653     xml_getObjectOps,  NULL,              NULL,              NULL,
5654     NULL,              NULL,              NULL,              NULL
5655 };
5656
5657 static JSObject *
5658 CallConstructorFunction(JSContext *cx, JSObject *obj, JSClass *clasp,
5659                         uintN argc, jsval *argv)
5660 0 {
5661 0     JSObject *tmp;
5662 0     jsval rval;
5663
5664 0     while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL)
5665 0         obj = tmp;
5666 0     if (!JS_CallFunctionName(cx, obj, clasp->name, argc, argv, &rval))
5667 0         return NULL;
5668 0     JS_ASSERT(!JSVAL_IS_PRIMITIVE(rval));
5669 0     return JSVAL_TO_OBJECT(rval);
5670 }
5671
5672 #define XML_METHOD_PROLOG                                                     \
5673     JS_BEGIN_MACRO                                                            \
5674         xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, argv);   \
5675         if (!xml)                                                             \
5676             return JS_FALSE;                                                  \
5677     JS_END_MACRO
5678
5679 static JSBool
5680 xml_addNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5681                  jsval *rval)
5682 0 {
5683 0     JSXML *xml;
5684 0     JSObject *nsobj;
5685 0     JSXMLNamespace *ns;
5686
5687 0     XML_METHOD_PROLOG;
5688 0     if (xml->xml_class != JSXML_CLASS_ELEMENT)
5689 0         return JS_TRUE;
5690 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5691 0     if (!xml)
5692 0         return JS_FALSE;
5693
5694 0     nsobj = CallConstructorFunction(cx, obj, &js_NamespaceClass.base, 1, argv);
5695 0     if (!nsobj)
5696 0         return JS_FALSE;
5697 0     argv[0] = OBJECT_TO_JSVAL(nsobj);
5698
5699 0     ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
5700 0     if (!AddInScopeNamespace(cx, xml, ns))
5701 0         return JS_FALSE;
5702 0     ns->declared = JS_TRUE;
5703 0     *rval = OBJECT_TO_JSVAL(obj);
5704 0     return JS_TRUE;
5705 }
5706
5707 static JSBool
5708 xml_appendChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5709                 jsval *rval)
5710 0 {
5711 0     JSXML *xml, *vxml;
5712 0     jsval name, v;
5713 0     JSObject *vobj;
5714
5715 0     XML_METHOD_PROLOG;
5716 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5717 0     if (!xml)
5718 0         return JS_FALSE;
5719
5720 0     if (!js_GetAnyName(cx, &name))
5721 0         return JS_FALSE;
5722
5723 0     if (!GetProperty(cx, obj, name, &v))
5724 0         return JS_FALSE;
5725
5726 0     JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5727 0     vobj = JSVAL_TO_OBJECT(v);
5728 0     JS_ASSERT(OBJECT_IS_XML(cx, vobj));
5729 0     vxml = (JSXML *) JS_GetPrivate(cx, vobj);
5730 0     JS_ASSERT(vxml->xml_class == JSXML_CLASS_LIST);
5731
5732 0     if (!IndexToIdVal(cx, vxml->xml_kids.length, &name))
5733 0         return JS_FALSE;
5734 0     if (!PutProperty(cx, JSVAL_TO_OBJECT(v), name, &argv[0]))
5735 0         return JS_FALSE;
5736
5737 0     *rval = OBJECT_TO_JSVAL(obj);
5738 0     return JS_TRUE;
5739 }
5740
5741 /* XML and XMLList */
5742 static JSBool
5743 xml_attribute(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5744               jsval *rval)
5745 0 {
5746 0     JSXMLQName *qn;
5747
5748 0     qn = ToAttributeName(cx, argv[0]);
5749 0     if (!qn)
5750 0         return JS_FALSE;
5751 0     argv[0] = OBJECT_TO_JSVAL(qn->object);      /* local root */
5752 0     return GetProperty(cx, obj, argv[0], rval);
5753 }
5754
5755 /* XML and XMLList */
5756 static JSBool
5757 xml_attributes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5758                jsval *rval)
5759 0 {
5760 0     jsval name;
5761 0     JSXMLQName *qn;
5762 0     JSTempValueRooter tvr;
5763 0     JSBool ok;
5764
5765 0     name = ATOM_KEY(cx->runtime->atomState.starAtom);
5766 0     qn = ToAttributeName(cx, name);
5767 0     if (!qn)
5768 0         return JS_FALSE;
5769 0     name = OBJECT_TO_JSVAL(qn->object);
5770 0     JS_PUSH_SINGLE_TEMP_ROOT(cx, name, &tvr);
5771 0     ok = GetProperty(cx, obj, name, rval);
5772 0     JS_POP_TEMP_ROOT(cx, &tvr);
5773 0     return ok;
5774 }
5775
5776 /* XML and XMLList */
5777 static JSBool
5778 xml_child_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval name,
5779                  jsval *rval)
5780 0 {
5781 0     uint32 index;
5782 0     JSXML *kid;
5783 0     JSObject *kidobj;
5784
5785     /* ECMA-357 13.4.4.6 */
5786 0     JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
5787
5788 0     if (js_IdIsIndex(name, &index)) {
5789 0         if (index >= JSXML_LENGTH(xml)) {
5790 0             *rval = JSVAL_VOID;
5791         } else {
5792 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
5793 0             if (!kid) {
5794 0                 *rval = JSVAL_VOID;
5795             } else {
5796 0                 kidobj = js_GetXMLObject(cx, kid);
5797 0                 if (!kidobj)
5798 0                     return JS_FALSE;
5799 0                 *rval = OBJECT_TO_JSVAL(kidobj);
5800             }
5801         }
5802 0         return JS_TRUE;
5803     }
5804
5805 0     return GetProperty(cx, obj, name, rval);
5806 }
5807
5808 static JSBool
5809 xml_child(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
5810 0 {
5811 0     JSXML *xml, *list, *kid, *vxml;
5812 0     JSXMLArrayCursor cursor;
5813 0     jsval name, v;
5814 0     JSObject *listobj, *kidobj;
5815
5816 0     XML_METHOD_PROLOG;
5817 0     name = argv[0];
5818 0     if (xml->xml_class == JSXML_CLASS_LIST) {
5819         /* ECMA-357 13.5.4.4 */
5820 0         listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5821 0         if (!listobj)
5822 0             return JS_FALSE;
5823
5824 0         *rval = OBJECT_TO_JSVAL(listobj);
5825 0         list = (JSXML *) JS_GetPrivate(cx, listobj);
5826 0         list->xml_target = xml;
5827
5828 0         XMLArrayCursorInit(&cursor, &xml->xml_kids);
5829 0         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5830 0             kidobj = js_GetXMLObject(cx, kid);
5831 0             if (!kidobj)
5832 0                 break;
5833 0             if (!xml_child_helper(cx, kidobj, kid, name, &v))
5834 0                 break;
5835 0             if (JSVAL_IS_VOID(v)) {
5836                 /* The property didn't exist in this kid. */
5837 0                 continue;
5838             }
5839
5840 0             JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5841 0             vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5842 0             if ((!JSXML_HAS_KIDS(vxml) || vxml->xml_kids.length != 0) &&
5843                 !Append(cx, list, vxml)) {
5844 0                 break;
5845             }
5846         }
5847 0         XMLArrayCursorFinish(&cursor);
5848 0         return !kid;
5849     }
5850
5851 0     return xml_child_helper(cx, obj, xml, name, rval);
5852 }
5853
5854 static JSBool
5855 xml_childIndex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5856                jsval *rval)
5857 0 {
5858 0     JSXML *xml, *parent;
5859 0     uint32 i, n;
5860
5861 0     XML_METHOD_PROLOG;
5862 0     parent = xml->parent;
5863 0     if (!parent || xml->xml_class == JSXML_CLASS_ATTRIBUTE) {
5864 0         *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN);
5865 0         return JS_TRUE;
5866     }
5867 0     for (i = 0, n = JSXML_LENGTH(parent); i < n; i++) {
5868 0         if (XMLARRAY_MEMBER(&parent->xml_kids, i, JSXML) == xml)
5869 0             break;
5870     }
5871 0     JS_ASSERT(i < n);
5872 0     return js_NewNumberValue(cx, i, rval);
5873 }
5874
5875 /* XML and XMLList */
5876 static JSBool
5877 xml_children(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5878              jsval *rval)
5879 0 {
5880 0     jsval name;
5881
5882 0     name = ATOM_KEY(cx->runtime->atomState.starAtom);
5883 0     return GetProperty(cx, obj, name, rval);
5884 }
5885
5886 /* XML and XMLList */
5887 static JSBool
5888 xml_comments(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5889              jsval *rval)
5890 0 {
5891 0     JSXML *xml, *list, *kid, *vxml;
5892 0     JSObject *listobj, *kidobj;
5893 0     JSBool ok;
5894 0     uint32 i, n;
5895 0     jsval v;
5896
5897 0     XML_METHOD_PROLOG;
5898 0     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5899 0     if (!listobj)
5900 0         return JS_FALSE;
5901
5902 0     *rval = OBJECT_TO_JSVAL(listobj);
5903 0     list = (JSXML *) JS_GetPrivate(cx, listobj);
5904 0     list->xml_target = xml;
5905
5906 0     ok = JS_TRUE;
5907
5908 0     if (xml->xml_class == JSXML_CLASS_LIST) {
5909         /* 13.5.4.6 Step 2. */
5910 0         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5911 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5912 0             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5913 0                 ok = JS_EnterLocalRootScope(cx);
5914 0                 if (!ok)
5915 0                     break;
5916 0                 kidobj = js_GetXMLObject(cx, kid);
5917 0                 ok = kidobj
5918                      ? xml_comments(cx, kidobj, argc, argv, &v)
5919                      : JS_FALSE;
5920 0                 JS_LeaveLocalRootScope(cx);
5921 0                 if (!ok)
5922 0                     break;
5923 0                 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5924 0                 if (JSXML_LENGTH(vxml) != 0) {
5925 0                     ok = Append(cx, list, vxml);
5926 0                     if (!ok)
5927 0                         break;
5928                 }
5929             }
5930         }
5931     } else {
5932         /* 13.4.4.9 Step 2. */
5933 0         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5934 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5935 0             if (kid && kid->xml_class == JSXML_CLASS_COMMENT) {
5936 0                 ok = Append(cx, list, kid);
5937 0                 if (!ok)
5938 0                     break;
5939             }
5940         }
5941     }
5942
5943 0     return ok;
5944 }
5945
5946 /* XML and XMLList */
5947 static JSBool
5948 xml_contains(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5949              jsval *rval)
5950 0 {
5951 0     JSXML *xml, *kid;
5952 0     jsval value;
5953 0     JSBool eq;
5954 0     JSXMLArrayCursor cursor;
5955 0     JSObject *kidobj;
5956
5957 0     XML_METHOD_PROLOG;
5958 0     value = argv[0];
5959 0     if (xml->xml_class == JSXML_CLASS_LIST) {
5960 0         eq = JS_FALSE;
5961 0         XMLArrayCursorInit(&cursor, &xml->xml_kids);
5962 0         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5963 0             kidobj = js_GetXMLObject(cx, kid);
5964 0             if (!kidobj || !xml_equality(cx, kidobj, value, &eq))
5965 0                 break;
5966 0             if (eq)
5967 0                 break;
5968         }
5969 0         XMLArrayCursorFinish(&cursor);
5970 0         if (kid)
5971 0             return JS_FALSE;
5972     } else {
5973 0         if (!xml_equality(cx, obj, value, &eq))
5974 0             return JS_FALSE;
5975     }
5976 0     *rval = BOOLEAN_TO_JSVAL(eq);
5977 0     return JS_TRUE;
5978 }
5979
5980 /* XML and XMLList */
5981 static JSBool
5982 xml_copy(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
5983 0 {
5984 0     JSXML *xml, *copy;
5985
5986 0     XML_METHOD_PROLOG;
5987 0     copy = DeepCopy(cx, xml, NULL, 0);
5988 0     if (!copy)
5989 0         return JS_FALSE;
5990 0     *rval = OBJECT_TO_JSVAL(copy->object);
5991 0     return JS_TRUE;
5992 }
5993
5994 /* XML and XMLList */
5995 static JSBool
5996 xml_descendants(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5997                 jsval *rval)
5998 0 {
5999 0     JSXML *xml, *list;
6000 0     jsval name;
6001
6002 0     XML_METHOD_PROLOG;
6003 0     name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6004 0     list = Descendants(cx, xml, name);
6005 0     if (!list)
6006 0         return JS_FALSE;
6007 0     *rval = OBJECT_TO_JSVAL(list->object);
6008 0     return JS_TRUE;
6009 }
6010
6011 /* XML and XMLList */
6012 static JSBool
6013 xml_elements(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6014              jsval *rval)
6015 0 {
6016 0     JSXML *xml, *list, *kid, *vxml;
6017 0     jsval name, v;
6018 0     JSXMLQName *nameqn;
6019 0     jsid funid;
6020 0     JSObject *listobj, *kidobj;
6021 0     JSBool ok;
6022 0     JSXMLArrayCursor cursor;
6023 0     uint32 i, n;
6024
6025 0     XML_METHOD_PROLOG;
6026 0     name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6027 0     nameqn = ToXMLName(cx, name, &funid);
6028 0     if (!nameqn)
6029 0         return JS_FALSE;
6030 0     argv[0] = OBJECT_TO_JSVAL(nameqn->object);
6031
6032 0     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
6033 0     if (!listobj)
6034 0         return JS_FALSE;
6035 0     *rval = OBJECT_TO_JSVAL(listobj);
6036 0     if (funid)
6037 0         return JS_TRUE;
6038
6039 0     list = (JSXML *) JS_GetPrivate(cx, listobj);
6040 0     list->xml_target = xml;
6041 0     list->xml_targetprop = nameqn;
6042 0     ok = JS_TRUE;
6043
6044 0     if (xml->xml_class == JSXML_CLASS_LIST) {
6045         /* 13.5.4.6 */
6046 0         XMLArrayCursorInit(&cursor, &xml->xml_kids);
6047 0         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
6048 0             if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6049 0                 ok = JS_EnterLocalRootScope(cx);
6050 0                 if (!ok)
6051 0                     break;
6052 0                 kidobj = js_GetXMLObject(cx, kid);
6053 0                 ok = kidobj
6054                      ? xml_elements(cx, kidobj, argc, argv, &v)
6055                      : JS_FALSE;
6056 0                 JS_LeaveLocalRootScope(cx);
6057 0                 if (!ok)
6058 0                     break;
6059 0                 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
6060 0                 if (JSXML_LENGTH(vxml) != 0) {
6061 0                     ok = Append(cx, list, vxml);
6062 0                     if (!ok)
6063 0                         break;
6064                 }
6065             }
6066         }
6067 0         XMLArrayCursorFinish(&cursor);
6068     } else {
6069 0         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
6070 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6071 0             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT &&
6072                 MatchElemName(nameqn, kid)) {
6073 0                 ok = Append(cx, list, kid);
6074 0                 if (!ok)
6075 0                     break;
6076             }
6077         }
6078     }
6079
6080 0     return ok;
6081 }
6082
6083 /* XML and XMLList */
6084 static JSBool
6085 xml_hasOwnProperty(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6086                    jsval *rval)
6087 0 {
6088 0     jsval name;
6089 0     JSObject *pobj;
6090 0     JSProperty *prop;
6091
6092 0     if (!JS_InstanceOf(cx, obj, &js_XMLClass, argv))
6093 0         return JS_FALSE;
6094
6095 0     name = argv[0];
6096 0     if (!HasProperty(cx, obj, name, &pobj, &prop))
6097 0         return JS_FALSE;
6098 0     if (!prop) {
6099 0         return js_HasOwnPropertyHelper(cx, obj, js_LookupProperty, argc, argv,
6100                                        rval);
6101     }
6102 0     DROP_PROPERTY(cx, pobj, prop);
6103 0     *rval = JSVAL_TRUE;
6104 0     return JS_TRUE;
6105 }
6106
6107 /* XML and XMLList */
6108 static JSBool
6109 xml_hasComplexContent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6110                       jsval *rval)
6111 0 {
6112 0     JSXML *xml, *kid;
6113 0     JSObject *kidobj;
6114 0     uint32 i, n;
6115
6116 0     XML_METHOD_PROLOG;
6117 again:
6118 0     switch (xml->xml_class) {
6119       case JSXML_CLASS_ATTRIBUTE:
6120       case JSXML_CLASS_COMMENT:
6121       case JSXML_CLASS_PROCESSING_INSTRUCTION:
6122       case JSXML_CLASS_TEXT:
6123 0         *rval = JSVAL_FALSE;
6124 0         break;
6125       case JSXML_CLASS_LIST:
6126 0         if (xml->xml_kids.length == 0) {
6127 0             *rval = JSVAL_TRUE;
6128 0         } else if (xml->xml_kids.length == 1) {
6129 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
6130 0             if (kid) {
6131 0                 kidobj = js_GetXMLObject(cx, kid);
6132 0                 if (!kidobj)
6133 0                     return JS_FALSE;
6134 0                 obj = kidobj;
6135 0                 xml = (JSXML *) JS_GetPrivate(cx, obj);
6136 0                 goto again;
6137             }
6138         }
6139         /* FALL THROUGH */
6140       default:
6141 0         *rval = JSVAL_FALSE;
6142 0         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6143 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6144 0             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
6145 0                 *rval = JSVAL_TRUE;
6146 0                 break;
6147             }
6148         }
6149 0         break;
6150     }
6151 0     return JS_TRUE;
6152 }
6153
6154 /* XML and XMLList */
6155 static JSBool
6156 xml_hasSimpleContent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6157                      jsval *rval)
6158 0 {
6159 0     JSXML *xml;
6160
6161 0     XML_METHOD_PROLOG;
6162 0     *rval = BOOLEAN_TO_JSVAL(HasSimpleContent(xml));
6163 0     return JS_TRUE;
6164 }
6165
6166 typedef struct JSTempRootedNSArray {
6167     JSTempValueRooter   tvr;
6168     JSXMLArray          array;
6169     jsval               value;  /* extra root for temporaries */
6170 } JSTempRootedNSArray;
6171
6172 JS_STATIC_DLL_CALLBACK(void)
6173 mark_temp_ns_array(JSContext *cx, JSTempValueRooter *tvr)
6174 0 {
6175 0     JSTempRootedNSArray *tmp = (JSTempRootedNSArray *)tvr;
6176
6177 0     namespace_mark_vector(cx,
6178                           (JSXMLNamespace **)tmp->array.vector,
6179                           tmp->array.length, NULL);
6180 0     XMLArrayCursorMark(cx, tmp->array.cursors);
6181 0     if (JSVAL_IS_GCTHING(tmp->value))
6182 0         GC_MARK(cx, JSVAL_TO_GCTHING(tmp->value), "temp_ns_array_value", NULL);
6183 }
6184
6185 static void
6186 InitTempNSArray(JSContext *cx, JSTempRootedNSArray *tmp)
6187 0 {
6188 0     XMLArrayInit(cx, &tmp->array, 0);
6189 0     tmp->value = JSVAL_NULL;
6190 0     JS_PUSH_TEMP_ROOT_MARKER(cx, mark_temp_ns_array, &tmp->tvr);
6191 }
6192
6193 static void
6194 FinishTempNSArray(JSContext *cx, JSTempRootedNSArray *tmp)
6195 0 {
6196 0     JS_ASSERT(tmp->tvr.u.marker == mark_temp_ns_array);
6197 0     JS_POP_TEMP_ROOT(cx, &tmp->tvr);
6198 0     XMLArrayFinish(cx, &tmp->array);
6199 }
6200
6201 /*
6202  * Populate a new JS array with elements of JSTempRootedNSArray.array and
6203  * place the result into rval.  rval must point to a rooted location.
6204  */
6205 static JSBool
6206 TempNSArrayToJSArray(JSContext *cx, JSTempRootedNSArray *tmp, jsval *rval)
6207 0 {
6208 0     JSObject *arrayobj;
6209 0     uint32 i, n;
6210 0     JSXMLNamespace *ns;
6211 0     JSObject *nsobj;
6212
6213 0     arrayobj = js_NewArrayObject(cx, 0, NULL);
6214 0     if (!arrayobj)
6215 0         return JS_FALSE;
6216 0     *rval = OBJECT_TO_JSVAL(arrayobj);
6217 0     for (i = 0, n = tmp->array.length; i < n; i++) {
6218 0         ns = XMLARRAY_MEMBER(&tmp->array, i, JSXMLNamespace);
6219 0         if (!ns)
6220 0             continue;
6221 0         nsobj = js_GetXMLNamespaceObject(cx, ns);
6222 0         if (!nsobj)
6223 0             return JS_FALSE;
6224 0         tmp->value = OBJECT_TO_JSVAL(nsobj);
6225 0         if (!OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(i), &tmp->value))
6226 0             return JS_FALSE;
6227     }
6228 0     return JS_TRUE;
6229 }
6230
6231 static JSBool
6232 FindInScopeNamespaces(JSContext *cx, JSXML *xml, JSXMLArray *nsarray)
6233 0 {
6234 0     uint32 length, i, j, n;
6235 0     JSXMLNamespace *ns, *ns2;
6236
6237 0     length = nsarray->length;
6238 0     do {
6239 0         if (xml->xml_class != JSXML_CLASS_ELEMENT)
6240 0             continue;
6241 0         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
6242 0             ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
6243 0             if (!ns)
6244 0                 continue;
6245
6246 0             for (j = 0; j < length; j++) {
6247 0                 ns2 = XMLARRAY_MEMBER(nsarray, j, JSXMLNamespace);
6248 0                 if (ns2 &&
6249                     ((ns2->prefix && ns->prefix)
6250                      ? !js_CompareStrings(ns2->prefix, ns->prefix)
6251                      : !js_CompareStrings(ns2->uri, ns->uri))) {
6252 0                     break;
6253                 }
6254             }
6255
6256 0             if (j == length) {
6257 0                 if (!XMLARRAY_APPEND(cx, nsarray, ns))
6258 0                     return JS_FALSE;
6259 0                 ++length;
6260             }
6261         }
6262 0     } while ((xml = xml->parent) != NULL);
6263 0     JS_ASSERT(length == nsarray->length);
6264
6265 0     return JS_TRUE;
6266 }
6267
6268 static JSBool
6269 xml_inScopeNamespaces(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6270                       jsval *rval)
6271 0 {
6272 0     JSXML *xml;
6273 0     JSTempRootedNSArray namespaces;
6274 0     JSBool ok;
6275
6276 0     XML_METHOD_PROLOG;
6277
6278 0     InitTempNSArray(cx, &namespaces);
6279 0     ok = FindInScopeNamespaces(cx, xml, &namespaces.array) &&
6280          TempNSArrayToJSArray(cx, &namespaces, rval);
6281 0     FinishTempNSArray(cx, &namespaces);
6282 0     return ok;
6283 }
6284
6285 static JSBool
6286 xml_insertChildAfter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6287                      jsval *rval)
6288 0 {
6289 0     JSXML *xml, *kid;
6290 0     jsval arg;
6291 0     uint32 i;
6292
6293 0     XML_METHOD_PROLOG;
6294 0     if (!JSXML_HAS_KIDS(xml))
6295 0         return JS_TRUE;
6296
6297 0     arg = argv[0];
6298 0     if (JSVAL_IS_NULL(arg)) {
6299 0         kid = NULL;
6300 0         i = 0;
6301     } else {
6302 0         if (!VALUE_IS_XML(cx, arg))
6303 0             return JS_TRUE;
6304 0         kid = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(arg));
6305 0         i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
6306 0         if (i == XML_NOT_FOUND)
6307 0             return JS_TRUE;
6308 0         ++i;
6309     }
6310
6311 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6312 0     if (!xml)
6313 0         return JS_FALSE;
6314 0     if (!Insert(cx, xml, i, argv[1]))
6315 0         return JS_FALSE;
6316 0     *rval = OBJECT_TO_JSVAL(obj);
6317 0     return JS_TRUE;
6318 }
6319
6320 static JSBool
6321 xml_insertChildBefore(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6322                       jsval *rval)
6323 0 {
6324 0     JSXML *xml, *kid;
6325 0     jsval arg;
6326 0     uint32 i;
6327
6328 0     XML_METHOD_PROLOG;
6329 0     if (!JSXML_HAS_KIDS(xml))
6330 0         return JS_TRUE;
6331
6332 0     arg = argv[0];
6333 0     if (JSVAL_IS_NULL(arg)) {
6334 0         kid = NULL;
6335 0         i = xml->xml_kids.length;
6336     } else {
6337 0         if (!VALUE_IS_XML(cx, arg))
6338 0             return JS_TRUE;
6339 0         kid = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(arg));
6340 0         i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
6341 0         if (i == XML_NOT_FOUND)
6342 0             return JS_TRUE;
6343     }
6344
6345 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6346 0     if (!xml)
6347 0         return JS_FALSE;
6348 0     if (!Insert(cx, xml, i, argv[1]))
6349 0         return JS_FALSE;
6350 0     *rval = OBJECT_TO_JSVAL(obj);
6351 0     return JS_TRUE;
6352 }
6353
6354 /* XML and XMLList */
6355 static JSBool
6356 xml_length(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6357 0 {
6358 0     JSXML *xml;
6359
6360 0     XML_METHOD_PROLOG;
6361 0     if (xml->xml_class != JSXML_CLASS_LIST) {
6362 0         *rval = JSVAL_ONE;
6363     } else {
6364 0         if (!js_NewNumberValue(cx, xml->xml_kids.length, rval))
6365 0             return JS_FALSE;
6366     }
6367 0     return JS_TRUE;
6368 }
6369
6370 static JSBool
6371 xml_localName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6372               jsval *rval)
6373 0 {
6374 0     JSXML *xml;
6375
6376 0     XML_METHOD_PROLOG;
6377 0     *rval = xml->name ? STRING_TO_JSVAL(xml->name->localName) : JSVAL_NULL;
6378 0     return JS_TRUE;
6379 }
6380
6381 static JSBool
6382 xml_name(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6383 0 {
6384 0     JSXML *xml;
6385 0     JSObject *nameobj;
6386
6387 0     XML_METHOD_PROLOG;
6388 0     if (!xml->name) {
6389 0         *rval = JSVAL_NULL;
6390     } else {
6391 0         nameobj = js_GetXMLQNameObject(cx, xml->name);
6392 0         if (!nameobj)
6393 0             return JS_FALSE;
6394 0         *rval = OBJECT_TO_JSVAL(nameobj);
6395     }
6396 0     return JS_TRUE;
6397 }
6398
6399 static JSBool
6400 xml_namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6401               jsval *rval)
6402 0 {
6403 0     JSXML *xml;
6404 0     JSString *prefix;
6405 0     JSTempRootedNSArray inScopeNSes;
6406 0     JSBool ok;
6407 0     jsuint i, length;
6408 0     JSXMLNamespace *ns;
6409 0     JSObject *nsobj;
6410
6411 0     XML_METHOD_PROLOG;
6412 0     if (argc == 0 &&
6413         (xml->xml_class == JSXML_CLASS_TEXT ||
6414          xml->xml_class == JSXML_CLASS_COMMENT ||
6415          xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)) {
6416 0         *rval = JSVAL_NULL;
6417 0         return JS_TRUE;
6418     }
6419
6420 0     if (argc == 0) {
6421 0         prefix = NULL;
6422     } else {
6423 0         prefix = js_ValueToString(cx, argv[0]);
6424 0         if (!prefix)
6425 0             return JS_FALSE;
6426 0         argv[0] = STRING_TO_JSVAL(prefix);      /* local root */
6427     }
6428
6429     /* After this point the control must flow through label out. */
6430 0     InitTempNSArray(cx, &inScopeNSes);
6431 0     ok = FindInScopeNamespaces(cx, xml, &inScopeNSes.array);
6432 0     if (!ok)
6433 0         goto out;
6434
6435 0     if (!prefix) {
6436 0         ns = GetNamespace(cx, xml->name, &inScopeNSes.array);
6437 0         if (!ns) {
6438 0             ok = JS_FALSE;
6439 0             goto out;
6440         }
6441     } else {
6442 0         ns = NULL;
6443 0         for (i = 0, length = inScopeNSes.array.length; i < length; i++) {
6444 0             ns = XMLARRAY_MEMBER(&inScopeNSes.array, i, JSXMLNamespace);
6445 0             if (ns && ns->prefix && !js_CompareStrings(ns->prefix, prefix))
6446 0                 break;
6447 0             ns = NULL;
6448         }
6449     }
6450
6451 0     if (!ns) {
6452 0         *rval = JSVAL_VOID;
6453     } else {
6454 0         nsobj = js_GetXMLNamespaceObject(cx, ns);
6455 0         if (!nsobj) {
6456 0             ok = JS_FALSE;
6457 0             goto out;
6458         }
6459 0         *rval = OBJECT_TO_JSVAL(nsobj);
6460     }
6461
6462   out:
6463 0     FinishTempNSArray(cx, &inScopeNSes);
6464 0     return JS_TRUE;
6465 }
6466
6467 static JSBool
6468 xml_namespaceDeclarations(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6469                           jsval *rval)
6470 0 {
6471 0     JSXML *xml, *yml;
6472 0     JSBool ok;
6473 0     JSTempRootedNSArray ancestors, declared;
6474 0     uint32 i, n;
6475 0     JSXMLNamespace *ns;
6476
6477 0     XML_METHOD_PROLOG;
6478 0     if (JSXML_HAS_VALUE(xml) || xml->xml_class == JSXML_CLASS_LIST)
6479 0         return JS_TRUE;
6480
6481     /* From here, control flow must goto out to finish these arrays. */
6482 0     ok = JS_TRUE;
6483 0     InitTempNSArray(cx, &ancestors);
6484 0     InitTempNSArray(cx, &declared);
6485 0     yml = xml;
6486
6487 0     while ((yml = yml->parent) != NULL) {
6488 0         JS_ASSERT(yml->xml_class == JSXML_CLASS_ELEMENT);
6489 0         for (i = 0, n = yml->xml_namespaces.length; i < n; i++) {
6490 0             ns = XMLARRAY_MEMBER(&yml->xml_namespaces, i, JSXMLNamespace);
6491 0             if (ns &&
6492                 !XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
6493 0                 ok = XMLARRAY_APPEND(cx, &ancestors.array, ns);
6494 0                 if (!ok)
6495 0                     goto out;
6496             }
6497         }
6498     }
6499
6500 0     for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
6501 0         ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
6502 0         if (!ns)
6503 0             continue;
6504 0         if (!ns->declared)
6505 0             continue;
6506 0         if (!XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
6507 0             ok = XMLARRAY_APPEND(cx, &declared.array, ns);
6508 0             if (!ok)
6509 0                 goto out;
6510         }
6511     }
6512
6513 0     ok = TempNSArrayToJSArray(cx, &declared, rval);
6514
6515 out:
6516     /* Finishing must be in reverse order of initialization to follow LIFO. */
6517 0     FinishTempNSArray(cx, &declared);
6518 0     FinishTempNSArray(cx, &ancestors);
6519 0     return ok;
6520 }
6521
6522 static const char js_attribute_str[] = "attribute";
6523 static const char js_text_str[]      = "text";
6524
6525 /* Exported to jsgc.c #ifdef GC_MARK_DEBUG. */
6526 const char *js_xml_class_str[] = {
6527     "list",
6528     "element",
6529     js_attribute_str,
6530     "processing-instruction",
6531     js_text_str,
6532     "comment"
6533 };
6534
6535 static JSBool
6536 xml_nodeKind(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6537              jsval *rval)
6538 0 {
6539 0     JSXML *xml;
6540 0     JSString *str;
6541
6542 0     XML_METHOD_PROLOG;
6543 0     str = JS_InternString(cx, js_xml_class_str[xml->xml_class]);
6544 0     if (!str)
6545 0         return JS_FALSE;
6546 0     *rval = STRING_TO_JSVAL(str);
6547 0     return JS_TRUE;
6548 }
6549
6550 static JSBool
6551 NormalizingDelete(JSContext *cx, JSObject *obj, JSXML *xml, jsval id)
6552 0 {
6553 0     jsval junk;
6554
6555 0     if (xml->xml_class == JSXML_CLASS_LIST)
6556 0         return DeleteProperty(cx, obj, id, &junk);
6557 0     return DeleteByIndex(cx, xml, id, &junk);
6558 }
6559
6560 /*
6561  * Erratum? the testcase js/tests/e4x/XML/13.4.4.26.js wants all-whitespace
6562  * text between tags to be removed by normalize.
6563  */
6564 static JSBool
6565 IsXMLSpace(JSString *str)
6566 0 {
6567 0     const jschar *cp, *end;
6568
6569 0     cp = JSSTRING_CHARS(str);
6570 0     end = cp + JSSTRING_LENGTH(str);
6571 0     while (cp < end) {
6572 0         if (!JS_ISXMLSPACE(*cp))
6573 0             return JS_FALSE;
6574 0         ++cp;
6575     }
6576 0     return JS_TRUE;
6577 }
6578
6579 /* XML and XMLList */
6580 static JSBool
6581 xml_normalize(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6582               jsval *rval)
6583 0 {
6584 0     JSXML *xml, *kid, *kid2;
6585 0     uint32 i, n;
6586 0     JSObject *kidobj;
6587 0     JSString *str;
6588 0     jsval junk;
6589
6590 0     XML_METHOD_PROLOG;
6591 0     *rval = OBJECT_TO_JSVAL(obj);
6592 0     if (!JSXML_HAS_KIDS(xml))
6593 0         return JS_TRUE;
6594
6595 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6596 0     if (!xml)
6597 0         return JS_FALSE;
6598
6599 0     for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6600 0         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6601 0         if (!kid)
6602 0             continue;
6603 0         if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6604 0             kidobj = js_GetXMLObject(cx, kid);
6605 0             if (!kidobj || !xml_normalize(cx, kidobj, argc, argv, &junk))
6606 0                 return JS_FALSE;
6607 0         } else if (kid->xml_class == JSXML_CLASS_TEXT) {
6608 0             while (i + 1 < n &&
6609                    (kid2 = XMLARRAY_MEMBER(&xml->xml_kids, i + 1, JSXML)) &&
6610                    kid2->xml_class == JSXML_CLASS_TEXT) {
6611 0                 str = js_ConcatStrings(cx, kid->xml_value, kid2->xml_value);
6612 0                 if (!str)
6613 0                     return JS_FALSE;
6614 0                 if (!NormalizingDelete(cx, obj, xml, INT_TO_JSVAL(i + 1)))
6615 0                     return JS_FALSE;
6616 0                 n = xml->xml_kids.length;
6617 0                 kid->xml_value = str;
6618             }
6619 0             if (IS_EMPTY(kid->xml_value) || IsXMLSpace(kid->xml_value)) {
6620 0                 if (!NormalizingDelete(cx, obj, xml, INT_TO_JSVAL(i)))
6621 0                     return JS_FALSE;
6622 0                 n = xml->xml_kids.length;
6623 0                 --i;
6624             }
6625         }
6626     }
6627
6628 0     return JS_TRUE;
6629 }
6630
6631 /* XML and XMLList */
6632 static JSBool
6633 xml_parent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6634 0 {
6635 0     JSXML *xml, *parent, *kid;
6636 0     uint32 i, n;
6637 0     JSObject *parentobj;
6638
6639 0     XML_METHOD_PROLOG;
6640 0     parent = xml->parent;
6641 0     if (xml->xml_class == JSXML_CLASS_LIST) {
6642 0         *rval = JSVAL_VOID;
6643 0         n = xml->xml_kids.length;
6644 0         if (n == 0)
6645 0             return JS_TRUE;
6646
6647 0         kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
6648 0         if (!kid)
6649 0             return JS_TRUE;
6650 0         parent = kid->parent;
6651 0         for (i = 1; i < n; i++) {
6652 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6653 0             if (kid && kid->parent != parent)
6654 0                 return JS_TRUE;
6655         }
6656     }
6657
6658 0     if (!parent) {
6659 0         *rval = JSVAL_NULL;
6660 0         return JS_TRUE;
6661     }
6662
6663 0     parentobj = js_GetXMLObject(cx, parent);
6664 0     if (!parentobj)
6665 0         return JS_FALSE;
6666 0     *rval = OBJECT_TO_JSVAL(parentobj);
6667 0     return JS_TRUE;
6668 }
6669
6670 /* XML and XMLList */
6671 static JSBool
6672 xml_processingInstructions(JSContext *cx, JSObject *obj, uintN argc,
6673                            jsval *argv, jsval *rval)
6674 0 {
6675 0     JSXML *xml, *list, *kid, *vxml;
6676 0     jsval name, v;
6677 0     JSXMLQName *nameqn;
6678 0     jsid funid;
6679 0     JSObject *listobj, *kidobj;
6680 0     JSBool ok;
6681 0     JSXMLArrayCursor cursor;
6682 0     uint32 i, n;
6683
6684 0     XML_METHOD_PROLOG;
6685 0     name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6686 0     nameqn = ToXMLName(cx, name, &funid);
6687 0     if (!nameqn)
6688 0         return JS_FALSE;
6689 0     argv[0] = OBJECT_TO_JSVAL(nameqn->object);
6690
6691 0     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
6692 0     if (!listobj)
6693 0         return JS_FALSE;
6694 0     *rval = OBJECT_TO_JSVAL(listobj);
6695 0     if (funid)
6696 0         return JS_TRUE;
6697
6698 0     list = (JSXML *) JS_GetPrivate(cx, listobj);
6699 0     list->xml_target = xml;
6700 0     list->xml_targetprop = nameqn;
6701 0     ok = JS_TRUE;
6702
6703 0     if (xml->xml_class == JSXML_CLASS_LIST) {
6704         /* 13.5.4.17 Step 4 (misnumbered 9 -- Erratum?). */
6705 0         XMLArrayCursorInit(&cursor, &xml->xml_kids);
6706 0         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
6707 0             if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6708 0                 ok = JS_EnterLocalRootScope(cx);
6709 0                 if (!ok)
6710 0                     break;
6711 0                 kidobj = js_GetXMLObject(cx, kid);
6712 0                 ok = kidobj
6713                      ? xml_processingInstructions(cx, kidobj, argc, argv, &v)
6714                      : JS_FALSE;
6715 0                 JS_LeaveLocalRootScope(cx);
6716 0                 if (!ok)
6717 0                     break;
6718 0                 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
6719 0                 if (JSXML_LENGTH(vxml) != 0) {
6720 0                     ok = Append(cx, list, vxml);
6721 0                     if (!ok)
6722 0                         break;
6723                 }
6724             }
6725         }
6726 0         XMLArrayCursorFinish(&cursor);
6727     } else {
6728         /* 13.4.4.28 Step 4. */
6729 0         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
6730 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6731 0             if (kid && kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
6732                 (IS_STAR(nameqn->localName) ||
6733                  !js_CompareStrings(nameqn->localName, kid->name->localName))) {
6734 0                 ok = Append(cx, list, kid);
6735 0                 if (!ok)
6736 0                     break;
6737             }
6738         }
6739     }
6740
6741 0     return ok;
6742 }
6743
6744 static JSBool
6745 xml_prependChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6746                  jsval *rval)
6747 0 {
6748 0     JSXML *xml;
6749
6750 0     XML_METHOD_PROLOG;
6751 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6752 0     if (!xml)
6753 0         return JS_FALSE;
6754 0     *rval = OBJECT_TO_JSVAL(obj);
6755 0     return Insert(cx, xml, 0, argv[0]);
6756 }
6757
6758 /* XML and XMLList */
6759 static JSBool
6760 xml_propertyIsEnumerable(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6761                          jsval *rval)
6762 0 {
6763 0     JSXML *xml;
6764 0     jsval name;
6765 0     uint32 index;
6766
6767 0     XML_METHOD_PROLOG;
6768 0     name = argv[0];
6769 0     *rval = JSVAL_FALSE;
6770 0     if (js_IdIsIndex(name, &index)) {
6771 0         if (xml->xml_class == JSXML_CLASS_LIST) {
6772             /* 13.5.4.18. */
6773 0             *rval = BOOLEAN_TO_JSVAL(index < xml->xml_kids.length);
6774         } else {
6775             /* 13.4.4.30. */
6776 0             *rval = BOOLEAN_TO_JSVAL(index == 0);
6777         }
6778     }
6779 0     return JS_TRUE;
6780 }
6781
6782 static JSBool
6783 namespace_full_match(const void *a, const void *b)
6784 0 {
6785 0     const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
6786 0     const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
6787
6788 0     if (nsa->prefix && nsb->prefix &&
6789         js_CompareStrings(nsa->prefix, nsb->prefix)) {
6790 0         return JS_FALSE;
6791     }
6792 0     return !js_CompareStrings(nsa->uri, nsb->uri);
6793 }
6794
6795 static JSBool
6796 xml_removeNamespace_helper(JSContext *cx, JSXML *xml, JSXMLNamespace *ns)
6797 0 {
6798 0     JSXMLNamespace *thisns, *attrns;
6799 0     uint32 i, n;
6800 0     JSXML *attr, *kid;
6801
6802 0     thisns = GetNamespace(cx, xml->name, &xml->xml_namespaces);
6803 0     JS_ASSERT(thisns);
6804 0     if (thisns == ns)
6805 0         return JS_TRUE;
6806
6807 0     for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
6808 0         attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
6809 0         if (!attr)
6810 0             continue;
6811 0         attrns = GetNamespace(cx, attr->name, &xml->xml_namespaces);
6812 0         JS_ASSERT(attrns);
6813 0         if (attrns == ns)
6814 0             return JS_TRUE;
6815     }
6816
6817 0     i = XMLARRAY_FIND_MEMBER(&xml->xml_namespaces, ns, namespace_full_match);
6818 0     if (i != XML_NOT_FOUND)
6819 0         XMLArrayDelete(cx, &xml->xml_namespaces, i, JS_TRUE);
6820
6821 0     for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6822 0         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6823 0         if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
6824 0             if (!xml_removeNamespace_helper(cx, kid, ns))
6825 0                 return JS_FALSE;
6826         }
6827     }
6828 0     return JS_TRUE;
6829 }
6830
6831 static JSBool
6832 xml_removeNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6833                     jsval *rval)
6834 0 {
6835 0     JSXML *xml;
6836 0     JSObject *nsobj;
6837 0     JSXMLNamespace *ns;
6838
6839 0     XML_METHOD_PROLOG;
6840 0     *rval = OBJECT_TO_JSVAL(obj);
6841 0     if (xml->xml_class != JSXML_CLASS_ELEMENT)
6842 0         return JS_TRUE;
6843 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6844 0     if (!xml)
6845 0         return JS_FALSE;
6846
6847 0     nsobj = CallConstructorFunction(cx, obj, &js_NamespaceClass.base, 1, argv);
6848 0     if (!nsobj)
6849 0         return JS_FALSE;
6850 0     argv[0] = OBJECT_TO_JSVAL(nsobj);
6851 0     ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
6852
6853     /* NOTE: remove ns from each ancestor if not used by that ancestor. */
6854 0     return xml_removeNamespace_helper(cx, xml, ns);
6855 }
6856
6857 static JSBool
6858 xml_replace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6859 0 {
6860 0     JSXML *xml, *vxml, *kid;
6861 0     jsval name, value, id, junk;
6862 0     uint32 index;
6863 0     JSObject *nameobj;
6864 0     JSXMLQName *nameqn;
6865
6866 0     XML_METHOD_PROLOG;
6867 0     *rval = OBJECT_TO_JSVAL(obj);
6868 0     if (xml->xml_class != JSXML_CLASS_ELEMENT)
6869 0         return JS_TRUE;
6870
6871 0     value = argv[1];
6872 0     vxml = VALUE_IS_XML(cx, value)
6873            ? (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(value))
6874            : NULL;
6875 0     if (!vxml) {
6876 0         if (!JS_ConvertValue(cx, value, JSTYPE_STRING, &argv[1]))
6877 0             return JS_FALSE;
6878 0         value = argv[1];
6879     } else {
6880 0         vxml = DeepCopy(cx, vxml, NULL, 0);
6881 0         if (!vxml)
6882 0             return JS_FALSE;
6883 0         value = argv[1] = OBJECT_TO_JSVAL(vxml->object);
6884     }
6885
6886 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6887 0     if (!xml)
6888 0         return JS_FALSE;
6889
6890 0     name = argv[0];
6891 0     if (js_IdIsIndex(name, &index))
6892 0         return Replace(cx, xml, name, value);
6893
6894     /* Call function QName per spec, not ToXMLName, to avoid attribute names. */
6895 0     nameobj = CallConstructorFunction(cx, obj, &js_QNameClass.base, 1, &name);
6896 0     if (!nameobj)
6897 0         return JS_FALSE;
6898 0     argv[0] = OBJECT_TO_JSVAL(nameobj);
6899 0     nameqn = (JSXMLQName *) JS_GetPrivate(cx, nameobj);
6900
6901 0     id = JSVAL_VOID;
6902 0     index = xml->xml_kids.length;
6903 0     while (index != 0) {
6904 0         --index;
6905 0         kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
6906 0         if (kid && MatchElemName(nameqn, kid)) {
6907 0             if (!JSVAL_IS_VOID(id) && !DeleteByIndex(cx, xml, id, &junk))
6908 0                 return JS_FALSE;
6909 0             if (!IndexToIdVal(cx, index, &id))
6910 0                 return JS_FALSE;
6911         }
6912     }
6913 0     if (JSVAL_IS_VOID(id))
6914 0         return JS_TRUE;
6915 0     return Replace(cx, xml, id, value);
6916 }
6917
6918 static JSBool
6919 xml_setChildren(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6920                 jsval *rval)
6921 0 {
6922 0     if (!PutProperty(cx, obj, ATOM_KEY(cx->runtime->atomState.starAtom),
6923                      &argv[0])) {
6924 0         return JS_FALSE;
6925     }
6926
6927 0     *rval = OBJECT_TO_JSVAL(obj);
6928 0     return JS_TRUE;
6929 }
6930
6931 static JSBool
6932 xml_setLocalName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6933                  jsval *rval)
6934 0 {
6935 0     JSXML *xml;
6936 0     jsval name;
6937 0     JSXMLQName *nameqn;
6938 0     JSString *namestr;
6939
6940 0     XML_METHOD_PROLOG;
6941 0     if (!JSXML_HAS_NAME(xml))
6942 0         return JS_TRUE;
6943
6944 0     name = argv[0];
6945 0     if (!JSVAL_IS_PRIMITIVE(name) &&
6946         OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(name)) == &js_QNameClass.base) {
6947 0         nameqn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(name));
6948 0         namestr = nameqn->localName;
6949     } else {
6950 0         if (!JS_ConvertValue(cx, name, JSTYPE_STRING, &argv[0]))
6951 0             return JS_FALSE;
6952 0         name = argv[0];
6953 0         namestr = JSVAL_TO_STRING(name);
6954     }
6955
6956 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6957 0     if (!xml)
6958 0         return JS_FALSE;
6959 0     xml->name->localName = namestr;
6960 0     return JS_TRUE;
6961 }
6962
6963 static JSBool
6964 xml_setName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6965 0 {
6966 0     JSXML *xml, *nsowner;
6967 0     jsval name;
6968 0     JSXMLQName *nameqn;
6969 0     JSObject *nameobj;
6970 0     JSXMLArray *nsarray;
6971 0     uint32 i, n;
6972 0     JSXMLNamespace *ns;
6973
6974 0     XML_METHOD_PROLOG;
6975 0     if (!JSXML_HAS_NAME(xml))
6976 0         return JS_TRUE;
6977
6978 0     name = argv[0];
6979 0     if (!JSVAL_IS_PRIMITIVE(name) &&
6980         OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(name)) == &js_QNameClass.base &&
6981         !(nameqn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(name)))
6982          ->uri) {
6983 0         name = argv[0] = STRING_TO_JSVAL(nameqn->localName);
6984     }
6985
6986 0     nameobj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &name);
6987 0     if (!nameobj)
6988 0         return JS_FALSE;
6989 0     nameqn = (JSXMLQName *) JS_GetPrivate(cx, nameobj);
6990
6991     /* ECMA-357 13.4.4.35 Step 4. */
6992 0     if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
6993 0         nameqn->uri = cx->runtime->emptyString;
6994
6995 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6996 0     if (!xml)
6997 0         return JS_FALSE;
6998 0     xml->name = nameqn;
6999
7000     /*
7001      * Erratum: nothing in 13.4.4.35 talks about making the name match the
7002      * in-scope namespaces, either by finding an in-scope namespace with a
7003      * matching uri and setting the new name's prefix to that namespace's
7004      * prefix, or by extending the in-scope namespaces for xml (which are in
7005      * xml->parent if xml is an attribute or a PI).
7006      */
7007 0     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7008 0         nsowner = xml;
7009     } else {
7010 0         if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
7011 0             return JS_TRUE;
7012 0         nsowner = xml->parent;
7013     }
7014
7015 0     if (nameqn->prefix) {
7016         /*
7017          * The name being set has a prefix, which originally came from some
7018          * namespace object (which may be the null namespace, where both the
7019          * prefix and uri are the empty string).  We must go through a full
7020          * GetNamespace in case that namespace is in-scope in nsowner.
7021          *
7022          * If we find such an in-scope namespace, we return true right away,
7023          * in this block.  Otherwise, we fall through to the final return of
7024          * AddInScopeNamespace(cx, nsowner, ns).
7025          */
7026 0         ns = GetNamespace(cx, nameqn, &nsowner->xml_namespaces);
7027 0         if (!ns)
7028 0             return JS_FALSE;
7029
7030         /* XXXbe have to test membership to see whether GetNamespace added */
7031 0         if (XMLARRAY_HAS_MEMBER(&nsowner->xml_namespaces, ns, NULL))
7032 0             return JS_TRUE;
7033     } else {
7034         /*
7035          * At this point, we know nameqn->prefix is null, so nameqn->uri can't
7036          * be the empty string (the null namespace always uses the empty string
7037          * for both prefix and uri).
7038          *
7039          * This means we must inline GetNamespace and specialize it to match
7040          * uri only, never prefix.  If we find a namespace with nameqn's uri
7041          * already in nsowner->xml_namespaces, then all that we need do is set
7042          * nameqn->prefix to that namespace's prefix.
7043          *
7044          * If no such namespace exists, we can create one without going through
7045          * the constructor, because we know nameqn->uri is non-empty (so prefix
7046          * does not need to be converted from null to empty by QName).
7047          */
7048 0         JS_ASSERT(!IS_EMPTY(nameqn->uri));
7049
7050 0         nsarray = &nsowner->xml_namespaces;
7051 0         for (i = 0, n = nsarray->length; i < n; i++) {
7052 0             ns = XMLARRAY_MEMBER(nsarray, i, JSXMLNamespace);
7053 0             if (ns && !js_CompareStrings(ns->uri, nameqn->uri)) {
7054 0                 nameqn->prefix = ns->prefix;
7055 0                 return JS_TRUE;
7056             }
7057         }
7058
7059 0         ns = js_NewXMLNamespace(cx, NULL, nameqn->uri, JS_TRUE);
7060 0         if (!ns)
7061 0             return JS_FALSE;
7062     }
7063
7064 0     return AddInScopeNamespace(cx, nsowner, ns);
7065 }
7066
7067 static JSBool
7068 xml_setNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7069                  jsval *rval)
7070 0 {
7071 0     JSXML *xml, *nsowner;
7072 0     JSObject *nsobj, *qnobj;
7073 0     JSXMLNamespace *ns;
7074 0     jsval qnargv[2];
7075
7076 0     XML_METHOD_PROLOG;
7077 0     if (xml->xml_class != JSXML_CLASS_ELEMENT &&
7078         xml->xml_class != JSXML_CLASS_ATTRIBUTE) {
7079 0         return JS_TRUE;
7080     }
7081
7082 0     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
7083 0     if (!xml || !js_GetXMLQNameObject(cx, xml->name))
7084 0         return JS_FALSE;
7085
7086 0     nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, obj, 1, argv);
7087 0     if (!nsobj)
7088 0         return JS_FALSE;
7089 0     ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
7090 0     ns->declared = JS_TRUE;
7091
7092 0     qnargv[0] = argv[0] = OBJECT_TO_JSVAL(nsobj);
7093 0     qnargv[1] = OBJECT_TO_JSVAL(xml->name->object);
7094 0     qnobj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, qnargv);
7095 0     if (!qnobj)
7096 0         return JS_FALSE;
7097
7098 0     xml->name = (JSXMLQName *) JS_GetPrivate(cx, qnobj);
7099
7100     /*
7101      * Erratum: the spec fails to update the governing in-scope namespaces.
7102      * See the erratum noted in xml_setName, above.
7103      */
7104 0     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7105 0         nsowner = xml;
7106     } else {
7107 0         if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
7108 0             return JS_TRUE;
7109 0         nsowner = xml->parent;
7110     }
7111 0     return AddInScopeNamespace(cx, nsowner, ns);
7112 }
7113
7114 /* XML and XMLList */
7115 static JSBool
7116 xml_text(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7117 0 {
7118 0     JSXML *xml, *list, *kid, *vxml;
7119 0     JSObject *listobj, *kidobj;
7120 0     uint32 i, n;
7121 0     JSBool ok;
7122 0     jsval v;
7123
7124 0     XML_METHOD_PROLOG;
7125 0     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
7126 0     if (!listobj)
7127 0         return JS_FALSE;
7128
7129 0     *rval = OBJECT_TO_JSVAL(listobj);
7130 0     list = (JSXML *) JS_GetPrivate(cx, listobj);
7131 0     list->xml_target = xml;
7132
7133 0     if (xml->xml_class == JSXML_CLASS_LIST) {
7134 0         ok = JS_TRUE;
7135 0         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
7136 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
7137 0             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
7138 0                 ok = JS_EnterLocalRootScope(cx);
7139 0                 if (!ok)
7140 0                     break;
7141 0                 kidobj = js_GetXMLObject(cx, kid);
7142 0                 ok = kidobj
7143                      ? xml_text(cx, kidobj, argc, argv, &v)
7144                      : JS_FALSE;
7145 0                 JS_LeaveLocalRootScope(cx);
7146 0                 if (!ok)
7147 0                     return JS_FALSE;
7148 0                 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
7149 0                 if (JSXML_LENGTH(vxml) != 0 && !Append(cx, list, vxml))
7150 0                     return JS_FALSE;
7151             }
7152         }
7153     } else {
7154 0         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
7155 0             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
7156 0             if (kid && kid->xml_class == JSXML_CLASS_TEXT) {
7157 0                 if (!Append(cx, list, kid))
7158 0                     return JS_FALSE;
7159             }
7160         }
7161     }
7162 0     return JS_TRUE;
7163 }
7164
7165 /* XML and XMLList */
7166 static JSBool
7167 xml_toXMLString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7168                 jsval *rval)
7169 0 {
7170 0     JSString *str;
7171
7172 0     str = ToXMLString(cx, OBJECT_TO_JSVAL(obj));
7173 0     if (!str)
7174 0         return JS_FALSE;
7175 0     *rval = STRING_TO_JSVAL(str);
7176 0     return JS_TRUE;
7177 }
7178
7179 /* XML and XMLList */
7180 static JSString *
7181 xml_toString_helper(JSContext *cx, JSXML *xml)
7182 0 {
7183 0     JSString *str, *kidstr;
7184 0     JSXML *kid;
7185 0     JSXMLArrayCursor cursor;
7186
7187 0     if (xml->xml_class == JSXML_CLASS_ATTRIBUTE ||
7188         xml->xml_class == JSXML_CLASS_TEXT) {
7189 0         return xml->xml_value;
7190     }
7191
7192 0     if (!HasSimpleContent(xml))
7193 0         return ToXMLString(cx, OBJECT_TO_JSVAL(xml->object));
7194
7195 0     str = cx->runtime->emptyString;
7196 0     JS_EnterLocalRootScope(cx);
7197 0     XMLArrayCursorInit(&cursor, &xml->xml_kids);
7198 0     while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
7199 0         if (kid->xml_class != JSXML_CLASS_COMMENT &&
7200             kid->xml_class != JSXML_CLASS_PROCESSING_INSTRUCTION) {
7201 0             kidstr = xml_toString_helper(cx, kid);
7202 0             if (!kidstr) {
7203 0                 str = NULL;
7204 0                 break;
7205             }
7206 0             str = js_ConcatStrings(cx, str, kidstr);
7207 0             if (!str)
7208 0                 break;
7209         }
7210     }
7211 0     XMLArrayCursorFinish(&cursor);
7212 0     JS_LeaveLocalRootScope(cx);
7213 0     return str;
7214 }
7215
7216 static JSBool
7217 xml_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7218              jsval *rval)
7219 0 {
7220 0     JSXML *xml;
7221 0     JSString *str;
7222
7223 0     XML_METHOD_PROLOG;
7224 0     str = xml_toString_helper(cx, xml);
7225 0     if (!str)
7226 0         return JS_FALSE;
7227 0     *rval = STRING_TO_JSVAL(str);
7228 0     return JS_TRUE;
7229 }
7230
7231 /* XML and XMLList */
7232 static JSBool
7233 xml_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7234 0 {
7235 0     *rval = OBJECT_TO_JSVAL(obj);
7236 0     return JS_TRUE;
7237 }
7238
7239 static JSFunctionSpec xml_methods[] = {
7240     {"addNamespace",          xml_addNamespace,          1,0,XML_MASK},
7241     {"appendChild",           xml_appendChild,           1,0,XML_MASK},
7242     {js_attribute_str,        xml_attribute,             1,0,GENERIC_MASK},
7243     {"attributes",            xml_attributes,            0,0,GENERIC_MASK},
7244     {"child",                 xml_child,                 1,0,GENERIC_MASK},
7245     {"childIndex",            xml_childIndex,            0,0,XML_MASK},
7246     {"children",              xml_children,              0,0,GENERIC_MASK},
7247     {"comments",              xml_comments,              0,0,GENERIC_MASK},
7248     {"contains",              xml_contains,              1,0,GENERIC_MASK},
7249     {"copy",                  xml_copy,                  0,0,GENERIC_MASK},
7250     {"descendants",           xml_descendants,           1,0,GENERIC_MASK},
7251     {"elements",              xml_elements,              1,0,GENERIC_MASK},
7252     {"hasOwnProperty",        xml_hasOwnProperty,        1,0,GENERIC_MASK},
7253     {"hasComplexContent",     xml_hasComplexContent,     1,0,GENERIC_MASK},
7254     {"hasSimpleContent",      xml_hasSimpleContent,      1,0,GENERIC_MASK},
7255     {"inScopeNamespaces",     xml_inScopeNamespaces,     0,0,XML_MASK},
7256     {"insertChildAfter",      xml_insertChildAfter,      2,0,XML_MASK},
7257     {"insertChildBefore",     xml_insertChildBefore,     2,0,XML_MASK},
7258     {js_length_str,           xml_length,                0,0,GENERIC_MASK},
7259     {js_localName_str,        xml_localName,             0,0,XML_MASK},
7260     {js_name_str,             xml_name,                  0,0,XML_MASK},
7261     {js_namespace_str,        xml_namespace,             1,0,XML_MASK},
7262     {"namespaceDeclarations", xml_namespaceDeclarations, 0,0,XML_MASK},
7263     {"nodeKind",              xml_nodeKind,              0,0,XML_MASK},
7264     {"normalize",             xml_normalize,             0,0,GENERIC_MASK},
7265     {js_xml_parent_str,       xml_parent,                0,0,GENERIC_MASK},
7266     {"processingInstructions",xml_processingInstructions,1,0,GENERIC_MASK},
7267     {"prependChild",          xml_prependChild,          1,0,XML_MASK},
7268     {"propertyIsEnumerable",  xml_propertyIsEnumerable,  1,0,GENERIC_MASK},
7269     {"removeNamespace",       xml_removeNamespace,       1,0,XML_MASK},
7270     {"replace",               xml_replace,               2,0,XML_MASK},
7271     {"setChildren",           xml_setChildren,           1,0,XML_MASK},
7272     {"setLocalName",          xml_setLocalName,          1,0,XML_MASK},
7273     {"setName",               xml_setName,               1,0,XML_MASK},
7274     {"setNamespace",          xml_setNamespace,          1,0,XML_MASK},
7275     {js_text_str,             xml_text,                  0,0,GENERIC_MASK},
7276     {js_toString_str,         xml_toString,              0,0,GENERIC_MASK},
7277     {js_toXMLString_str,      xml_toXMLString,           0,0,GENERIC_MASK},
7278     {js_toSource_str,         xml_toXMLString,           0,0,GENERIC_MASK},
7279     {js_valueOf_str,          xml_valueOf,               0,0,GENERIC_MASK},
7280     {0,0,0,0,0}
7281 };
7282
7283 static JSBool
7284 CopyXMLSettings(JSContext *cx, JSObject *from, JSObject *to)
7285 0 {
7286 0     int i;
7287 0     const char *name;
7288 0     jsval v;
7289
7290 0     for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
7291 0         name = xml_static_props[i].name;
7292 0         if (!JS_GetProperty(cx, from, name, &v))
7293 0             return JS_FALSE;
7294 0         if (JSVAL_IS_BOOLEAN(v) && !JS_SetProperty(cx, to, name, &v))
7295 0             return JS_FALSE;
7296     }
7297
7298 0     name = xml_static_props[i].name;
7299 0     if (!JS_GetProperty(cx, from, name, &v))
7300 0         return JS_FALSE;
7301 0     if (JSVAL_IS_NUMBER(v) && !JS_SetProperty(cx, to, name, &v))
7302 0         return JS_FALSE;
7303 0     return JS_TRUE;
7304 }
7305
7306 static JSBool
7307 SetDefaultXMLSettings(JSContext *cx, JSObject *obj)
7308 16 {
7309 16     int i;
7310 16     jsval v;
7311
7312 80     for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
7313 64         v = JSVAL_TRUE;
7314 64         if (!JS_SetProperty(cx, obj, xml_static_props[i].name, &v))
7315 0             return JS_FALSE;
7316     }
7317 16     v = INT_TO_JSVAL(2);
7318 16     return JS_SetProperty(cx, obj, xml_static_props[i].name, &v);
7319 }
7320
7321 static JSBool
7322 xml_settings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7323 0 {
7324 0     JSObject *settings;
7325
7326 0     settings = JS_NewObject(cx, NULL, NULL, NULL);
7327 0     if (!settings)
7328 0         return JS_FALSE;
7329 0     *rval = OBJECT_TO_JSVAL(settings);
7330 0     return CopyXMLSettings(cx, obj, settings);
7331 }
7332
7333 static JSBool
7334 xml_setSettings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7335                 jsval *rval)
7336 16 {
7337 16     jsval v;
7338 16     JSBool ok;
7339 16     JSObject *settings;
7340
7341 16     v = argv[0];
7342 16     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
7343 16         cx->xmlSettingFlags = 0;
7344 16         ok = SetDefaultXMLSettings(cx, obj);
7345     } else {
7346 0         if (JSVAL_IS_PRIMITIVE(v))
7347 0             return JS_TRUE;
7348 0         settings = JSVAL_TO_OBJECT(v);
7349 0         cx->xmlSettingFlags = 0;
7350 0         ok = CopyXMLSettings(cx, settings, obj);
7351     }
7352 16     if (ok)
7353 16         cx->xmlSettingFlags |= XSF_CACHE_VALID;
7354 16     return ok;
7355 }
7356
7357 static JSBool
7358 xml_defaultSettings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7359                     jsval *rval)
7360 0 {
7361 0     JSObject *settings;
7362
7363 0     settings = JS_NewObject(cx, NULL, NULL, NULL);
7364 0     if (!settings)
7365 0         return JS_FALSE;
7366 0     *rval = OBJECT_TO_JSVAL(settings);
7367 0     return SetDefaultXMLSettings(cx, settings);
7368 }
7369
7370 static JSFunctionSpec xml_static_methods[] = {
7371     {"settings",         xml_settings,          0,0,0},
7372     {"setSettings",      xml_setSettings,       1,0,0},
7373     {"defaultSettings",  xml_defaultSettings,   0,0,0},
7374     {0,0,0,0,0}
7375 };
7376
7377 static JSBool
7378 XML(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7379 0 {
7380 0     jsval v;
7381 0     JSXML *xml, *copy;
7382 0     JSObject *xobj, *vobj;
7383 0     JSClass *clasp;
7384
7385 0     v = argv[0];
7386 0     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
7387 0         v = STRING_TO_JSVAL(cx->runtime->emptyString);
7388
7389 0     xobj = ToXML(cx, v);
7390 0     if (!xobj)
7391 0         return JS_FALSE;
7392 0     *rval = OBJECT_TO_JSVAL(xobj);
7393 0     xml = (JSXML *) JS_GetPrivate(cx, xobj);
7394
7395 0     if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && !JSVAL_IS_PRIMITIVE(v)) {
7396 0         vobj = JSVAL_TO_OBJECT(v);
7397 0         clasp = OBJ_GET_CLASS(cx, vobj);
7398 0         if (clasp == &js_XMLClass ||
7399             (clasp->flags & JSCLASS_DOCUMENT_OBSERVER)) {
7400             /* No need to lock obj, it's newly constructed and thread local. */
7401 0             copy = DeepCopy(cx, xml, obj, 0);
7402 0             if (!copy)
7403 0                 return JS_FALSE;
7404 0             JS_ASSERT(copy->object == obj);
7405 0             *rval = OBJECT_TO_JSVAL(obj);
7406 0             return JS_TRUE;
7407         }
7408     }
7409 0     return JS_TRUE;
7410 }
7411
7412 static JSBool
7413 XMLList(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7414 0 {
7415 0     jsval v;
7416 0     JSObject *vobj, *listobj;
7417 0     JSXML *xml, *list;
7418
7419 0     v = argv[0];
7420 0     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
7421 0         v = STRING_TO_JSVAL(cx->runtime->emptyString);
7422
7423 0     if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && !JSVAL_IS_PRIMITIVE(v)) {
7424 0         vobj = JSVAL_TO_OBJECT(v);
7425 0         if (OBJECT_IS_XML(cx, vobj)) {
7426 0             xml = (JSXML *) JS_GetPrivate(cx, vobj);
7427 0             if (xml->xml_class == JSXML_CLASS_LIST) {
7428 0                 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
7429 0                 if (!listobj)
7430 0                     return JS_FALSE;
7431 0                 *rval = OBJECT_TO_JSVAL(listobj);
7432
7433 0                 list = (JSXML *) JS_GetPrivate(cx, listobj);
7434 0                 if (!Append(cx, list, xml))
7435 0                     return JS_FALSE;
7436 0                 return JS_TRUE;
7437             }
7438         }
7439     }
7440
7441     /* Toggle on XML support since the script has explicitly requested it. */
7442 0     listobj = ToXMLList(cx, v);
7443 0     if (!listobj)
7444 0         return JS_FALSE;
7445
7446 0     *rval = OBJECT_TO_JSVAL(listobj);
7447 0     return JS_TRUE;
7448 }
7449
7450 #define JSXML_LIST_SIZE     (offsetof(JSXML, u) + sizeof(struct JSXMLListVar))
7451 #define JSXML_ELEMENT_SIZE  (offsetof(JSXML, u) + sizeof(struct JSXMLVar))
7452 #define JSXML_LEAF_SIZE     (offsetof(JSXML, u) + sizeof(JSString *))
7453
7454 static size_t sizeof_JSXML[JSXML_CLASS_LIMIT] = {
7455     JSXML_LIST_SIZE,        /* JSXML_CLASS_LIST */
7456     JSXML_ELEMENT_SIZE,     /* JSXML_CLASS_ELEMENT */
7457     JSXML_LEAF_SIZE,        /* JSXML_CLASS_ATTRIBUTE */
7458     JSXML_LEAF_SIZE,        /* JSXML_CLASS_PROCESSING_INSTRUCTION */
7459     JSXML_LEAF_SIZE,        /* JSXML_CLASS_TEXT */
7460     JSXML_LEAF_SIZE         /* JSXML_CLASS_COMMENT */
7461 };
7462
7463 #ifdef DEBUG_notme
7464 JSCList xml_leaks = JS_INIT_STATIC_CLIST(&xml_leaks);
7465 uint32  xml_serial;
7466 #endif
7467
7468 JSXML *
7469 js_NewXML(JSContext *cx, JSXMLClass xml_class)
7470 16 {
7471 16     JSXML *xml;
7472
7473 16     xml = (JSXML *) js_NewGCThing(cx, GCX_XML, sizeof_JSXML[xml_class]);
7474 16     if (!xml)
7475 0         return NULL;
7476
7477 16     xml->object = NULL;
7478 16     xml->domnode = NULL;
7479 16     xml->parent = NULL;
7480 16     xml->name = NULL;
7481 16     xml->xml_class = xml_class;
7482 16     xml->xml_flags = 0;
7483 16     if (JSXML_CLASS_HAS_VALUE(xml_class)) {
7484 16         xml->xml_value = cx->runtime->emptyString;
7485     } else {
7486 0         XMLArrayInit(cx, &xml->xml_kids, 0);
7487 0         if (xml_class == JSXML_CLASS_LIST) {
7488 0             xml->xml_target = NULL;
7489 0             xml->xml_targetprop = NULL;
7490         } else {
7491 0             XMLArrayInit(cx, &xml->xml_namespaces, 0);
7492 0             XMLArrayInit(cx, &xml->xml_attrs, 0);
7493         }
7494     }
7495
7496 #ifdef DEBUG_notme
7497     JS_APPEND_LINK(&xml->links, &xml_leaks);
7498     xml->serial = xml_serial++;
7499 #endif
7500     METER(xml_stats.xml);
7501     METER(xml_stats.livexml);
7502 16     return xml;
7503 }
7504
7505 static void
7506 xml_mark_tail(JSContext *cx, JSXML *xml, void *arg)
7507 0 {
7508 0     XMLArrayTrim(&xml->xml_kids);
7509
7510 0     if (xml->xml_class == JSXML_CLASS_LIST) {
7511 0         if (xml->xml_target)
7512 0             JS_MarkGCThing(cx, xml->xml_target, "target", arg);
7513 0         if (xml->xml_targetprop)
7514 0             JS_MarkGCThing(cx, xml->xml_targetprop, "targetprop", arg);
7515     } else {
7516 0         namespace_mark_vector(cx,
7517                               (JSXMLNamespace **) xml->xml_namespaces.vector,
7518                               xml->xml_namespaces.length,
7519                               arg);
7520 0         XMLArrayCursorMark(cx, xml->xml_namespaces.cursors);
7521 0         XMLArrayTrim(&xml->xml_namespaces);
7522
7523 0         xml_mark_vector(cx,
7524                         (JSXML **) xml->xml_attrs.vector,
7525                         xml->xml_attrs.length,
7526                         arg);
7527 0         XMLArrayCursorMark(cx, xml->xml_attrs.cursors);
7528 0         XMLArrayTrim(&xml->xml_attrs);
7529     }
7530 }
7531
7532 void
7533 js_MarkXML(JSContext *cx, JSXML *xml, void *arg)
7534 16 {
7535 16     JS_MarkGCThing(cx, xml->object, js_object_str, arg);
7536 16     JS_MarkGCThing(cx, xml->name, js_name_str, arg);
7537 16     JS_MarkGCThing(cx, xml->parent, js_xml_parent_str, arg);
7538
7539 16     if (JSXML_HAS_VALUE(xml)) {
7540 16         JS_MarkGCThing(cx, xml->xml_value, "value", arg);
7541     } else {
7542 0         xml_mark_vector(cx,
7543                         (JSXML **) xml->xml_kids.vector,
7544                         xml->xml_kids.length,
7545                         arg);
7546 0         XMLArrayCursorMark(cx, xml->xml_kids.cursors);
7547
7548 0         xml_mark_tail(cx, xml, arg);
7549     }
7550 }
7551
7552 void
7553 js_FinalizeXML(JSContext *cx, JSXML *xml)
7554 16 {
7555 16     if (JSXML_HAS_KIDS(xml)) {
7556 0         XMLArrayFinish(cx, &xml->xml_kids);
7557 0         if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7558 0             XMLArrayFinish(cx, &xml->xml_namespaces);
7559 0             XMLArrayFinish(cx, &xml->xml_attrs);
7560         }
7561     }
7562
7563 #ifdef DEBUG_notme
7564     JS_REMOVE_LINK(&xml->links);
7565 #endif
7566
7567     UNMETER(xml_stats.livexml);
7568 }
7569
7570 JSObject *
7571 js_ParseNodeToXMLObject(JSContext *cx, JSParseNode *pn)
7572 0 {
7573 0     jsval nsval;
7574 0     JSXMLNamespace *ns;
7575 0     JSXMLArray nsarray;
7576 0     JSXML *xml;
7577
7578 0     if (!js_GetDefaultXMLNamespace(cx, &nsval))
7579 0         return NULL;
7580 0     JS_ASSERT(!JSVAL_IS_PRIMITIVE(nsval));
7581 0     ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
7582
7583 0     if (!XMLArrayInit(cx, &nsarray, 1))
7584 0         return NULL;
7585
7586 0     XMLARRAY_APPEND(cx, &nsarray, ns);
7587 0     xml = ParseNodeToXML(cx, pn, &nsarray, XSF_PRECOMPILED_ROOT);
7588 0     XMLArrayFinish(cx, &nsarray);
7589 0     if (!xml)
7590 0         return NULL;
7591
7592 0     return xml->object;
7593 }
7594
7595 JSObject *
7596 js_NewXMLObject(JSContext *cx, JSXMLClass xml_class)
7597 0 {
7598 0     JSXML *xml;
7599 0     JSObject *obj;
7600 0     JSTempValueRooter tvr;
7601
7602 0     xml = js_NewXML(cx, xml_class);
7603 0     if (!xml)
7604 0         return NULL;
7605 0     JS_PUSH_SINGLE_TEMP_ROOT(cx, OBJECT_TO_JSVAL(xml), &tvr);
7606 0     obj = js_GetXMLObject(cx, xml);
7607 0     JS_POP_TEMP_ROOT(cx, &tvr);
7608 0     return obj;
7609 }
7610
7611 static JSObject *
7612 NewXMLObject(JSContext *cx, JSXML *xml)
7613 0 {
7614 0     JSObject *obj;
7615
7616 0     obj = js_NewObject(cx, &js_XMLClass, NULL, NULL);
7617 0     if (!obj || !JS_SetPrivate(cx, obj, xml)) {
7618 0         cx->newborn[GCX_OBJECT] = NULL;
7619 0         return NULL;
7620     }
7621     METER(xml_stats.xmlobj);
7622     METER(xml_stats.livexmlobj);
7623 0     return obj;
7624 }
7625
7626 JSObject *
7627 js_GetXMLObject(JSContext *cx, JSXML *xml)
7628 0 {
7629 0     JSObject *obj;
7630
7631 0     obj = xml->object;
7632 0     if (obj) {
7633 0         JS_ASSERT(JS_GetPrivate(cx, obj) == xml);
7634 0         return obj;
7635     }
7636
7637     /*
7638      * A JSXML cannot be shared among threads unless it has an object.
7639      * A JSXML cannot be given an object unless:
7640      * (a) it has no parent; or
7641      * (b) its parent has no object (therefore is thread-private); or
7642      * (c) its parent's object is locked.
7643      *
7644      * Once given an object, a JSXML is immutable.
7645      */
7646 0     JS_ASSERT(!xml->parent ||
7647               !xml->parent->object ||
7648               JS_IS_OBJ_LOCKED(cx, xml->parent->object));
7649
7650 0     obj = NewXMLObject(cx, xml);
7651 0     if (!obj)
7652 0         return NULL;
7653 0     xml->object = obj;
7654 0     return obj;
7655 }
7656
7657 JSObject *
7658 js_InitNamespaceClass(JSContext *cx, JSObject *obj)
7659 16 {
7660 16     return JS_InitClass(cx, obj, NULL, &js_NamespaceClass.base, Namespace, 2,
7661                         namespace_props, namespace_methods, NULL, NULL);
7662 }
7663
7664 JSObject *
7665 js_InitQNameClass(JSContext *cx, JSObject *obj)
7666 16 {
7667 16     return JS_InitClass(cx, obj, NULL, &js_QNameClass.base, QName, 2,
7668                         qname_props, qname_methods, NULL, NULL);
7669 }
7670
7671 JSObject *
7672 js_InitAttributeNameClass(JSContext *cx, JSObject *obj)
7673 16 {
7674 16     return JS_InitClass(cx, obj, NULL, &js_AttributeNameClass, AttributeName, 2,
7675                         qname_props, qname_methods, NULL, NULL);
7676 }
7677
7678 JSObject *
7679 js_InitAnyNameClass(JSContext *cx, JSObject *obj)
7680 16 {
7681 16     jsval v;
7682
7683 16     if (!js_GetAnyName(cx, &v))
7684 0         return NULL;
7685 16     return JSVAL_TO_OBJECT(v);
7686 }
7687
7688 JSObject *
7689 js_InitXMLClass(JSContext *cx, JSObject *obj)
7690 16 {
7691 16     JSObject *proto, *pobj, *ctor;
7692 16     JSFunctionSpec *fs;
7693 16     JSFunction *fun;
7694 16     JSXML *xml;
7695 16     JSProperty *prop;
7696 16     JSScopeProperty *sprop;
7697 16     jsval cval, argv[1], junk;
7698
7699     /* Define the isXMLName function. */
7700 16     if (!JS_DefineFunction(cx, obj, js_isXMLName_str, xml_isXMLName, 1, 0))
7701 0         return NULL;
7702
7703     /* Define the XML class constructor and prototype. */
7704 16     proto = JS_InitClass(cx, obj, NULL, &js_XMLClass, XML, 1,
7705                          NULL, NULL,
7706                          xml_static_props, xml_static_methods);
7707 16     if (!proto)
7708 0         return NULL;
7709
7710     /*
7711