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 ***** */
40 #include "jsstddef.h"
41 #include "jsconfig.h"
43 #if JS_HAS_XML_SUPPORT
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"
71 #ifdef DEBUG
72 #include <string.h> /* for #ifdef DEBUG memset calls */
73 #endif
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 */
89 #ifdef DEBUG_brendan
90 #define METERING 1
91 #endif
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;
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
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";
129 const char js_amp_entity_str[] = "&";
130 const char js_gt_entity_str[] = ">";
131 const char js_lt_entity_str[] = "<";
132 const char js_quot_entity_str[] = """;
134 #define IS_EMPTY(str) (JSSTRING_LENGTH(str) == 0)
135 #define IS_STAR(str) (JSSTRING_LENGTH(str) == 1 && *JSSTRING_CHARS(str) == '*')
137 static JSBool
138 xml_isXMLName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
139 jsval *rval)
140 {
141 *rval = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argv[0]));
142 return JS_TRUE;
143 }
145 /*
146 * Namespace class and library functions.
147 */
148 enum namespace_tinyid {
149 NAMESPACE_PREFIX = -1,
150 NAMESPACE_URI = -2
151 };
153 static JSBool
154 namespace_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
155 {
156 JSXMLNamespace *ns;
158 if (!JSVAL_IS_INT(id))
159 return JS_TRUE;
161 ns = (JSXMLNamespace *)
162 JS_GetInstancePrivate(cx, obj, &js_NamespaceClass.base, NULL);
163 if (!ns)
164 return JS_TRUE;
166 switch (JSVAL_TO_INT(id)) {
167 case NAMESPACE_PREFIX:
168 *vp = ns->prefix ? STRING_TO_JSVAL(ns->prefix) : JSVAL_VOID;
169 break;
170 case NAMESPACE_URI:
171 *vp = STRING_TO_JSVAL(ns->uri);
172 break;
173 }
174 return JS_TRUE;
175 }
177 static void
178 namespace_finalize(JSContext *cx, JSObject *obj)
179 {
180 JSXMLNamespace *ns;
181 JSRuntime *rt;
183 ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
184 if (!ns)
185 return;
186 JS_ASSERT(ns->object == obj);
187 ns->object = NULL;
188 UNMETER(xml_stats.livenamespaceobj);
190 rt = cx->runtime;
191 if (rt->functionNamespaceObject == obj)
192 rt->functionNamespaceObject = NULL;
193 }
195 static void
196 namespace_mark_vector(JSContext *cx, JSXMLNamespace **vec, uint32 len,
197 void *arg)
198 {
199 uint32 i;
200 JSXMLNamespace *ns;
202 for (i = 0; i < len; i++) {
203 ns = vec[i];
204 {
205 #ifdef GC_MARK_DEBUG
206 char buf[100];
208 JS_snprintf(buf, sizeof buf, "%s=%s",
209 ns->prefix ? JS_GetStringBytes(ns->prefix) : "",
210 JS_GetStringBytes(ns->uri));
211 #else
212 const char *buf = NULL;
213 #endif
214 JS_MarkGCThing(cx, ns, buf, arg);
215 }
216 }
217 }
219 static uint32
220 namespace_mark(JSContext *cx, JSObject *obj, void *arg)
221 {
222 JSXMLNamespace *ns;
224 ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
225 JS_MarkGCThing(cx, ns, js_private_str, arg);
226 return 0;
227 }
229 static JSBool
230 namespace_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
231 {
232 JSXMLNamespace *ns, *ns2;
233 JSObject *obj2;
235 ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
236 JS_ASSERT(JSVAL_IS_OBJECT(v));
237 obj2 = JSVAL_TO_OBJECT(v);
238 if (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_NamespaceClass.base) {
239 *bp = JS_FALSE;
240 } else {
241 ns2 = (JSXMLNamespace *) JS_GetPrivate(cx, obj2);
242 *bp = !js_CompareStrings(ns->uri, ns2->uri);
243 }
244 return JS_TRUE;
245 }
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 };
259 #define NAMESPACE_ATTRS \
260 (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
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 };
268 static JSBool
269 namespace_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
270 jsval *rval)
271 {
272 JSXMLNamespace *ns;
274 ns = (JSXMLNamespace *)
275 JS_GetInstancePrivate(cx, obj, &js_NamespaceClass.base, argv);
276 if (!ns)
277 return JS_FALSE;
279 *rval = STRING_TO_JSVAL(ns->uri);
280 return JS_TRUE;
281 }
283 static JSFunctionSpec namespace_methods[] = {
284 {js_toString_str, namespace_toString, 0,0,0},
285 {0,0,0,0,0}
286 };
288 JSXMLNamespace *
289 js_NewXMLNamespace(JSContext *cx, JSString *prefix, JSString *uri,
290 JSBool declared)
291 {
292 JSXMLNamespace *ns;
294 ns = (JSXMLNamespace *)
295 js_NewGCThing(cx, GCX_NAMESPACE, sizeof(JSXMLNamespace));
296 if (!ns)
297 return NULL;
298 ns->object = NULL;
299 ns->prefix = prefix;
300 ns->uri = uri;
301 ns->declared = declared;
302 METER(xml_stats.namespace);
303 METER(xml_stats.livenamespace);
304 return ns;
305 }
307 void
308 js_MarkXMLNamespace(JSContext *cx, JSXMLNamespace *ns, void *arg)
309 {
310 JS_MarkGCThing(cx, ns->object, js_object_str, arg);
311 JS_MarkGCThing(cx, ns->prefix, js_prefix_str, arg);
312 JS_MarkGCThing(cx, ns->uri, js_uri_str, arg);
313 }
315 void
316 js_FinalizeXMLNamespace(JSContext *cx, JSXMLNamespace *ns)
317 {
318 UNMETER(xml_stats.livenamespace);
319 }
321 JSObject *
322 js_NewXMLNamespaceObject(JSContext *cx, JSString *prefix, JSString *uri,
323 JSBool declared)
324 {
325 JSXMLNamespace *ns;
327 ns = js_NewXMLNamespace(cx, prefix, uri, declared);
328 if (!ns)
329 return NULL;
330 return js_GetXMLNamespaceObject(cx, ns);
331 }
333 JSObject *
334 js_GetXMLNamespaceObject(JSContext *cx, JSXMLNamespace *ns)
335 {
336 JSObject *obj;
338 obj = ns->object;
339 if (obj) {
340 JS_ASSERT(JS_GetPrivate(cx, obj) == ns);
341 return obj;
342 }
343 obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
344 if (!obj || !JS_SetPrivate(cx, obj, ns)) {
345 cx->newborn[GCX_OBJECT] = NULL;
346 return NULL;
347 }
348 ns->object = obj;
349 METER(xml_stats.namespaceobj);
350 METER(xml_stats.livenamespaceobj);
351 return obj;
352 }
354 /*
355 * QName class and library functions.
356 */
357 enum qname_tinyid {
358 QNAME_URI = -1,
359 QNAME_LOCALNAME = -2
360 };
362 static JSBool
363 qname_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
364 {
365 JSXMLQName *qn;
367 if (!JSVAL_IS_INT(id))
368 return JS_TRUE;
370 qn = (JSXMLQName *)
371 JS_GetInstancePrivate(cx, obj, &js_QNameClass.base, NULL);
372 if (!qn)
373 return JS_TRUE;
375 switch (JSVAL_TO_INT(id)) {
376 case QNAME_URI:
377 *vp = qn->uri ? STRING_TO_JSVAL(qn->uri) : JSVAL_NULL;
378 break;
379 case QNAME_LOCALNAME:
380 *vp = STRING_TO_JSVAL(qn->localName);
381 break;
382 }
383 return JS_TRUE;
384 }
386 static void
387 qname_finalize(JSContext *cx, JSObject *obj)
388 {
389 JSXMLQName *qn;
391 qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
392 if (!qn)
393 return;
394 JS_ASSERT(qn->object == obj);
395 qn->object = NULL;
396 UNMETER(xml_stats.liveqnameobj);
397 }
399 static void
400 anyname_finalize(JSContext* cx, JSObject* obj)
401 {
402 JSRuntime *rt;
404 /* Make sure the next call to js_GetAnyName doesn't try to use obj. */
405 rt = cx->runtime;
406 if (rt->anynameObject == obj)
407 rt->anynameObject = NULL;
409 qname_finalize(cx, obj);
410 }
412 static uint32
413 qname_mark(JSContext *cx, JSObject *obj, void *arg)
414 {
415 JSXMLQName *qn;
417 qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
418 JS_MarkGCThing(cx, qn, js_private_str, arg);
419 return 0;
420 }
422 static JSBool
423 qname_identity(JSXMLQName *qna, JSXMLQName *qnb)
424 {
425 if (!qna->uri ^ !qnb->uri)
426 return JS_FALSE;
427 if (qna->uri && js_CompareStrings(qna->uri, qnb->uri))
428 return JS_FALSE;
429 return !js_CompareStrings(qna->localName, qnb->localName);
430 }
432 static JSBool
433 qname_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
434 {
435 JSXMLQName *qn, *qn2;
436 JSObject *obj2;
438 qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
439 JS_ASSERT(JSVAL_IS_OBJECT(v));
440 obj2 = JSVAL_TO_OBJECT(v);
441 if (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_QNameClass.base) {
442 *bp = JS_FALSE;
443 } else {
444 qn2 = (JSXMLQName *) JS_GetPrivate(cx, obj2);
445 *bp = qname_identity(qn, qn2);
446 }
447 return JS_TRUE;
448 }
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 };
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 };
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 };
484 #define QNAME_ATTRS \
485 (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
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 };
493 static JSBool
494 qname_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
495 jsval *rval)
496 {
497 JSClass *clasp;
498 JSXMLQName *qn;
499 JSString *str, *qualstr;
500 size_t length;
501 jschar *chars;
503 clasp = OBJ_GET_CLASS(cx, obj);
504 if (clasp == &js_AttributeNameClass || clasp == &js_AnyNameClass) {
505 qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
506 } else {
507 qn = (JSXMLQName *)
508 JS_GetInstancePrivate(cx, obj, &js_QNameClass.base, argv);
509 if (!qn)
510 return JS_FALSE;
511 }
513 if (!qn->uri) {
514 /* No uri means wildcard qualifier. */
515 str = ATOM_TO_STRING(cx->runtime->atomState.starQualifierAtom);
516 } else if (IS_EMPTY(qn->uri)) {
517 /* Empty string for uri means localName is in no namespace. */
518 str = cx->runtime->emptyString;
519 } else {
520 qualstr = ATOM_TO_STRING(cx->runtime->atomState.qualifierAtom);
521 str = js_ConcatStrings(cx, qn->uri, qualstr);
522 if (!str)
523 return JS_FALSE;
524 }
525 str = js_ConcatStrings(cx, str, qn->localName);
526 if (!str)
527 return JS_FALSE;
529 if (str && clasp == &js_AttributeNameClass) {
530 length = JSSTRING_LENGTH(str);
531 chars = (jschar *) JS_malloc(cx, (length + 2) * sizeof(jschar));
532 if (!chars)
533 return JS_FALSE;
534 *chars = '@';
535 js_strncpy(chars + 1, JSSTRING_CHARS(str), length);
536 chars[++length] = 0;
537 str = js_NewString(cx, chars, length, 0);
538 if (!str) {
539 JS_free(cx, chars);
540 return JS_FALSE;
541 }
542 }
544 *rval = STRING_TO_JSVAL(str);
545 return JS_TRUE;
546 }
548 static JSFunctionSpec qname_methods[] = {
549 {js_toString_str, qname_toString, 0,0,0},
550 {0,0,0,0,0}
551 };
553 JSXMLQName *
554 js_NewXMLQName(JSContext *cx, JSString *uri, JSString *prefix,
555 JSString *localName)
556 {
557 JSXMLQName *qn;
559 qn = (JSXMLQName *) js_NewGCThing(cx, GCX_QNAME, sizeof(JSXMLQName));
560 if (!qn)
561 return NULL;
562 qn->object = NULL;
563 qn->uri = uri;
564 qn->prefix = prefix;
565 qn->localName = localName;
566 METER(xml_stats.qname);
567 METER(xml_stats.liveqname);
568 return qn;
569 }
571 void
572 js_MarkXMLQName(JSContext *cx, JSXMLQName *qn, void *arg)
573 {
574 JS_MarkGCThing(cx, qn->object, js_object_str, arg);
575 JS_MarkGCThing(cx, qn->uri, js_uri_str, arg);
576 JS_MarkGCThing(cx, qn->prefix, js_prefix_str, arg);
577 JS_MarkGCThing(cx, qn->localName, js_localName_str, arg);
578 }
580 void
581 js_FinalizeXMLQName(JSContext *cx, JSXMLQName *qn)
582 {
583 UNMETER(xml_stats.liveqname);
584 }
586 JSObject *
587 js_NewXMLQNameObject(JSContext *cx, JSString *uri, JSString *prefix,
588 JSString *localName)
589 {
590 JSXMLQName *qn;
592 qn = js_NewXMLQName(cx, uri, prefix, localName);
593 if (!qn)
594 return NULL;
595 return js_GetXMLQNameObject(cx, qn);
596 }
598 JSObject *
599 js_GetXMLQNameObject(JSContext *cx, JSXMLQName *qn)
600 {
601 JSObject *obj;
603 obj = qn->object;
604 if (obj) {
605 JS_ASSERT(JS_GetPrivate(cx, obj) == qn);
606 return obj;
607 }
608 obj = js_NewObject(cx, &js_QNameClass.base, NULL, NULL);
609 if (!obj || !JS_SetPrivate(cx, obj, qn)) {
610 cx->newborn[GCX_OBJECT] = NULL;
611 return NULL;
612 }
613 qn->object = obj;
614 METER(xml_stats.qnameobj);
615 METER(xml_stats.liveqnameobj);
616 return obj;
617 }
619 JSObject *
620 js_GetAttributeNameObject(JSContext *cx, JSXMLQName *qn)
621 {
622 JSObject *obj;
624 obj = qn->object;
625 if (obj) {
626 if (OBJ_GET_CLASS(cx, obj) == &js_AttributeNameClass)
627 return obj;
628 qn = js_NewXMLQName(cx, qn->uri, qn->prefix, qn->localName);
629 if (!qn)
630 return NULL;
631 }
633 obj = js_NewObject(cx, &js_AttributeNameClass, NULL, NULL);
634 if (!obj || !JS_SetPrivate(cx, obj, qn)) {
635 cx->newborn[GCX_OBJECT] = NULL;
636 return NULL;
637 }
639 qn->object = obj;
640 METER(xml_stats.qnameobj);
641 METER(xml_stats.liveqnameobj);
642 return obj;
643 }
645 JSObject *
646 js_ConstructXMLQNameObject(JSContext *cx, jsval nsval, jsval lnval)
647 {
648 jsval argv[2];
650 /*
651 * ECMA-357 11.1.2,
652 * The _QualifiedIdentifier : PropertySelector :: PropertySelector_
653 * production, step 2.
654 */
655 if (!JSVAL_IS_PRIMITIVE(nsval) &&
656 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nsval)) == &js_AnyNameClass) {
657 nsval = JSVAL_NULL;
658 }
660 argv[0] = nsval;
661 argv[1] = lnval;
662 return js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, argv);
663 }
665 static JSBool
666 IsXMLName(const jschar *cp, size_t n)
667 {
668 JSBool rv;
669 jschar c;
671 rv = JS_FALSE;
672 if (n != 0 && JS_ISXMLNSSTART(*cp)) {
673 while (--n != 0) {
674 c = *++cp;
675 if (!JS_ISXMLNS(c))
676 return rv;
677 }
678 rv = JS_TRUE;
679 }
680 return rv;
681 }
683 JSBool
684 js_IsXMLName(JSContext *cx, jsval v)
685 {
686 JSClass *clasp;
687 JSXMLQName *qn;
688 JSString *name;
689 JSErrorReporter older;
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 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 qn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
703 name = qn->localName;
704 } else {
705 older = JS_SetErrorReporter(cx, NULL);
706 name = js_ValueToString(cx, v);
707 JS_SetErrorReporter(cx, older);
708 if (!name) {
709 JS_ClearPendingException(cx);
710 return JS_FALSE;
711 }
712 }
714 return IsXMLName(JSSTRING_CHARS(name), JSSTRING_LENGTH(name));
715 }
717 static JSBool
718 Namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
719 {
720 jsval urival, prefixval;
721 JSObject *uriobj;
722 JSBool isNamespace, isQName;
723 JSClass *clasp;
724 JSString *empty, *prefix;
725 JSXMLNamespace *ns, *ns2;
726 JSXMLQName *qn;
728 urival = argv[argc > 1];
729 isNamespace = isQName = JS_FALSE;
730 if (!JSVAL_IS_PRIMITIVE(urival)) {
731 uriobj = JSVAL_TO_OBJECT(urival);
732 clasp = OBJ_GET_CLASS(cx, uriobj);
733 isNamespace = (clasp == &js_NamespaceClass.base);
734 isQName = (clasp == &js_QNameClass.base);
735 }
736 #ifdef __GNUC__ /* suppress bogus gcc warnings */
737 else uriobj = NULL;
738 #endif
740 if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
741 /* Namespace called as function. */
742 if (argc == 1 && isNamespace) {
743 /* Namespace called with one Namespace argument is identity. */
744 *rval = urival;
745 return JS_TRUE;
746 }
748 /* Create and return a new QName object exactly as if constructed. */
749 obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
750 if (!obj)
751 return JS_FALSE;
752 *rval = OBJECT_TO_JSVAL(obj);
753 }
754 METER(xml_stats.namespaceobj);
755 METER(xml_stats.livenamespaceobj);
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 empty = cx->runtime->emptyString;
763 ns = js_NewXMLNamespace(cx, empty, empty, JS_FALSE);
764 if (!ns)
765 return JS_FALSE;
766 if (!JS_SetPrivate(cx, obj, ns))
767 return JS_FALSE;
768 ns->object = obj;
770 if (argc == 1) {
771 if (isNamespace) {
772 ns2 = (JSXMLNamespace *) JS_GetPrivate(cx, uriobj);
773 ns->uri = ns2->uri;
774 ns->prefix = ns2->prefix;
775 } else if (isQName &&
776 (qn = (JSXMLQName *) JS_GetPrivate(cx, uriobj))->uri) {
777 ns->uri = qn->uri;
778 ns->prefix = qn->prefix;
779 } else {
780 ns->uri = js_ValueToString(cx, urival);
781 if (!ns->uri)
782 return JS_FALSE;
784 /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
785 if (!IS_EMPTY(ns->uri))
786 ns->prefix = NULL;
787 }
788 } else if (argc == 2) {
789 if (isQName &&
790 (qn = (JSXMLQName *) JS_GetPrivate(cx, uriobj))->uri) {
791 ns->uri = qn->uri;
792 } else {
793 ns->uri = js_ValueToString(cx, urival);
794 if (!ns->uri)
795 return JS_FALSE;
796 }
798 prefixval = argv[0];
799 if (IS_EMPTY(ns->uri)) {
800 if (!JSVAL_IS_VOID(prefixval)) {
801 prefix = js_ValueToString(cx, prefixval);
802 if (!prefix)
803 return JS_FALSE;
804 if (!IS_EMPTY(prefix)) {
805 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
806 JSMSG_BAD_XML_NAMESPACE,
807 js_ValueToPrintableString(cx,
808 STRING_TO_JSVAL(prefix)));
809 return JS_FALSE;
810 }
811 }
812 } 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 ns->prefix = NULL;
815 } else {
816 prefix = js_ValueToString(cx, prefixval);
817 if (!prefix)
818 return JS_FALSE;
819 ns->prefix = prefix;
820 }
821 }
823 return JS_TRUE;
824 }
826 static JSBool
827 QName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
828 {
829 jsval nameval, nsval;
830 JSBool isQName, isNamespace;
831 JSXMLQName *qn;
832 JSString *uri, *prefix, *name;
833 JSObject *nsobj;
834 JSClass *clasp;
835 JSXMLNamespace *ns;
837 nameval = argv[argc > 1];
838 isQName =
839 !JSVAL_IS_PRIMITIVE(nameval) &&
840 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nameval)) == &js_QNameClass.base;
842 if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
843 /* QName called as function. */
844 if (argc == 1 && isQName) {
845 /* QName called with one QName argument is identity. */
846 *rval = nameval;
847 return JS_TRUE;
848 }
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 obj = js_NewObject(cx,
856 argv
857 ? JS_ValueToFunction(cx, argv[-2])->clasp
858 : &js_QNameClass.base,
859 NULL, NULL);
860 if (!obj)
861 return JS_FALSE;
862 *rval = OBJECT_TO_JSVAL(obj);
863 }
864 METER(xml_stats.qnameobj);
865 METER(xml_stats.liveqnameobj);
867 if (isQName) {
868 /* If namespace is not specified and name is a QName, clone it. */
869 qn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nameval));
870 if (argc == 1) {
871 uri = qn->uri;
872 prefix = qn->prefix;
873 name = qn->localName;
874 goto out;
875 }
877 /* Namespace and qname were passed -- use the qname's localName. */
878 nameval = STRING_TO_JSVAL(qn->localName);
879 }
881 if (argc == 0) {
882 name = cx->runtime->emptyString;
883 } else {
884 name = js_ValueToString(cx, nameval);
885 if (!name)
886 return JS_FALSE;
888 /* Use argv[1] as a local root for name, even if it was not passed. */
889 argv[1] = STRING_TO_JSVAL(name);
890 }
892 nsval = argv[0];
893 if (argc == 1 || JSVAL_IS_VOID(nsval)) {
894 if (IS_STAR(name)) {
895 nsval = JSVAL_NULL;
896 } else {
897 if (!js_GetDefaultXMLNamespace(cx, &nsval))
898 return JS_FALSE;
899 }
900 }
902 if (JSVAL_IS_NULL(nsval)) {
903 /* NULL prefix represents *undefined* in ECMA-357 13.3.2 5(a). */
904 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 isNamespace = isQName = JS_FALSE;
914 if (!JSVAL_IS_PRIMITIVE(nsval)) {
915 nsobj = JSVAL_TO_OBJECT(nsval);
916 clasp = OBJ_GET_CLASS(cx, nsobj);
917 isNamespace = (clasp == &js_NamespaceClass.base);
918 isQName = (clasp == &js_QNameClass.base);
919 }
920 #ifdef __GNUC__ /* suppress bogus gcc warnings */
921 else nsobj = NULL;
922 #endif
924 if (isNamespace) {
925 ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
926 uri = ns->uri;
927 prefix = ns->prefix;
928 } else if (isQName &&
929 (qn = (JSXMLQName *) JS_GetPrivate(cx, nsobj))->uri) {
930 uri = qn->uri;
931 prefix = qn->prefix;
932 } else {
933 uri = js_ValueToString(cx, nsval);
934 if (!uri)
935 return JS_FALSE;
936 argv[0] = STRING_TO_JSVAL(uri); /* local root */
938 /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
939 prefix = IS_EMPTY(uri) ? cx->runtime->emptyString : NULL;
940 }
941 }
943 out:
944 qn = js_NewXMLQName(cx, uri, prefix, name);
945 if (!qn)
946 return JS_FALSE;
947 if (!JS_SetPrivate(cx, obj, qn))
948 return JS_FALSE;
949 qn->object = obj;
950 return JS_TRUE;
951 }
953 static JSBool
954 AttributeName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
955 jsval *rval)
956 {
957 /*
958 * Since js_AttributeNameClass was initialized, obj will have that as its
959 * class, not js_QNameClass.
960 */
961 return QName(cx, obj, argc, argv, rval);
962 }
964 /*
965 * XMLArray library functions.
966 */
967 static JSBool
968 namespace_identity(const void *a, const void *b)
969 {
970 const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
971 const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
973 if (nsa->prefix && nsb->prefix) {
974 if (js_CompareStrings(nsa->prefix, nsb->prefix))
975 return JS_FALSE;
976 } else {
977 if (nsa->prefix || nsb->prefix)
978 return JS_FALSE;
979 }
980 return !js_CompareStrings(nsa->uri, nsb->uri);
981 }
983 static JSBool
984 attr_identity(const void *a, const void *b)
985 {
986 const JSXML *xmla = (const JSXML *) a;
987 const JSXML *xmlb = (const JSXML *) b;
989 return qname_identity(xmla->name, xmlb->name);
990 }
992 static void
993 XMLArrayCursorInit(JSXMLArrayCursor *cursor, JSXMLArray *array)
994 {
995 JSXMLArrayCursor *next;
997 cursor->array = array;
998 cursor->index = 0;
999 next = cursor->next = array->cursors;
1000 if (next)
1001 next->prevp = &cursor->next;
1002 cursor->prevp = &array->cursors;
1003 array->cursors = cursor;
1004 cursor->root = NULL;
1005 }
1007 static void
1008 XMLArrayCursorFinish(JSXMLArrayCursor *cursor)
1009 {
1010 JSXMLArrayCursor *next;
1012 if (!cursor->array)
1013 return;
1014 next = cursor->next;
1015 if (next)
1016 next->prevp = cursor->prevp;
1017 *cursor->prevp = next;
1018 cursor->array = NULL;
1019 }
1021 static void *
1022 XMLArrayCursorNext(JSXMLArrayCursor *cursor)
1023 {
1024 JSXMLArray *array;
1026 array = cursor->array;
1027 if (!array || cursor->index >= array->length)
1028 return NULL;
1029 return cursor->root = array->vector[cursor->index++];
1030 }
1032 static void *
1033 XMLArrayCursorItem(JSXMLArrayCursor *cursor)
1034 {
1035 JSXMLArray *array;
1037 array = cursor->array;
1038 if (!array || cursor->index >= array->length)
1039 return NULL;
1040 return cursor->root = array->vector[cursor->index];
1041 }
1043 static void
1044 XMLArrayCursorMark(JSContext *cx, JSXMLArrayCursor *cursor)
1045 {
1046 while (cursor) {
1047 GC_MARK(cx, cursor->root, "cursor->root", NULL);
1048 cursor = cursor->next;
1049 }
1050 }
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 {
1056 void **vector;
1058 if (capacity == 0) {
1059 /* We could let realloc(p, 0) free this, but purify gets confused. */
1060 if (array->vector)
1061 free(array->vector);
1062 vector = NULL;
1063 } else {
1064 if ((size_t)capacity > ~(size_t)0 / sizeof(void *) ||
1065 !(vector = (void **)
1066 realloc(array->vector, capacity * sizeof(void *)))) {
1067 if (cx)
1068 JS_ReportOutOfMemory(cx);
1069 return JS_FALSE;
1070 }
1071 }
1072 array->capacity = JSXML_PRESET_CAPACITY | capacity;
1073 array->vector = vector;
1074 return JS_TRUE;
1075 }
1077 static void
1078 XMLArrayTrim(JSXMLArray *array)
1079 {
1080 if (array->capacity & JSXML_PRESET_CAPACITY)
1081 return;
1082 if (array->length < array->capacity)
1083 XMLArraySetCapacity(NULL, array, array->length);
1084 }
1086 static JSBool
1087 XMLArrayInit(JSContext *cx, JSXMLArray *array, uint32 capacity)
1088 {
1089 array->length = array->capacity = 0;
1090 array->vector = NULL;
1091 array->cursors = NULL;
1092 return capacity == 0 || XMLArraySetCapacity(cx, array, capacity);
1093 }
1095 static void
1096 XMLArrayFinish(JSContext *cx, JSXMLArray *array)
1097 {
1098 JSXMLArrayCursor *cursor;
1100 JS_free(cx, array->vector);
1102 while ((cursor = array->cursors) != NULL)
1103 XMLArrayCursorFinish(cursor);
1105 #ifdef DEBUG
1106 memset(array, 0xd5, sizeof *array);
1107 #endif
1108 }
1110 #define XML_NOT_FOUND ((uint32) -1)
1112 static uint32
1113 XMLArrayFindMember(const JSXMLArray *array, void *elt, JSIdentityOp identity)
1114 {
1115 void **vector;
1116 uint32 i, n;
1118 /* The identity op must not reallocate array->vector. */
1119 vector = array->vector;
1120 if (identity) {
1121 for (i = 0, n = array->length; i < n; i++) {
1122 if (identity(vector[i], elt))
1123 return i;
1124 }
1125 } else {
1126 for (i = 0, n = array->length; i < n; i++) {
1127 if (vector[i] == elt)
1128 return i;
1129 }
1130 }
1131 return XML_NOT_FOUND;
1132 }
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
1142 static JSBool
1143 XMLArrayAddMember(JSContext *cx, JSXMLArray *array, uint32 index, void *elt)
1144 {
1145 uint32 capacity, i;
1146 int log2;
1147 void **vector;
1149 if (index >= array->length) {
1150 if (index >= JSXML_CAPACITY(array)) {
1151 /* Arrange to clear JSXML_PRESET_CAPACITY from array->capacity. */
1152 capacity = index + 1;
1153 if (index >= LINEAR_THRESHOLD) {
1154 capacity = JS_ROUNDUP(capacity, LINEAR_INCREMENT);
1155 } else {
1156 JS_CEILING_LOG2(log2, capacity);
1157 capacity = JS_BIT(log2);
1158 }
1159 if ((size_t)capacity > ~(size_t)0 / sizeof(void *) ||
1160 !(vector = (void **)
1161 realloc(array->vector, capacity * sizeof(void *)))) {
1162 JS_ReportOutOfMemory(cx);
1163 return JS_FALSE;
1164 }
1165 array->capacity = capacity;
1166 array->vector = vector;
1167 for (i = array->length; i < index; i++)
1168 vector[i] = NULL;
1169 }
1170 array->length = index + 1;
1171 }
1173 array->vector[index] = elt;
1174 return JS_TRUE;
1175 }
1177 static JSBool
1178 XMLArrayInsert(JSContext *cx, JSXMLArray *array, uint32 i, uint32 n)
1179 {
1180 uint32 j;
1181 JSXMLArrayCursor *cursor;
1183 j = array->length;
1184 JS_ASSERT(i <= j);
1185 if (!XMLArraySetCapacity(cx, array, j + n))
1186 return JS_FALSE;
1188 array->length = j + n;
1189 JS_ASSERT(n != (uint32)-1);
1190 while (j != i) {
1191 --j;
1192 array->vector[j + n] = array->vector[j];
1193 }
1195 for (cursor = array->cursors; cursor; cursor = cursor->next) {
1196 if (cursor->index > i)
1197 cursor->index += n;
1198 }
1199 return JS_TRUE;
1200 }
1202 static void *
1203 XMLArrayDelete(JSContext *cx, JSXMLArray *array, uint32 index, JSBool compress)
1204 {
1205 uint32 length;
1206 void **vector, *elt;
1207 JSXMLArrayCursor *cursor;
1209 length = array->length;
1210 if (index >= length)
1211 return NULL;
1213 vector = array->vector;
1214 elt = vector[index];
1215 if (compress) {
1216 while (++index < length)
1217 vector[index-1] = vector[index];
1218 array->length = length - 1;
1219 array->capacity = JSXML_CAPACITY(array);
1220 } else {
1221 vector[index] = NULL;
1222 }
1224 for (cursor = array->cursors; cursor; cursor = cursor->next) {
1225 if (cursor->index > index)
1226 --cursor->index;
1227 }
1228 return elt;
1229 }
1231 static void
1232 XMLArrayTruncate(JSContext *cx, JSXMLArray *array, uint32 length)
1233 {
1234 void **vector;
1236 JS_ASSERT(!array->cursors);
1237 if (length >= array->length)
1238 return;
1240 if (length == 0) {
1241 if (array->vector)
1242 free(array->vector);
1243 vector = NULL;
1244 } else {
1245 vector = realloc(array->vector, length * sizeof(void *));
1246 if (!vector)
1247 return;
1248 }
1250 if (array->length > length)
1251 array->length = length;
1252 array->capacity = length;
1253 array->vector = vector;
1254 }
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)
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";
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 };
1298 static JSBool
1299 xml_setting_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
1300 {
1301 return JS_TRUE;
1302 }
1304 static JSBool
1305 xml_setting_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
1306 {
1307 JSBool b;
1308 uint8 flag;
1310 JS_ASSERT(JSVAL_IS_INT(id));
1311 if (!js_ValueToBoolean(cx, *vp, &b))
1312 return JS_FALSE;
1314 flag = JS_BIT(JSVAL_TO_INT(id));
1315 if (b)
1316 cx->xmlSettingFlags |= flag;
1317 else
1318 cx->xmlSettingFlags &= ~flag;
1319 return JS_TRUE;
1320 }
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 };
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)
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)
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)))
1364 #define IS_XMLNS(str) \
1365 (JSSTRING_LENGTH(str) == 5 && IS_XMLNS_CHARS(JSSTRING_CHARS(str)))
1367 #define IS_XML_CHARS(chars) \
1368 (JS_TOLOWER((chars)[0]) == 'x' && \
1369 JS_TOLOWER((chars)[1]) == 'm' && \
1370 JS_TOLOWER((chars)[2]) == 'l')
1372 #define HAS_NS_AFTER_XML(chars) \
1373 (JS_TOLOWER((chars)[3]) == 'n' && \
1374 JS_TOLOWER((chars)[4]) == 's')
1376 #define IS_XMLNS_CHARS(chars) \
1377 (IS_XML_CHARS(chars) && HAS_NS_AFTER_XML(chars))
1379 #define STARTS_WITH_XML(chars,length) \
1380 (length >= 3 && IS_XML_CHARS(chars))
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/";
1385 static JSXMLQName *
1386 ParseNodeToQName(JSContext *cx, JSParseNode *pn, JSXMLArray *inScopeNSes,
1387 JSBool isAttributeName)
1388 {
1389 JSString *str, *uri, *prefix, *localName;
1390 size_t length, offset;
1391 const jschar *start, *limit, *colon;
1392 uint32 n;
1393 JSXMLNamespace *ns;
1395 JS_ASSERT(pn->pn_arity == PN_NULLARY);
1396 str = ATOM_TO_STRING(pn->pn_atom);
1397 length = JSSTRING_LENGTH(str);
1398 start = JSSTRING_CHARS(str);
1399 JS_ASSERT(length != 0 && *start != '@');
1400 JS_ASSERT(length != 1 || *start != '*');
1402 uri = cx->runtime->emptyString;
1403 limit = start + length;
1404 colon = js_strchr_limit(start, ':', limit);
1405 if (colon) {
1406 offset = PTRDIFF(colon, start, jschar);
1407 prefix = js_NewDependentString(cx, str, 0, offset, 0);
1408 if (!prefix)
1409 return NULL;
1411 if (STARTS_WITH_XML(start, offset)) {
1412 if (offset == 3) {
1413 uri = JS_InternString(cx, xml_namespace_str);
1414 if (!uri)
1415 return NULL;
1416 } else if (offset == 5 && HAS_NS_AFTER_XML(start)) {
1417 uri = JS_InternString(cx, xmlns_namespace_str);
1418 if (!uri)
1419 return NULL;
1420 } else {
1421 uri = NULL;
1422 }
1423 } else {
1424 uri = NULL;
1425 n = inScopeNSes->length;
1426 while (n != 0) {
1427 --n;
1428 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSXMLNamespace);
1429 if (ns->prefix && !js_CompareStrings(ns->prefix, prefix)) {
1430 uri = ns->uri;
1431 break;
1432 }
1433 }
1434 }
1436 if (!uri) {
1437 js_ReportCompileErrorNumber(cx, pn,
1438 JSREPORT_PN | JSREPORT_ERROR,
1439 JSMSG_BAD_XML_NAMESPACE,
1440 js_ValueToPrintableString(cx,
1441 STRING_TO_JSVAL(prefix)));
1442 return NULL;
1443 }
1445 localName = js_NewStringCopyN(cx, colon + 1, length - (offset + 1), 0);
1446 if (!localName)
1447 return NULL;
1448 } else {
1449 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 prefix = uri;
1455 } else {
1456 /*
1457 * Loop from back to front looking for the closest declared default
1458 * namespace.
1459 */
1460 n = inScopeNSes->length;
1461 while (n != 0) {
1462 --n;
1463 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSXMLNamespace);
1464 if (!ns->prefix || IS_EMPTY(ns->prefix)) {
1465 uri = ns->uri;
1466 break;
1467 }
1468 }
1469 prefix = NULL;
1470 }
1471 localName = str;
1472 }
1474 return js_NewXMLQName(cx, uri, prefix, localName);
1475 }
1477 static JSString *
1478 ChompXMLWhitespace(JSContext *cx, JSString *str)
1479 {
1480 size_t length, newlength, offset;
1481 const jschar *cp, *start, *end;
1482 jschar c;
1484 length = JSSTRING_LENGTH(str);
1485 for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
1486 c = *cp;
1487 if (!JS_ISXMLSPACE(c))
1488 break;
1489 }
1490 while (end > cp) {
1491 c = end[-1];
1492 if (!JS_ISXMLSPACE(c))
1493 break;
1494 --end;
1495 }
1496 newlength = PTRDIFF(end, cp, jschar);
1497 if (newlength == length)
1498 return str;
1499 offset = PTRDIFF(cp, start, jschar);
1500 return js_NewDependentString(cx, str, offset, newlength, 0);
1501 }
1503 static JSXML *
1504 ParseNodeToXML(JSContext *cx, JSParseNode *pn, JSXMLArray *inScopeNSes,
1505 uintN flags)
1506 {
1507 JSXML *xml, *kid, *attr, *attrj;
1508 JSString *str;
1509 uint32 length, n, i, j;
1510 JSParseNode *pn2, *pn3, *head, **pnp;
1511 JSXMLNamespace *ns;
1512 JSXMLQName *qn, *attrjqn;
1513 JSXMLClass xml_class;
1515 #define PN2X_SKIP_CHILD ((JSXML *) 1)
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 xml = NULL;
1523 if (!JS_EnterLocalRootScope(cx))
1524 return NULL;
1525 switch (pn->pn_type) {
1526 case TOK_XMLELEM:
1527 length = inScopeNSes->length;
1528 pn2 = pn->pn_head;
1529 xml = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1530 if (!xml)
1531 goto fail;
1532 if (js_PushLocalRoot(cx, cx->localRootStack, (jsval)xml) < 0)
1533 goto fail;
1535 flags &= ~XSF_PRECOMPILED_ROOT;
1536 n = pn->pn_count;
1537 JS_ASSERT(n >= 2);
1538 n -= 2;
1539 if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
1540 goto fail;
1542 i = 0;
1543 while ((pn2 = pn2->pn_next) != NULL) {
1544 if (!pn2->pn_next) {
1545 /* Don't append the end tag! */
1546 JS_ASSERT(pn2->pn_type == TOK_XMLETAGO);
1547 break;
1548 }
1550 if ((flags & XSF_IGNORE_WHITESPACE) &&
1551 n > 1 && pn2->pn_type == TOK_XMLSPACE) {
1552 --n;
1553 continue;
1554 }
1556 kid = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1557 if (kid == PN2X_SKIP_CHILD) {
1558 --n;
1559 continue;
1560 }
1562 if (!kid)
1563 goto fail;
1565 /* Store kid in xml right away, to protect it from GC. */
1566 XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1567 kid->parent = xml;
1568 ++i;
1570 /* XXX where is this documented in an XML spec, or in E4X? */
1571 if ((flags & XSF_IGNORE_WHITESPACE) &&
1572 n > 1 && kid->xml_class == JSXML_CLASS_TEXT) {
1573 str = ChompXMLWhitespace(cx, kid->xml_value);
1574 if (!str)
1575 goto fail;
1576 kid->xml_value = str;
1577 }
1578 }
1580 JS_ASSERT(i == n);
1581 if (n < pn->pn_count - 2)
1582 XMLArrayTrim(&xml->xml_kids);
1583 XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1584 break;
1586 case TOK_XMLLIST:
1587 xml = js_NewXML(cx, JSXML_CLASS_LIST);
1588 if (!xml)
1589 goto fail;
1591 n = pn->pn_count;
1592 if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
1593 goto fail;
1595 i = 0;
1596 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 if (pn2->pn_type == TOK_XMLSPACE) {
1603 --n;
1604 continue;
1605 }
1607 kid = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1608 if (kid == PN2X_SKIP_CHILD) {
1609 --n;
1610 continue;
1611 }
1613 if (!kid)
1614 goto fail;
1616 XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1617 ++i;
1618 }
1620 if (n < pn->pn_count)
1621 XMLArrayTrim(&xml->xml_kids);
1622 break;
1624 case TOK_XMLSTAGO:
1625 case TOK_XMLPTAGC:
1626 length = inScopeNSes->length;
1627 pn2 = pn->pn_head;
1628 JS_ASSERT(pn2->pn_type == TOK_XMLNAME);
1629 if (pn2->pn_arity == PN_LIST)
1630 goto syntax;
1632 xml = js_NewXML(cx, JSXML_CLASS_ELEMENT);
1633 if (!xml)
1634 goto fail;
1636 /* First pass: check syntax and process namespace declarations. */
1637 JS_ASSERT(pn->pn_count >= 1);
1638 n = pn->pn_count - 1;
1639 pnp = &pn2->pn_next;
1640 head = *pnp;
1641 while ((pn2 = *pnp) != NULL) {
1642 size_t length;
1643 const jschar *chars;
1645 if (pn2->pn_type != TOK_XMLNAME || pn2->pn_arity != PN_NULLARY)
1646 goto syntax;
1648 /* Enforce "Well-formedness constraint: Unique Att Spec". */
1649 for (pn3 = head; pn3 != pn2; pn3 = pn3->pn_next->pn_next) {
1650 if (pn3->pn_atom == pn2->pn_atom) {
1651 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 goto fail;
1657 }
1658 }
1660 str = ATOM_TO_STRING(pn2->pn_atom);
1661 pn2 = pn2->pn_next;
1662 JS_ASSERT(pn2);
1663 if (pn2->pn_type != TOK_XMLATTR)
1664 goto syntax;
1666 length = JSSTRING_LENGTH(str);
1667 chars = JSSTRING_CHARS(str);
1668 if (length >= 5 &&
1669 IS_XMLNS_CHARS(chars) &&
1670 (length == 5 || chars[5] == ':')) {
1671 JSString *uri, *prefix;
1673 uri = ATOM_TO_STRING(pn2->pn_atom);
1674 if (length == 5) {
1675 /* 10.3.2.1. Step 6(h)(i)(1)(a). */
1676 prefix = cx->runtime->emptyString;
1677 } else {
1678 prefix = js_NewStringCopyN(cx, chars + 6, length - 6, 0);
1679 if (!prefix)
1680 goto fail;
1681 }
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 ns = js_NewXMLNamespace(cx, prefix, uri, JS_TRUE);
1690 if (!ns)
1691 goto fail;
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 if (!XMLARRAY_HAS_MEMBER(inScopeNSes, ns, namespace_identity)) {
1701 if (!XMLARRAY_APPEND(cx, inScopeNSes, ns) ||
1702 !XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns)) {
1703 goto fail;
1704 }
1705 }
1707 JS_ASSERT(n >= 2);
1708 n -= 2;
1709 *pnp = pn2->pn_next;
1710 /* XXXbe recycle pn2 */
1711 continue;
1712 }
1714 pnp = &pn2->pn_next;
1715 }
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 if (flags & XSF_PRECOMPILED_ROOT) {
1723 JS_ASSERT(length >= 1);
1724 ns = XMLARRAY_MEMBER(inScopeNSes, 0, JSXMLNamespace);
1725 JS_ASSERT(!XMLARRAY_HAS_MEMBER(&xml->xml_namespaces, ns,
1726 namespace_identity));
1727 ns = js_NewXMLNamespace(cx, ns->prefix, ns->uri, JS_FALSE);
1728 if (!ns)
1729 goto fail;
1730 if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
1731 goto fail;
1732 }
1733 XMLArrayTrim(&xml->xml_namespaces);
1735 /* Second pass: process tag name and attributes, using namespaces. */
1736 pn2 = pn->pn_head;
1737 qn = ParseNodeToQName(cx, pn2, inScopeNSes, JS_FALSE);
1738 if (!qn)
1739 goto fail;
1740 xml->name = qn;
1742 JS_ASSERT((n & 1) == 0);
1743 n >>= 1;
1744 if (!XMLArraySetCapacity(cx, &xml->xml_attrs, n))
1745 goto fail;
1747 for (i = 0; (pn2 = pn2->pn_next) != NULL; i++) {
1748 qn = ParseNodeToQName(cx, pn2, inScopeNSes, JS_TRUE);
1749 if (!qn) {
1750 xml->xml_attrs.length = i;
1751 goto fail;
1752 }
1754 /*
1755 * Enforce "Well-formedness constraint: Unique Att Spec", part 2:
1756 * this time checking local name and namespace URI.
1757 */
1758 for (j = 0; j < i; j++) {
1759 attrj = XMLARRAY_MEMBER(&xml->xml_attrs, j, JSXML);
1760 attrjqn = attrj->name;
1761 if (!js_CompareStrings(attrjqn->uri, qn->uri) &&
1762 !js_CompareStrings(attrjqn->localName, qn->localName)) {
1763 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 goto fail;
1769 }
1770 }
1772 pn2 = pn2->pn_next;
1773 JS_ASSERT(pn2);
1774 JS_ASSERT(pn2->pn_type == TOK_XMLATTR);
1776 attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
1777 if (!attr)
1778 goto fail;
1780 XMLARRAY_SET_MEMBER(&xml->xml_attrs, i, attr);
1781 attr->parent = xml;
1782 attr->name = qn;
1783 attr->xml_value = ATOM_TO_STRING(pn2->pn_atom);
1784 }
1786 /* Point tag closes its own namespace scope. */
1787 if (pn->pn_type == TOK_XMLPTAGC)
1788 XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1789 break;
1791 case TOK_XMLSPACE:
1792 case TOK_XMLTEXT:
1793 case TOK_XMLCDATA:
1794 case TOK_XMLCOMMENT:
1795 case TOK_XMLPI:
1796 str = ATOM_TO_STRING(pn->pn_atom);
1797 qn = NULL;
1798 if (pn->pn_type == TOK_XMLCOMMENT) {
1799 if (flags & XSF_IGNORE_COMMENTS)
1800 goto skip_child;
1801 xml_class = JSXML_CLASS_COMMENT;
1802 } else if (pn->pn_type == TOK_XMLPI) {
1803 if (IS_XML(str)) {
1804 js_ReportCompileErrorNumber(cx, pn,
1805 JSREPORT_PN | JSREPORT_ERROR,
1806 JSMSG_RESERVED_ID,
1807 js_ValueToPrintableString(cx,
1808 STRING_TO_JSVAL(str)));
1809 goto fail;
1810 }
1812 if (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS)
1813 goto skip_child;
1815 qn = ParseNodeToQName(cx, pn, inScopeNSes, JS_FALSE);
1816 if (!qn)
1817 goto fail;
1819 str = pn->pn_atom2
1820 ? ATOM_TO_STRING(pn->pn_atom2)
1821 : cx->runtime->emptyString;
1822 xml_class = JSXML_CLASS_PROCESSING_INSTRUCTION;
1823 } else {
1824 /* CDATA section content, or element text. */
1825 xml_class = JSXML_CLASS_TEXT;
1826 }
1828 xml = js_NewXML(cx, xml_class);
1829 if (!xml)
1830 goto fail;
1831 xml->name = qn;
1832 if (pn->pn_type == TOK_XMLSPACE)
1833 xml->xml_flags |= XMLF_WHITESPACE_TEXT;
1834 xml->xml_value = str;
1835 break;
1837 default:
1838 goto syntax;
1839 }
1841 JS_LeaveLocalRootScope(cx);
1842 if ((flags & XSF_PRECOMPILED_ROOT) && !js_GetXMLObject(cx, xml))
1843 return NULL;
1844 return xml;
1846 skip_child:
1847 js_LeaveLocalRootScope(cx);
1848 return PN2X_SKIP_CHILD;
1850 #undef PN2X_SKIP_CHILD
1852 syntax:
1853 js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR,
1854 JSMSG_BAD_XML_MARKUP);
1855 fail:
1856 JS_LeaveLocalRootScope(cx);
1857 return NULL;
1858 }
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 {
1867 jsval v;
1869 if (!js_FindConstructor(cx, NULL, js_XML_str, &v))
1870 return JS_FALSE;
1871 if (!JSVAL_IS_FUNCTION(cx, v)) {
1872 *vp = JSVAL_VOID;
1873 return JS_TRUE;
1874 }
1875 return JS_GetProperty(cx, JSVAL_TO_OBJECT(v), name, vp);
1876 }
1878 static JSBool
1879 FillSettingsCache(JSContext *cx)
1880 {
1881 int i;
1882 const char *name;
1883 jsval v;
1884 JSBool isSet;
1886 /* Note: XML_PRETTY_INDENT is not a boolean setting. */
1887 for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
1888 name = xml_static_props[i].name;
1889 if (!GetXMLSetting(cx, name, &v) || !js_ValueToBoolean(cx, v, &isSet))
1890 return JS_FALSE;
1891 if (isSet)
1892 cx->xmlSettingFlags |= JS_BIT(i);
1893 else
1894 cx->xmlSettingFlags &= ~JS_BIT(i);
1895 }
1897 cx->xmlSettingFlags |= XSF_CACHE_VALID;
1898 return JS_TRUE;
1899 }
1901 static JSBool
1902 GetBooleanXMLSetting(JSContext *cx, const char *name, JSBool *bp)
1903 {
1904 int i;
1906 if (!(cx->xmlSettingFlags & XSF_CACHE_VALID) && !FillSettingsCache(cx))
1907 return JS_FALSE;
1909 for (i = 0; xml_static_props[i].name; i++) {
1910 if (!strcmp(xml_static_props[i].name, name)) {
1911 *bp = (cx->xmlSettingFlags & JS_BIT(i)) != 0;
1912 return JS_TRUE;
1913 }
1914 }
1915 *bp = JS_FALSE;
1916 return JS_TRUE;
1917 }
1919 static JSBool
1920 GetUint32XMLSetting(JSContext *cx, const char *name, uint32 *uip)
1921 {
1922 jsval v;
1924 return GetXMLSetting(cx, name, &v) && js_ValueToECMAUint32(cx, v, uip);
1925 }
1927 static JSBool
1928 GetXMLSettingFlags(JSContext *cx, uintN *flagsp)
1929 {
1930 JSBool flag;
1932 /* Just get the first flag to validate the setting flags cache. */
1933 if (!GetBooleanXMLSetting(cx, js_ignoreComments_str, &flag))
1934 return JS_FALSE;
1935 *flagsp = cx->xmlSettingFlags;
1936 return JS_TRUE;
1937 }
1939 static JSXML *
1940 ParseXMLSource(JSContext *cx, JSString *src)
1941 {
1942 jsval nsval;
1943 JSXMLNamespace *ns;
1944 size_t urilen, srclen, length, offset, dstlen;
1945 jschar *chars;
1946 const jschar *srcp, *endp;
1947 void *mark;
1948 JSTokenStream *ts;
1949 uintN lineno;
1950 JSStackFrame *fp;
1951 JSOp op;
1952 JSParseNode *pn;
1953 JSXML *xml;
1954 JSXMLArray nsarray;
1955 uintN flags;
1957 static const char prefix[] = "<parent xmlns='";
1958 static const char middle[] = "'>";
1959 static const char suffix[] = "</parent>";
1961 #define constrlen(constr) (sizeof(constr) - 1)
1963 if (!js_GetDefaultXMLNamespace(cx, &nsval))
1964 return NULL;
1965 ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
1967 urilen = JSSTRING_LENGTH(ns->uri);
1968 srclen = JSSTRING_LENGTH(src);
1969 length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
1970 constrlen(suffix);
1972 chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
1973 if (!chars)
1974 return NULL;
1976 dstlen = length;
1977 js_InflateStringToBuffer(cx, prefix, constrlen(prefix), chars, &dstlen);
1978 offset = dstlen;
1979 js_strncpy(chars + offset, JSSTRING_CHARS(ns->uri), urilen);
1980 offset += urilen;
1981 dstlen = length - offset + 1;
1982 js_InflateStringToBuffer(cx, middle, constrlen(middle), chars + offset, &dstlen);
1983 offset += dstlen;
1984 srcp = JSSTRING_CHARS(src);
1985 js_strncpy(chars + offset, srcp, srclen);
1986 offset += srclen;
1987 dstlen = length - offset + 1;
1988 js_InflateStringToBuffer(cx, suffix, constrlen(suffix), chars + offset, &dstlen);
1989 chars [offset + dstlen] = 0;
1991 mark = JS_ARENA_MARK(&cx->tempPool);
1992 ts = js_NewBufferTokenStream(cx, chars, length);
1993 if (!ts)
1994 return NULL;
1995 for (fp = cx->fp; fp && !fp->pc; fp = fp->down)
1996 continue;
1997 if (fp) {
1998 op = (JSOp) *fp->pc;
1999 if (op == JSOP_TOXML || op == JSOP_TOXMLLIST) {
2000 ts->filename = fp->script->filename;
2001 lineno = js_PCToLineNumber(cx, fp->script, fp->pc);
2002 for (endp = srcp + srclen; srcp < endp; srcp++)
2003 if (*srcp == '\n')
2004 --lineno;
2005 ts->lineno = lineno;
2006 }
2007 }
2009 JS_KEEP_ATOMS(cx->runtime);
2010 pn = js_ParseXMLTokenStream(cx, cx->fp->scopeChain, ts, JS_FALSE);
2011 xml = NULL;
2012 if (pn && XMLArrayInit(cx, &nsarray, 1)) {
2013 if (GetXMLSettingFlags(cx, &flags))
2014 xml = ParseNodeToXML(cx, pn, &nsarray, flags);
2016 XMLArrayFinish(cx, &nsarray);
2017 }
2018 JS_UNKEEP_ATOMS(cx->runtime);
2020 JS_ARENA_RELEASE(&cx->tempPool, mark);
2021 JS_free(cx, chars);
2022 return xml;
2024 #undef constrlen
2025 }
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 {
2049 JSXMLNamespace *ns;
2051 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, 0, JSXMLNamespace);
2052 xml = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
2053 if (!ns || !xml)
2054 return xml;
2055 if (xml->xml_class == JSXML_CLASS_ELEMENT) {
2056 if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
2057 return NULL;
2058 ns->declared = JS_FALSE;
2059 }
2060 xml->parent = NULL;
2061 return xml;
2062 }
2064 static JSObject *
2065 ToXML(JSContext *cx, jsval v)
2066 {
2067 JSObject *obj;
2068 JSXML *xml;
2069 JSClass *clasp;
2070 JSString *str;
2071 uint32 length;
2073 if (JSVAL_IS_PRIMITIVE(v)) {
2074 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
2075 goto bad;
2076 } else {
2077 obj = JSVAL_TO_OBJECT(v);
2078 if (OBJECT_IS_XML(cx, obj)) {
2079 xml = (JSXML *) JS_GetPrivate(cx, obj);
2080 if (xml->xml_class == JSXML_CLASS_LIST) {
2081 if (xml->xml_kids.length != 1)
2082 goto bad;
2083 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
2084 if (xml) {
2085 JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
2086 return js_GetXMLObject(cx, xml);
2087 }
2088 }
2089 return obj;
2090 }
2092 clasp = OBJ_GET_CLASS(cx, obj);
2093 if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
2094 JS_ASSERT(0);
2095 }
2097 if (clasp != &js_StringClass &&
2098 clasp != &js_NumberClass &&
2099 clasp != &js_BooleanClass) {
2100 goto bad;
2101 }
2102 }
2104 str = js_ValueToString(cx, v);
2105 if (!str)
2106 return NULL;
2107 if (IS_EMPTY(str)) {
2108 length = 0;
2109 #ifdef __GNUC__ /* suppress bogus gcc warnings */
2110 xml = NULL;
2111 #endif
2112 } else {
2113 xml = ParseXMLSource(cx, str);
2114 if (!xml)
2115 return NULL;
2116 length = JSXML_LENGTH(xml);
2117 }
2119 if (length == 0) {
2120 obj = js_NewXMLObject(cx, JSXML_CLASS_TEXT);
2121 if (!obj)
2122 return NULL;
2123 } else if (length == 1) {
2124 xml = OrphanXMLChild(cx, xml, 0);
2125 if (!xml)
2126 return NULL;
2127 obj = js_GetXMLObject(cx, xml);
2128 if (!obj)
2129 return NULL;
2130 } else {
2131 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SYNTAX_ERROR);
2132 return NULL;
2133 }
2134 return obj;
2136 bad:
2137 str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
2138 if (str) {
2139 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2140 JSMSG_BAD_XML_CONVERSION,
2141 JS_GetStringBytes(str));
2142 }
2143 return NULL;
2144 }
2146 static JSBool
2147 Append(JSContext *cx, JSXML *list, JSXML *kid);
2149 static JSObject *
2150 ToXMLList(JSContext *cx, jsval v)
2151 {
2152 JSObject *obj, *listobj;
2153 JSXML *xml, *list, *kid;
2154 JSClass *clasp;
2155 JSString *str;
2156 uint32 i, length;
2158 if (JSVAL_IS_PRIMITIVE(v)) {
2159 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
2160 goto bad;
2161 } else {
2162 obj = JSVAL_TO_OBJECT(v);
2163 if (OBJECT_IS_XML(cx, obj)) {
2164 xml = (JSXML *) JS_GetPrivate(cx, obj);
2165 if (xml->xml_class != JSXML_CLASS_LIST) {
2166 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2167 if (!listobj)
2168 return NULL;
2169 list = (JSXML *) JS_GetPrivate(cx, listobj);
2170 if (!Append(cx, list, xml))
2171 return NULL;
2172 return listobj;
2173 }
2174 return obj;
2175 }
2177 clasp = OBJ_GET_CLASS(cx, obj);
2178 if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
2179 JS_ASSERT(0);
2180 }
2182 if (clasp != &js_StringClass &&
2183 clasp != &js_NumberClass &&
2184 clasp != &js_BooleanClass) {
2185 goto bad;
2186 }
2187 }
2189 str = js_ValueToString(cx, v);
2190 if (!str)
2191 return NULL;
2192 if (IS_EMPTY(str)) {
2193 xml = NULL;
2194 length = 0;
2195 } else {
2196 if (!JS_EnterLocalRootScope(cx))
2197 return NULL;
2198 xml = ParseXMLSource(cx, str);
2199 if (!xml) {
2200 JS_LeaveLocalRootScope(cx);
2201 return NULL;
2202 }
2203 length = JSXML_LENGTH(xml);
2204 }
2206 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2207 if (listobj) {
2208 list = (JSXML *) JS_GetPrivate(cx, listobj);
2209 for (i = 0; i < length; i++) {
2210 kid = OrphanXMLChild(cx, xml, i);
2211 if (!kid)
2212 return NULL;
2213 if (!Append(cx, list, kid)) {
2214 listobj = NULL;
2215 break;
2216 }
2217 }
2218 }
2220 if (xml)
2221 JS_LeaveLocalRootScope(cx);
2222 return listobj;
2224 bad:
2225 str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
2226 if (str) {
2227 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2228 JSMSG_BAD_XMLLIST_CONVERSION,
2229 JS_GetStringBytes(str));
2230 }
2231 return NULL;
2232 }
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 {
2249 JSStringBuffer localSB;
2250 size_t length, length2, newlength;
2251 jschar *bp, *base;
2253 if (!sb) {
2254 sb = &localSB;
2255 js_InitStringBuffer(sb);
2256 }
2258 length = JSSTRING_LENGTH(str);
2259 length2 = str2 ? JSSTRING_LENGTH(str2) : 0;
2260 newlength = STRING_BUFFER_OFFSET(sb) +
2261 prefixlength + length + ((length2 != 0) ? 1 + length2 : 0) +
2262 suffixlength;
2263 bp = base = (jschar *)
2264 JS_realloc(cx, sb->base, (newlength + 1) * sizeof(jschar));
2265 if (!bp) {
2266 js_FinishStringBuffer(sb);
2267 return NULL;
2268 }
2270 bp += STRING_BUFFER_OFFSET(sb);
2271 js_strncpy(bp, prefix, prefixlength);
2272 bp += prefixlength;
2273 js_strncpy(bp, JSSTRING_CHARS(str), length);
2274 bp += length;
2275 if (length2 != 0) {
2276 *bp++ = (jschar) ' ';
2277 js_strncpy(bp, JSSTRING_CHARS(str2), length2);
2278 bp += length2;
2279 }
2280 js_strncpy(bp, suffix, suffixlength);
2281 bp[suffixlength] = 0;
2283 str = js_NewString(cx, base, newlength, 0);
2284 if (!str)
2285 free(base);
2286 return str;
2287 }
2289 static JSString *
2290 MakeXMLCDATAString(JSContext *cx, JSStringBuffer *sb, JSString *str)
2291 {
2292 static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
2293 'C', 'D', 'A', 'T', 'A',
2294 '['};
2295 static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};
2297 return MakeXMLSpecialString(cx, sb, str, NULL,
2298 cdata_prefix_ucNstr, 9,
2299 cdata_suffix_ucNstr, 3);
2300 }
2302 static JSString *
2303 MakeXMLCommentString(JSContext *cx, JSStringBuffer *sb, JSString *str)
2304 {
2305 static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
2306 static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};
2308 return MakeXMLSpecialString(cx, sb, str, NULL,
2309 comment_prefix_ucNstr, 4,
2310 comment_suffix_ucNstr, 3);
2311 }
2313 static JSString *
2314 MakeXMLPIString(JSContext *cx, JSStringBuffer *sb, JSString *name,
2315 JSString *value)
2316 {
2317 static const jschar pi_prefix_ucNstr[] = {'<', '?'};
2318 static const jschar pi_suffix_ucNstr[] = {'?', '>'};
2320 return MakeXMLSpecialString(cx, sb, name, value,
2321 pi_prefix_ucNstr, 2,
2322 pi_suffix_ucNstr, 2);
2323 }
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 {
2332 js_AppendCString(sb, "=\"");
2333 valstr = js_EscapeAttributeValue(cx, valstr);
2334 if (!valstr) {
2335 free(sb->base);
2336 sb->base = STRING_BUFFER_ERROR_BASE;
2337 return;
2338 }
2339 js_AppendJSString(sb, valstr);
2340 js_AppendChar(sb, '"');
2341 }
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 {
2352 size_t length, newlength;
2353 const jschar *cp, *start, *end;
2354 jschar c;
2356 length = newlength = JSSTRING_LENGTH(str);
2357 for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
2358 c = *cp;
2359 if (c == '<' || c == '>')
2360 newlength += 3;
2361 else if (c == '&')
2362 newlength += 4;
2364 if (newlength < length) {
2365 JS_ReportOutOfMemory(cx);
2366 return NULL;
2367 }
2368 }
2369 if ((sb && STRING_BUFFER_OFFSET(sb) != 0) || newlength > length) {
2370 JSStringBuffer localSB;
2371 if (!sb) {
2372 sb = &localSB;
2373 js_InitStringBuffer(sb);
2374 }
2375 if (!sb->grow(sb, newlength)) {
2376 JS_ReportOutOfMemory(cx);
2377 return NULL;
2378 }
2379 for (cp = start; cp < end; cp++) {
2380 c = *cp;
2381 if (c == '<')
2382 js_AppendCString(sb, js_lt_entity_str);
2383 else if (c == '>')
2384 js_AppendCString(sb, js_gt_entity_str);
2385 else if (c == '&')
2386 js_AppendCString(sb, js_amp_entity_str);
2387 else
2388 js_AppendChar(sb, c);
2389 }
2390 JS_ASSERT(STRING_BUFFER_OK(sb));
2391 str = js_NewString(cx, sb->base, STRING_BUFFER_OFFSET(sb), 0);
2392 if (!str)
2393 js_FinishStringBuffer(sb);
2394 }
2395 return str;
2396 }
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 {
2405 size_t length, newlength;
2406 const jschar *cp, *start, *end;
2407 jschar c;
2409 length = newlength = JSSTRING_LENGTH(str);
2410 for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
2411 c = *cp;
2412 if (c == '"')
2413 newlength += 5;
2414 else if (c == '<')
2415 newlength += 3;
2416 else if (c == '&' || c == '\n' || c == '\r' || c == '\t')
2417 newlength += 4;
2419 if (newlength < length) {
2420 JS_ReportOutOfMemory(cx);
2421 return NULL;
2422 }
2423 }
2424 if ((sb && STRING_BUFFER_OFFSET(sb) != 0) || newlength > length) {
2425 JSStringBuffer localSB;
2426 if (!sb) {
2427 sb = &localSB;
2428 js_InitStringBuffer(sb);
2429 }
2430 if (!sb->grow(sb, newlength)) {
2431 JS_ReportOutOfMemory(cx);
2432 return NULL;
2433 }
2434 for (cp = start; cp < end; cp++) {
2435 c = *cp;
2436 if (c == '"')
2437 js_AppendCString(sb, js_quot_entity_str);
2438 else if (c == '<')
2439 js_AppendCString(sb, js_lt_entity_str);
2440 else if (c == '&')
2441 js_AppendCString(sb, js_amp_entity_str);
2442 else if (c == '\n')
2443 js_AppendCString(sb, "
");
2444 else if (c == '\r')
2445 js_AppendCString(sb, "
");
2446 else if (c == '\t')
2447 js_AppendCString(sb, "	");
2448 else
2449 js_AppendChar(sb, c);
2450 }
2451 JS_ASSERT(STRING_BUFFER_OK(sb));
2452 str = js_NewString(cx, sb->base, STRING_BUFFER_OFFSET(sb), 0);
2453 if (!str)
2454 js_FinishStringBuffer(sb);
2455 }
2456 return str;
2457 }
2459 /* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
2460 static JSXMLNamespace *
2461 GetNamespace(JSContext *cx, JSXMLQName *qn, const JSXMLArray *inScopeNSes)
2462 {
2463 JSXMLNamespace *match, *ns;
2464 uint32 i, n;
2465 jsval argv[2];
2466 JSObject *nsobj;
2468 JS_ASSERT(qn->uri);
2469 if (!qn->uri) {
2470 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 return NULL;
2477 }
2479 /* Look for a matching namespace in inScopeNSes, if provided. */
2480 match = NULL;
2481 if (inScopeNSes) {
2482 for (i = 0, n = inScopeNSes->length; i < n; i++) {
2483 ns = XMLARRAY_MEMBER(inScopeNSes, i, JSXMLNamespace);
2484 if (!ns)
2485 continue;
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 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 match = ns;
2520 break;
2521 }
2522 }
2523 }
2525 /* If we didn't match, make a new namespace from qn. */
2526 if (!match) {
2527 argv[0] = qn->prefix ? STRING_TO_JSVAL(qn->prefix) : JSVAL_VOID;
2528 argv[1] = STRING_TO_JSVAL(qn->uri);
2529 nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL,
2530 2, argv);
2531 if (!nsobj)
2532 return NULL;
2533 match = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
2534 }
2535 return match;
2536 }
2538 static JSString *
2539 GeneratePrefix(JSContext *cx, JSString *uri, JSXMLArray *decls)
2540 {
2541 const jschar *cp, *start, *end;
2542 size_t length, newlength, offset;
2543 uint32 i, n, m, serial;
2544 jschar *bp, *dp;
2545 JSBool done;
2546 JSXMLNamespace *ns;
2547 JSString *prefix;
2549 JS_ASSERT(!IS_EMPTY(uri));
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 start = JSSTRING_CHARS(uri);
2558 cp = end = start + JSSTRING_LENGTH(uri);
2559 while (--cp > start) {
2560 if (*cp == '.' || *cp == '/' || *cp == ':') {
2561 ++cp;
2562 if (IsXMLName(cp, PTRDIFF(end, cp, jschar)))
2563 break;
2564 end = --cp;
2565 }
2566 }
2567 length = PTRDIFF(end, cp, jschar);
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 serial = 0;
2574 bp = NULL;
2575 #ifdef __GNUC__ /* suppress bogus gcc warnings */
2576 newlength = 0;
2577 #endif
2578 do {
2579 done = JS_TRUE;
2580 for (i = 0, n = decls->length; i < n; i++) {
2581 ns = XMLARRAY_MEMBER(decls, i, JSXMLNamespace);
2582 if (ns && ns->prefix &&
2583 JSSTRING_LENGTH(ns->prefix) == length &&
2584 !memcmp(JSSTRING_CHARS(ns->prefix), cp,
2585 length * sizeof(jschar))) {
2586 if (!bp) {
2587 newlength = length + 2 + (size_t) log10(n);
2588 bp = (jschar *)
2589 JS_malloc(cx, (newlength + 1) * sizeof(jschar));
2590 if (!bp)
2591 return NULL;
2592 js_strncpy(bp, cp, length);
2593 }
2595 ++serial;
2596 JS_ASSERT(serial <= n);
2597 dp = bp + length + 2 + (size_t) log10(serial);
2598 *dp = 0;
2599 for (m = serial; m != 0; m /= 10)
2600 *--dp = (jschar)('0' + m % 10);
2601 *--dp = '-';
2602 JS_ASSERT(dp == bp + length);
2604 done = JS_FALSE;
2605 break;
2606 }
2607 }
2608 } while (!done);
2610 if (!bp) {
2611 offset = PTRDIFF(cp, start, jschar);
2612 prefix = js_NewDependentString(cx, uri, offset, length, 0);
2613 } else {
2614 prefix = js_NewString(cx, bp, newlength, 0);
2615 if (!prefix)
2616 JS_free(cx, bp);
2617 }
2618 return prefix;
2619 }
2621 static JSBool
2622 namespace_match(const void *a, const void *b)
2623 {
2624 const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
2625 const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
2627 if (nsb->prefix)
2628 return nsa->prefix && !js_CompareStrings(nsa->prefix, nsb->prefix);
2629 return !js_CompareStrings(nsa->uri, nsb->uri);
2630 }
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 {
2637 JSBool pretty, indentKids;
2638 JSStringBuffer sb;
2639 JSString *str, *prefix, *kidstr;
2640 JSXMLArrayCursor cursor;
2641 uint32 i, n;
2642 JSXMLArray empty, decls, ancdecls;
2643 JSXMLNamespace *ns, *ns2;
2644 uintN nextIndentLevel;
2645 JSXML *attr, *kid;
2647 if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
2648 return NULL;
2650 js_InitStringBuffer(&sb);
2651 if (pretty)
2652 js_RepeatChar(&sb, ' ', indentLevel);
2653 str = NULL;
2655 switch (xml->xml_class) {
2656 case JSXML_CLASS_TEXT:
2657 /* Step 4. */
2658 if (pretty) {
2659 str = ChompXMLWhitespace(cx, xml->xml_value);
2660 if (!str)
2661 return NULL;
2662 } else {
2663 str = xml->xml_value;
2664 }
2665 return EscapeElementValue(cx, &sb, str);
2667 case JSXML_CLASS_ATTRIBUTE:
2668 /* Step 5. */
2669 return EscapeAttributeValue(cx, &sb, xml->xml_value);
2671 case JSXML_CLASS_COMMENT:
2672 /* Step 6. */
2673 return MakeXMLCommentString(cx, &sb, xml->xml_value);
2675 case JSXML_CLASS_PROCESSING_INSTRUCTION:
2676 /* Step 7. */
2677 return MakeXMLPIString(cx, &sb, xml->name->localName, xml->xml_value);
2679 case JSXML_CLASS_LIST:
2680 /* ECMA-357 10.2.2. */
2681 XMLArrayCursorInit(&cursor, &xml->xml_kids);
2682 i = 0;
2683 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2684 if (pretty && i != 0)
2685 js_AppendChar(&sb, '\n');
2687 kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel);
2688 if (!kidstr)
2689 break;
2691 js_AppendJSString(&sb, kidstr);
2692 ++i;
2693 }
2694 XMLArrayCursorFinish(&cursor);
2695 if (kid)
2696 goto list_out;
2698 if (!sb.base) {
2699 if (!STRING_BUFFER_OK(&sb)) {
2700 JS_ReportOutOfMemory(cx);
2701 return NULL;
2702 }
2703 return cx->runtime->emptyString;
2704 }
2706 str = js_NewString(cx, sb.base, STRING_BUFFER_OFFSET(&sb), 0);
2707 list_out:
2708 if (!str)
2709 js_FinishStringBuffer(&sb);
2710 return str;
2712 default:;
2713 }
2715 /* After this point, control must flow through label out: to exit. */
2716 if (!JS_EnterLocalRootScope(cx))
2717 return NULL;
2719 /* ECMA-357 10.2.1 step 8 onward: handle ToXMLString on an XML element. */
2720 if (!ancestorNSes) {
2721 XMLArrayInit(cx, &empty, 0);
2722 ancestorNSes = ∅
2723 }
2724 XMLArrayInit(cx, &decls, 0);
2725 ancdecls.capacity = 0;
2727 /* Clone in-scope namespaces not in ancestorNSes into decls. */
2728 XMLArrayCursorInit(&cursor, &xml->xml_namespaces);
2729 while ((ns = (JSXMLNamespace *) XMLArrayCursorNext(&cursor)) != NULL) {
2730 if (!ns->declared)
2731 continue;
2732 if (!XMLARRAY_HAS_MEMBER(ancestorNSes, ns, namespace_identity)) {
2733 /* NOTE: may want to exclude unused namespaces here. */
2734 ns2 = js_NewXMLNamespace(cx, ns->prefix, ns->uri, JS_TRUE);
2735 if (!ns2 || !XMLARRAY_APPEND(cx, &decls, ns2))
2736 break;
2737 }
2738 }
2739 XMLArrayCursorFinish(&cursor);
2740 if (ns)
2741 goto out;
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 if (!XMLArrayInit(cx, &ancdecls, ancestorNSes->length + decls.length))
2749 goto out;
2750 for (i = 0, n = ancestorNSes->length; i < n; i++) {
2751 ns2 = XMLARRAY_MEMBER(ancestorNSes, i, JSXMLNamespace);
2752 if (!ns2)
2753 continue;
2754 JS_ASSERT(!XMLARRAY_HAS_MEMBER(&decls, ns2, namespace_identity));
2755 if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
2756 goto out;
2757 }
2758 for (i = 0, n = decls.length; i < n; i++) {
2759 ns2 = XMLARRAY_MEMBER(&decls, i, JSXMLNamespace);
2760 if (!ns2)
2761 continue;
2762 JS_ASSERT(!XMLARRAY_HAS_MEMBER(&ancdecls, ns2, namespace_identity));
2763 if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
2764 goto out;
2765 }
2767 /* Step 11, except we don't clone ns unless its prefix is undefined. */
2768 ns = GetNamespace(cx, xml->name, &ancdecls);
2769 if (!ns)
2770 goto out;
2772 /* Step 12 (NULL means *undefined* here), plus the deferred ns cloning. */
2773 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 if (!xml->name->prefix) {
2789 prefix = cx->runtime->emptyString;
2790 } else {
2791 prefix = GeneratePrefix(cx, ns->uri, &ancdecls);
2792 if (!prefix)
2793 goto out;
2794 }
2795 ns = js_NewXMLNamespace(cx, prefix, ns->uri, JS_TRUE);
2796 if (!ns)
2797 goto out;
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 if (IS_EMPTY(prefix)) {
2813 i = XMLArrayFindMember(&decls, ns, namespace_match);
2814 if (i != XML_NOT_FOUND)
2815 XMLArrayDelete(cx, &decls, i, JS_TRUE);
2816 }
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 if (!XMLARRAY_APPEND(cx, &ancdecls, ns) ||
2826 !XMLARRAY_APPEND(cx, &decls, ns)) {
2827 goto out;
2828 }
2829 }
2831 /* Format the element or point-tag into sb. */
2832 js_AppendChar(&sb, '<');
2834 if (ns->prefix && !IS_EMPTY(ns->prefix)) {
2835 js_AppendJSString(&sb, ns->prefix);
2836 js_AppendChar(&sb, ':');
2837 }
2838 js_AppendJSString(&sb, xml->name->localName);
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 */
2846 /* Step 17(c): append XML namespace declarations. */
2847 XMLArrayCursorInit(&cursor, &decls);
2848 while ((ns2 = (JSXMLNamespace *) XMLArrayCursorNext(&cursor)) != NULL) {
2849 JS_ASSERT(ns2->declared);
2851 js_AppendCString(&sb, " xmlns");
2853 /* 17(c)(ii): NULL means *undefined* here. */
2854 if (!ns2->prefix) {
2855 prefix = GeneratePrefix(cx, ns2->uri, &ancdecls);
2856 if (!prefix)
2857 break;
2858 ns2->prefix = prefix;
2859 }
2861 /* 17(c)(iii). */
2862 if (!IS_EMPTY(ns2->prefix)) {
2863 js_AppendChar(&sb, ':');
2864 js_AppendJSString(&sb, ns2->prefix);
2865 }
2867 /* 17(d-g). */
2868 AppendAttributeValue(cx, &sb, ns2->uri);
2869 }
2870 XMLArrayCursorFinish(&cursor);
2871 if (ns2)
2872 goto out;
2874 /* Step 17(b): append attributes. */
2875 XMLArrayCursorInit(&cursor, &xml->xml_attrs);
2876 while ((attr = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2877 js_AppendChar(&sb, ' ');
2878 ns2 = GetNamespace(cx, attr->name, &ancdecls);
2879 if (!ns2)
2880 break;
2882 /* 17(b)(ii): NULL means *undefined* here. */
2883 if (!ns2->prefix) {
2884 prefix = GeneratePrefix(cx, ns2->uri, &ancdecls);
2885 if (!prefix)
2886 break;
2888 /* Again, we avoid copying ns2 until we know it's prefix-less. */
2889 ns2 = js_NewXMLNamespace(cx, prefix, ns2->uri, JS_TRUE);
2890 if (!ns2)
2891 break;
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 if (!XMLARRAY_APPEND(cx, &ancdecls, ns2) ||
2901 !XMLARRAY_APPEND(cx, &decls, ns2)) {
2902 break;
2903 }
2904 }
2906 /* 17(b)(iii). */
2907 if (!IS_EMPTY(ns2->prefix)) {
2908 js_AppendJSString(&sb, ns2->prefix);
2909 js_AppendChar(&sb, ':');
2910 }
2912 /* 17(b)(iv). */
2913 js_AppendJSString(&sb, attr->name->localName);
2915 /* 17(d-g). */
2916 AppendAttributeValue(cx, &sb, attr->xml_value);
2917 }
2918 XMLArrayCursorFinish(&cursor);
2919 if (attr)
2920 goto out;
2922 /* Step 18: handle point tags. */
2923 n = xml->xml_kids.length;
2924 if (n == 0) {
2925 js_AppendCString(&sb, "/>");
2926 } else {
2927 /* Steps 19 through 25: handle element content, and open the end-tag. */
2928 js_AppendChar(&sb, '>');
2929 indentKids = n > 1 ||
2930 (n == 1 &&
2931 (kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
2932 kid->xml_class != JSXML_CLASS_TEXT);
2934 if (pretty && indentKids) {
2935 if (!GetUint32XMLSetting(cx, js_prettyIndent_str, &i))
2936 goto out;
2937 nextIndentLevel = indentLevel + i;
2938 } else {
2939 nextIndentLevel = 0;
2940 }
2942 XMLArrayCursorInit(&cursor, &xml->xml_kids);
2943 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2944 if (pretty && indentKids)
2945 js_AppendChar(&sb, '\n');
2947 kidstr = XMLToXMLString(cx, kid, &ancdecls, nextIndentLevel);
2948 if (!kidstr)
2949 break;
2951 js_AppendJSString(&sb, kidstr);
2952 }
2953 XMLArrayCursorFinish(&cursor);
2954 if (kid)
2955 goto out;
2957 if (pretty && indentKids) {
2958 js_AppendChar(&sb, '\n');
2959 js_RepeatChar(&sb, ' ', indentLevel);
2960 }
2961 js_AppendCString(&sb, "</");
2963 /* Step 26. */
2964 if (ns->prefix && !IS_EMPTY(ns->prefix)) {
2965 js_AppendJSString(&sb, ns->prefix);
2966 js_AppendChar(&sb, ':');
2967 }
2969 /* Step 27. */
2970 js_AppendJSString(&sb, xml->name->localName);
2971 js_AppendChar(&sb, '>');
2972 }
2974 if (!STRING_BUFFER_OK(&sb)) {
2975 JS_ReportOutOfMemory(cx);
2976 goto out;
2977 }
2979 str = js_NewString(cx, sb.base, STRING_BUFFER_OFFSET(&sb), 0);
2980 out:
2981 JS_LeaveLocalRootScope(cx);
2982 if (!str && STRING_BUFFER_OK(&sb))
2983 js_FinishStringBuffer(&sb);
2984 XMLArrayFinish(cx, &decls);
2985 if (ancdecls.capacity != 0)
2986 XMLArrayFinish(cx, &ancdecls);
2987 return str;
2988 }
2990 /* ECMA-357 10.2 */
2991 static JSString *
2992 ToXMLString(JSContext *cx, jsval v)
2993 {
2994 JSObject *obj;
2995 JSString *str;
2996 JSXML *xml;
2998 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
2999 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 return NULL;
3005 }
3007 if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
3008 return js_ValueToString(cx, v);
3010 if (JSVAL_IS_STRING(v))
3011 return EscapeElementValue(cx, NULL, JSVAL_TO_STRING(v));
3013 obj = JSVAL_TO_OBJECT(v);
3014 if (!OBJECT_IS_XML(cx, obj)) {
3015 if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, &v))
3016 return NULL;
3017 str = js_ValueToString(cx, v);
3018 if (!str)
3019 return NULL;
3020 return EscapeElementValue(cx, NULL, str);
3021 }
3023 /* Handle non-element cases in this switch, returning from each case. */
3024 xml = (JSXML *) JS_GetPrivate(cx, obj);
3025 return XMLToXMLString(cx, xml, NULL, 0);
3026 }
3028 static JSXMLQName *
3029 ToAttributeName(JSContext *cx, jsval v)
3030 {
3031 JSString *name, *uri, *prefix;
3032 JSObject *obj;
3033 JSClass *clasp;
3034 JSXMLQName *qn;
3035 JSTempValueRooter tvr;
3037 if (JSVAL_IS_STRING(v)) {
3038 name = JSVAL_TO_STRING(v);
3039 uri = prefix = cx->runtime->emptyString;
3040 } else {
3041 if (JSVAL_IS_PRIMITIVE(v)) {
3042 name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
3043 if (name) {
3044 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3045 JSMSG_BAD_XML_ATTR_NAME,
3046 JS_GetStringBytes(name));
3047 }
3048 return NULL;
3049 }
3051 obj = JSVAL_TO_OBJECT(v);
3052 clasp = OBJ_GET_CLASS(cx, obj);
3053 if (clasp == &js_AttributeNameClass)
3054 return (JSXMLQName *) JS_GetPrivate(cx, obj);
3056 if (clasp == &js_QNameClass.base) {
3057 qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
3058 uri = qn->uri;
3059 prefix = qn->prefix;
3060 name = qn->localName;
3061 } else {
3062 if (clasp == &js_AnyNameClass) {
3063 name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
3064 } else {
3065 name = js_ValueToString(cx, v);
3066 if (!name)
3067 return NULL;
3068 }
3069 uri = prefix = cx->runtime->emptyString;
3070 }
3071 }
3073 qn = js_NewXMLQName(cx, uri, prefix, name);
3074 if (!qn)
3075 return NULL;
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 JS_PUSH_SINGLE_TEMP_ROOT(cx, OBJECT_TO_JSVAL(qn), &tvr);
3083 obj = js_GetAttributeNameObject(cx, qn);
3084 JS_POP_TEMP_ROOT(cx, &tvr);
3085 if (!obj)
3086 return NULL;
3087 return qn;
3088 }
3090 static JSXMLQName *
3091 ToXMLName(JSContext *cx, jsval v, jsid *funidp)
3092 {
3093 JSString *name;
3094 JSObject *obj;
3095 JSClass *clasp;
3096 uint32 index;
3097 JSXMLQName *qn;
3098 JSAtom *atom;
3100 if (JSVAL_IS_STRING(v)) {
3101 name = JSVAL_TO_STRING(v);
3102 } else {
3103 if (JSVAL_IS_PRIMITIVE(v)) {
3104 name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
3105 if (name)
3106 goto bad;
3107 return NULL;
3108 }
3110 obj = JSVAL_TO_OBJECT(v);
3111 clasp = OBJ_GET_CLASS(cx, obj);
3112 if (clasp == &js_AttributeNameClass || clasp == &js_QNameClass.base)
3113 goto out;
3114 if (clasp == &js_AnyNameClass) {
3115 name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
3116 goto construct;
3117 }
3118 name = js_ValueToString(cx, v);
3119 if (!name)
3120 return NULL;
3121 }
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 if (js_IdIsIndex(STRING_TO_JSVAL(name), &index))
3137 goto bad;
3139 if (*JSSTRING_CHARS(name) == '@') {
3140 name = js_NewDependentString(cx, name, 1, JSSTRING_LENGTH(name) - 1, 0);
3141 if (!name)
3142 return NULL;
3143 *funidp = 0;
3144 return ToAttributeName(cx, STRING_TO_JSVAL(name));
3145 }
3147 construct:
3148 v = STRING_TO_JSVAL(name);
3149 obj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &v);
3150 if (!obj)
3151 return NULL;
3153 out:
3154 qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
3155 atom = cx->runtime->atomState.lazy.functionNamespaceURIAtom;
3156 if (qn->uri && atom &&
3157 (qn->uri == ATOM_TO_STRING(atom) ||
3158 !js_CompareStrings(qn->uri, ATOM_TO_STRING(atom)))) {
3159 if (!JS_ValueToId(cx, STRING_TO_JSVAL(qn->localName), funidp))
3160 return NULL;
3161 } else {
3162 *funidp = 0;
3163 }
3164 return qn;
3166 bad:
3167 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3168 JSMSG_BAD_XML_NAME,
3169 js_ValueToPrintableString(cx, STRING_TO_JSVAL(name)));
3170 return NULL;
3171 }
3173 /* ECMA-357 9.1.1.13 XML [[AddInScopeNamespace]]. */
3174 static JSBool
3175 AddInScopeNamespace(JSContext *cx, JSXML *xml, JSXMLNamespace *ns)
3176 {
3177 JSXMLNamespace *match, *ns2;
3178 uint32 i, n, m;
3180 if (xml->xml_class != JSXML_CLASS_ELEMENT)
3181 return JS_TRUE;
3183 /* NULL means *undefined* here -- see ECMA-357 9.1.1.13 step 2. */
3184 if (!ns->prefix) {
3185 match = NULL;
3186 for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3187 ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3188 if (ns2 && !js_CompareStrings(ns2->uri, ns->uri)) {
3189 match = ns2;
3190 break;
3191 }
3192 }
3193 if (!match && !XMLARRAY_ADD_MEMBER(cx, &xml->xml_namespaces, n, ns))
3194 return JS_FALSE;
3195 } else {
3196 if (IS_EMPTY(ns->prefix) && IS_EMPTY(xml->name->uri))
3197 return JS_TRUE;
3198 match = NULL;
3199 #ifdef __GNUC__ /* suppress bogus gcc warnings */
3200 m = XML_NOT_FOUND;
3201 #endif
3202 for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3203 ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3204 if (ns2 && ns2->prefix &&
3205 !js_CompareStrings(ns2->prefix, ns->prefix)) {
3206 match = ns2;
3207 m = i;
3208 break;
3209 }
3210 }
3211 if (match && js_CompareStrings(match->uri, ns->uri)) {
3212 ns2 = XMLARRAY_DELETE(cx, &xml->xml_namespaces, m, JS_TRUE,
3213 JSXMLNamespace);
3214 JS_ASSERT(ns2 == match);
3215 match->prefix = NULL;
3216 if (!AddInScopeNamespace(cx, xml, match))
3217 return JS_FALSE;
3218 }
3219 if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
3220 return JS_FALSE;
3221 }
3223 /* OPTION: enforce that descendants have superset namespaces. */
3224 return JS_TRUE;
3225 }
3227 /* ECMA-357 9.2.1.6 XMLList [[Append]]. */
3228 static JSBool
3229 Append(JSContext *cx, JSXML *list, JSXML *xml)
3230 {
3231 uint32 i, j, k, n;
3232 JSXML *kid;
3234 JS_ASSERT(list->xml_class == JSXML_CLASS_LIST);
3235 i = list->xml_kids.length;
3236 n = 1;
3237 if (xml->xml_class == JSXML_CLASS_LIST) {
3238 list->xml_target = xml->xml_target;
3239 list->xml_targetprop = xml->xml_targetprop;
3240 n = JSXML_LENGTH(xml);
3241 k = i + n;
3242 if (!XMLArraySetCapacity(cx, &list->xml_kids, k))
3243 return JS_FALSE;
3244 for (j = 0; j < n; j++) {
3245 kid = XMLARRAY_MEMBER(&xml->xml_kids, j, JSXML);
3246 if (kid)
3247 XMLARRAY_SET_MEMBER(&list->xml_kids, i + j, kid);
3248 }
3249 return JS_TRUE;
3250 }
3252 list->xml_target = xml->parent;
3253 if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
3254 list->xml_targetprop = NULL;
3255 else
3256 list->xml_targetprop = xml->name;
3257 if (!XMLARRAY_ADD_MEMBER(cx, &list->xml_kids, i, xml))
3258 return JS_FALSE;
3259 return JS_TRUE;
3260 }
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);
3266 static JSXML *
3267 DeepCopy(JSContext *cx, JSXML *xml, JSObject *obj, uintN flags)
3268 {
3269 JSXML *copy;
3270 JSBool ok;
3272 /* Our caller may not be protecting newborns with a local root scope. */
3273 if (!JS_EnterLocalRootScope(cx))
3274 return NULL;
3275 copy = DeepCopyInLRS(cx, xml, flags);
3276 if (copy) {
3277 if (obj) {
3278 /* Caller provided the object for this copy, hook 'em up. */
3279 ok = JS_SetPrivate(cx, obj, copy);
3280 if (ok)
3281 copy->object = obj;
3282 } else {
3283 ok = js_GetXMLObject(cx, copy) != NULL;
3284 }
3285 if (!ok)
3286 copy = NULL;
3287 }
3288 JS_LeaveLocalRootScope(cx);
3289 return copy;
3290 }
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 {
3301 uint32 j, n;
3302 JSXMLArrayCursor cursor;
3303 JSBool ok;
3304 JSXML *kid, *kid2;
3305 JSString *str;
3307 JS_ASSERT(cx->localRootStack);
3309 n = from->length;
3310 if (!XMLArraySetCapacity(cx, to, n))
3311 return JS_FALSE;
3313 XMLArrayCursorInit(&cursor, from);
3314 j = 0;
3315 ok = JS_TRUE;
3316 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
3317 if ((flags & XSF_IGNORE_COMMENTS) &&
3318 kid->xml_class == JSXML_CLASS_COMMENT) {
3319 continue;
3320 }
3321 if ((flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS) &&
3322 kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
3323 continue;
3324 }
3325 if ((flags & XSF_IGNORE_WHITESPACE) &&
3326 (kid->xml_flags & XMLF_WHITESPACE_TEXT)) {
3327 continue;
3328 }
3329 kid2 = DeepCopyInLRS(cx, kid, flags);
3330 if (!kid2) {
3331 to->length = j;
3332 ok = JS_FALSE;
3333 break;
3334 }
3336 if ((flags & XSF_IGNORE_WHITESPACE) &&
3337 n > 1 && kid2->xml_class == JSXML_CLASS_TEXT) {
3338 str = ChompXMLWhitespace(cx, kid2->xml_value);
3339 if (!str) {
3340 to->length = j;
3341 ok = JS_FALSE;
3342 break;
3343 }
3344 kid2->xml_value = str;
3345 }
3347 XMLARRAY_SET_MEMBER(to, j, kid2);
3348 ++j;
3349 if (parent->xml_class != JSXML_CLASS_LIST)
3350 kid2->parent = parent;
3351 }
3352 XMLArrayCursorFinish(&cursor);
3353 if (!ok)
3354 return JS_FALSE;
3356 if (j < n)
3357 XMLArrayTrim(to);
3358 return JS_TRUE;
3359 }
3361 static JSXML *
3362 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags)
3363 {
3364 JSXML *copy;
3365 JSXMLQName *qn;
3366 JSBool ok;
3367 uint32 i, n;
3368 JSXMLNamespace *ns, *ns2;
3370 /* Our caller must be protecting newborn objects. */
3371 JS_ASSERT(cx->localRootStack);
3373 copy = js_NewXML(cx, xml->xml_class);
3374 if (!copy)
3375 return NULL;
3376 qn = xml->name;
3377 if (qn) {
3378 qn = js_NewXMLQName(cx, qn->uri, qn->prefix, qn->localName);
3379 if (!qn) {
3380 ok = JS_FALSE;
3381 goto out;
3382 }
3383 }
3384 copy->name = qn;
3385 copy->xml_flags = xml->xml_flags;
3387 if (JSXML_HAS_VALUE(xml)) {
3388 copy->xml_value = xml->xml_value;
3389 ok = JS_TRUE;
3390 } else {
3391 ok = DeepCopySetInLRS(cx, &xml->xml_kids, ©->xml_kids, copy, flags);
3392 if (!ok)
3393 goto out;
3395 if (xml->xml_class == JSXML_CLASS_LIST) {
3396 copy->xml_target = xml->xml_target;
3397 copy->xml_targetprop = xml->xml_targetprop;
3398 } else {
3399 n = xml->xml_namespaces.length;
3400 ok = XMLArraySetCapacity(cx, ©->xml_namespaces, n);
3401 if (!ok)
3402 goto out;
3403 for (i = 0; i < n; i++) {
3404 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3405 if (!ns)
3406 continue;
3407 ns2 = js_NewXMLNamespace(cx, ns->prefix, ns->uri, ns->declared);
3408 if (!ns2) {
3409 copy->xml_namespaces.length = i;
3410 ok = JS_FALSE;
3411 goto out;
3412 }
3413 XMLARRAY_SET_MEMBER(©->xml_namespaces, i, ns2);
3414 }
3416 ok = DeepCopySetInLRS(cx, &xml->xml_attrs, ©->xml_attrs, copy,
3417 0);
3418 if (!ok)
3419 goto out;
3420 }
3421 }
3423 out:
3424 if (!ok)
3425 return NULL;
3426 return copy;
3427 }
3429 static void
3430 ReportBadXMLName(JSContext *cx, jsval id)
3431 {
3432 JSString *name;
3434 name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, id, NULL);
3435 if (name) {
3436 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3437 JSMSG_BAD_XML_NAME,
3438 JS_GetStringBytes(name));
3439 }
3440 }
3442 /* ECMA-357 9.1.1.4 XML [[DeleteByIndex]]. */
3443 static JSBool
3444 DeleteByIndex(JSContext *cx, JSXML *xml, jsval id, jsval *vp)
3445 {
3446 uint32 index;
3447 JSXML *kid;
3449 if (!js_IdIsIndex(id, &index)) {
3450 ReportBadXMLName(cx, id);
3451 return JS_FALSE;
3452 }
3454 if (JSXML_HAS_KIDS(xml) && index < xml->xml_kids.length) {
3455 kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3456 if (kid)
3457 kid->parent = NULL;
3458 XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
3459 }
3461 *vp = JSVAL_TRUE;
3462 return JS_TRUE;
3463 }
3465 typedef JSBool (*JSXMLNameMatcher)(JSXMLQName *nameqn, JSXML *xml);
3467 static JSBool
3468 MatchAttrName(JSXMLQName *nameqn, JSXML *attr)
3469 {
3470 JSXMLQName *attrqn = attr->name;
3472 return (IS_STAR(nameqn->localName) ||
3473 !js_CompareStrings(attrqn->localName, nameqn->localName)) &&
3474 (!nameqn->uri ||
3475 !js_CompareStrings(attrqn->uri, nameqn->uri));
3476 }
3478 static JSBool
3479 MatchElemName(JSXMLQName *nameqn, JSXML *elem)
3480 {
3481 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 }
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 {
3493 uint32 i, n;
3494 JSXML *attr, *kid;
3496 if (xml->xml_class == JSXML_CLASS_ELEMENT &&
3497 OBJ_GET_CLASS(cx, nameqn->object) == &js_AttributeNameClass) {
3498 for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
3499 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3500 if (attr && MatchAttrName(nameqn, attr)) {
3501 if (!Append(cx, list, attr))
3502 return JS_FALSE;
3503 }
3504 }
3505 }
3507 for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
3508 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3509 if (!kid)
3510 continue;
3511 if (OBJ_GET_CLASS(cx, nameqn->object) != &js_AttributeNameClass &&
3512 MatchElemName(nameqn, kid)) {
3513 if (!Append(cx, list, kid))
3514 return JS_FALSE;
3515 }
3516 if (!DescendantsHelper(cx, kid, nameqn, list))
3517 return JS_FALSE;
3518 }
3519 return JS_TRUE;
3520 }
3522 static JSXML *
3523 Descendants(JSContext *cx, JSXML *xml, jsval id)
3524 {
3525 jsid funid;
3526 JSXMLQName *nameqn;
3527 JSObject *listobj;
3528 JSXML *list, *kid;
3529 uint32 i, n;
3530 JSBool ok;
3532 nameqn = ToXMLName(cx, id, &funid);
3533 if (!nameqn)
3534 return NULL;
3536 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
3537 if (!listobj)
3538 return NULL;
3539 list = (JSXML *) JS_GetPrivate(cx, listobj);
3540 if (funid)
3541 return list;
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 list->name = nameqn;
3550 if (!JS_EnterLocalRootScope(cx))
3551 return NULL;
3552 if (xml->xml_class == JSXML_CLASS_LIST) {
3553 ok = JS_TRUE;
3554 for (i = 0, n = xml->xml_kids.length; i < n; i++) {
3555 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3556 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
3557 ok = DescendantsHelper(cx, kid, nameqn, list);
3558 if (!ok)
3559 break;
3560 }
3561 }
3562 } else {
3563 ok = DescendantsHelper(cx, xml, nameqn, list);
3564 }
3565 JS_LeaveLocalRootScope(cx);
3566 if (!ok)
3567 return NULL;
3568 list->name = NULL;
3569 return list;
3570 }
3572 static JSBool
3573 xml_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
3575 /* Recursive (JSXML *) parameterized version of Equals. */
3576 static JSBool
3577 XMLEquals(JSContext *cx, JSXML *xml, JSXML *vxml, JSBool *bp)
3578 {
3579 JSXMLQName *qn, *vqn;
3580 uint32 i, j, n;
3581 JSXMLArrayCursor cursor, vcursor;
3582 JSXML *kid, *vkid, *attr, *vattr;
3583 JSBool ok;
3584 JSObject *xobj, *vobj;
3586 retry:
3587 if (xml->xml_class != vxml->xml_class) {
3588 if (xml->xml_class == JSXML_CLASS_LIST && xml->xml_kids.length == 1) {
3589 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3590 if (xml)
3591 goto retry;
3592 }
3593 if (vxml->xml_class == JSXML_CLASS_LIST && vxml->xml_kids.length == 1) {
3594 vxml = XMLARRAY_MEMBER(&vxml->xml_kids, 0, JSXML);
3595 if (vxml)
3596 goto retry;
3597 }
3598 *bp = JS_FALSE;
3599 return JS_TRUE;
3600 }
3602 qn = xml->name;
3603 vqn = vxml->name;
3604 if (qn) {
3605 *bp = vqn &&
3606 !js_CompareStrings(qn->localName, vqn->localName) &&
3607 !js_CompareStrings(qn->uri, vqn->uri);
3608 } else {
3609 *bp = vqn == NULL;
3610 }
3611 if (!*bp)
3612 return JS_TRUE;
3614 if (JSXML_HAS_VALUE(xml)) {
3615 *bp = !js_CompareStrings(xml->xml_value, vxml->xml_value);
3616 } else if (xml->xml_kids.length != vxml->xml_kids.length) {
3617 *bp = JS_FALSE;
3618 } else {
3619 XMLArrayCursorInit(&cursor, &xml->xml_kids);
3620 XMLArrayCursorInit(&vcursor, &vxml->xml_kids);
3621 for (;;) {
3622 kid = (JSXML *) XMLArrayCursorNext(&cursor);
3623 vkid = (JSXML *) XMLArrayCursorNext(&vcursor);
3624 if (!kid || !vkid) {
3625 *bp = !kid && !vkid;
3626 ok = JS_TRUE;
3627 break;
3628 }
3629 xobj = js_GetXMLObject(cx, kid);
3630 vobj = js_GetXMLObject(cx, vkid);
3631 ok = xobj && vobj &&
3632 xml_equality(cx, xobj, OBJECT_TO_JSVAL(vobj), bp);
3633 if (!ok || !*bp)
3634 break;
3635 }
3636 XMLArrayCursorFinish(&vcursor);
3637 XMLArrayCursorFinish(&cursor);
3638 if (!ok)
3639 return JS_FALSE;
3641 if (*bp && xml->xml_class == JSXML_CLASS_ELEMENT) {
3642 n = xml->xml_attrs.length;
3643 if (n != vxml->xml_attrs.length)
3644 *bp = JS_FALSE;
3645 for (i = 0; *bp && i < n; i++) {
3646 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3647 if (!attr)
3648 continue;
3649 j = XMLARRAY_FIND_MEMBER(&vxml->xml_attrs, attr, attr_identity);
3650 if (j == XML_NOT_FOUND) {
3651 *bp = JS_FALSE;
3652 break;
3653 }
3654 vattr = XMLARRAY_MEMBER(&vxml->xml_attrs, j, JSXML);
3655 if (!vattr)
3656 continue;
3657 *bp = !js_CompareStrings(attr->xml_value, vattr->xml_value);
3658 }
3659 }
3660 }
3662 return JS_TRUE;
3663 }
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 {
3669 JSObject *vobj;
3670 JSXML *vxml;
3672 if (JSVAL_IS_PRIMITIVE(v)) {
3673 *bp = JS_FALSE;
3674 if (xml->xml_class == JSXML_CLASS_LIST) {
3675 if (xml->xml_kids.length == 1) {
3676 vxml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3677 if (!vxml)
3678 return JS_TRUE;
3679 vobj = js_GetXMLObject(cx, vxml);
3680 if (!vobj)
3681 return JS_FALSE;
3682 return js_XMLObjectOps.equality(cx, vobj, v, bp);
3683 }
3684 if (JSVAL_IS_VOID(v) && xml->xml_kids.length == 0)
3685 *bp = JS_TRUE;
3686 }
3687 } else {
3688 vobj = JSVAL_TO_OBJECT(v);
3689 if (!OBJECT_IS_XML(cx, vobj)) {
3690 *bp = JS_FALSE;
3691 } else {
3692 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3693 if (!XMLEquals(cx, xml, vxml, bp))
3694 return JS_FALSE;
3695 }
3696 }
3697 return JS_TRUE;
3698 }
3700 static JSBool
3701 CheckCycle(JSContext *cx, JSXML *xml, JSXML *kid)
3702 {
3703 JS_ASSERT(kid->xml_class != JSXML_CLASS_LIST);
3705 do {
3706 if (xml == kid) {
3707 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3708 JSMSG_CYCLIC_VALUE, js_XML_str);
3709 return JS_FALSE;
3710 }
3711 } while ((xml = xml->parent) != NULL);
3713 return JS_TRUE;
3714 }
3716 /* ECMA-357 9.1.1.11 XML [[Insert]]. */
3717 static JSBool
3718 Insert(JSContext *cx, JSXML *xml, uint32 i, jsval v)
3719 {
3720 uint32 j, n;
3721 JSXML *vxml, *kid;
3722 JSObject *vobj;
3723 JSString *str;
3725 if (!JSXML_HAS_KIDS(xml))
3726 return JS_TRUE;
3728 n = 1;
3729 vxml = NULL;
3730 if (!JSVAL_IS_PRIMITIVE(v)) {
3731 vobj = JSVAL_TO_OBJECT(v);
3732 if (OBJECT_IS_XML(cx, vobj)) {
3733 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3734 if (vxml->xml_class == JSXML_CLASS_LIST) {
3735 n = vxml->xml_kids.length;
3736 if (n == 0)
3737 return JS_TRUE;
3738 for (j = 0; j < n; j++) {
3739 kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3740 if (!kid)
3741 continue;
3742 if (!CheckCycle(cx, xml, kid))
3743 return JS_FALSE;
3744 }
3745 } else if (vxml->xml_class == JSXML_CLASS_ELEMENT) {
3746 /* OPTION: enforce that descendants have superset namespaces. */
3747 if (!CheckCycle(cx, xml, vxml))
3748 return JS_FALSE;
3749 }
3750 }
3751 }
3752 if (!vxml) {
3753 str = js_ValueToString(cx, v);
3754 if (!str)
3755 return JS_FALSE;
3757 vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3758 if (!vxml)
3759 return JS_FALSE;
3760 vxml->xml_value = str;
3761 }
3763 if (i > xml->xml_kids.length)
3764 i = xml->xml_kids.length;
3766 if (!XMLArrayInsert(cx, &xml->xml_kids, i, n))
3767 return JS_FALSE;
3769 if (vxml->xml_class == JSXML_CLASS_LIST) {
3770 for (j = 0; j < n; j++) {
3771 kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3772 if (!kid)
3773 continue;
3774 kid->parent = xml;
3775 XMLARRAY_SET_MEMBER(&xml->xml_kids, i + j, kid);
3777 /* OPTION: enforce that descendants have superset namespaces. */
3778 }
3779 } else {
3780 vxml->parent = xml;
3781 XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
3782 }
3783 return JS_TRUE;
3784 }
3786 static JSBool
3787 IndexToIdVal(JSContext *cx, uint32 index, jsval *idvp)
3788 {
3789 JSString *str;
3791 if (index <= JSVAL_INT_MAX) {
3792 *idvp = INT_TO_JSVAL(index);
3793 } else {
3794 str = js_NumberToString(cx, (jsdouble) index);
3795 if (!str)
3796 return JS_FALSE;
3797 *idvp = STRING_TO_JSVAL(str);
3798 }
3799 return JS_TRUE;
3800 }
3802 /* ECMA-357 9.1.1.12 XML [[Replace]]. */
3803 static JSBool
3804 Replace(JSContext *cx, JSXML *xml, jsval id, jsval v)
3805 {
3806 uint32 i, n;
3807 JSXML *vxml, *kid;
3808 JSObject *vobj;
3809 jsval junk;
3810 JSString *str;
3812 if (!JSXML_HAS_KIDS(xml))
3813 return JS_TRUE;
3815 if (!js_IdIsIndex(id, &i)) {
3816 ReportBadXMLName(cx, id);
3817 return JS_FALSE;
3818 }
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 n = xml->xml_kids.length;
3826 if (i >= n) {
3827 if (!IndexToIdVal(cx, n, &id))
3828 return JS_FALSE;
3829 i = n;
3830 }
3832 vxml = NULL;
3833 if (!JSVAL_IS_PRIMITIVE(v)) {
3834 vobj = JSVAL_TO_OBJECT(v);
3835 if (OBJECT_IS_XML(cx, vobj))
3836 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3837 }
3839 switch (vxml ? vxml->xml_class : JSXML_CLASS_LIMIT) {
3840 case JSXML_CLASS_ELEMENT:
3841 /* OPTION: enforce that descendants have superset namespaces. */
3842 if (!CheckCycle(cx, xml, vxml))
3843 return JS_FALSE;
3844 case JSXML_CLASS_COMMENT:
3845 case JSXML_CLASS_PROCESSING_INSTRUCTION:
3846 case JSXML_CLASS_TEXT:
3847 goto do_replace;
3849 case JSXML_CLASS_LIST:
3850 if (i < n && !DeleteByIndex(cx, xml, id, &junk))
3851 return JS_FALSE;
3852 if (!Insert(cx, xml, i, v))
3853 return JS_FALSE;
3854 break;
3856 default:
3857 str = js_ValueToString(cx, v);
3858 if (!str)
3859 return JS_FALSE;
3861 vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3862 if (!vxml)
3863 return JS_FALSE;
3864 vxml->xml_value = str;
3866 do_replace:
3867 vxml->parent = xml;
3868 if (i < n) {
3869 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3870 if (kid)
3871 kid->parent = NULL;
3872 }
3873 if (!XMLARRAY_ADD_MEMBER(cx, &xml->xml_kids, i, vxml))
3874 return JS_FALSE;
3875 break;
3876 }
3878 return JS_TRUE;
3879 }
3881 /* Forward declared -- its implementation uses other statics that call it. */
3882 static JSBool
3883 ResolveValue(JSContext *cx, JSXML *list, JSXML **result);
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 {
3889 JSXML *xml, *kid, *parent;
3890 JSBool isIndex;
3891 JSXMLArray *array;
3892 uint32 length, index, deleteCount;
3893 JSXMLQName *nameqn;
3894 jsid funid;
3895 JSObject *nameobj, *kidobj;
3896 JSXMLNameMatcher matcher;
3898 xml = (JSXML *) JS_GetPrivate(cx, obj);
3899 isIndex = js_IdIsIndex(id, &index);
3900 if (JSXML_HAS_KIDS(xml)) {
3901 array = &xml->xml_kids;
3902 length = array->length;
3903 } else {
3904 array = NULL;
3905 length = 0;
3906 }
3908 if (xml->xml_class == JSXML_CLASS_LIST) {
3909 /* ECMA-357 9.2.1.3. */
3910 if (isIndex && index < length) {
3911 kid = XMLARRAY_MEMBER(array, index, JSXML);
3912 if (!kid)
3913 goto out;
3914 parent = kid->parent;
3915 if (parent) {
3916 JS_ASSERT(parent != xml);
3917 JS_ASSERT(JSXML_HAS_KIDS(parent));
3919 if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
3920 nameqn = kid->name;
3921 nameobj = js_GetAttributeNameObject(cx, nameqn);
3922 if (!nameobj || !js_GetXMLObject(cx, parent))
3923 return JS_FALSE;
3925 id = OBJECT_TO_JSVAL(nameobj);
3926 if (!DeleteProperty(cx, parent->object, id, vp))
3927 return JS_FALSE;
3928 } else {
3929 index = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
3930 JS_ASSERT(index != XML_NOT_FOUND);
3931 if (!IndexToIdVal(cx, index, &id))
3932 return JS_FALSE;
3933 if (!DeleteByIndex(cx, parent, id, vp))
3934 return JS_FALSE;
3935 }
3936 }
3938 XMLArrayDelete(cx, array, index, JS_TRUE);
3939 } else {
3940 for (index = 0; index < length; index++) {
3941 kid = XMLARRAY_MEMBER(array, index, JSXML);
3942 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
3943 kidobj = js_GetXMLObject(cx, kid);
3944 if (!kidobj || !DeleteProperty(cx, kidobj, id, vp))
3945 return JS_FALSE;
3946 }
3947 }
3948 }
3949 } else {
3950 /* ECMA-357 9.1.1.3. */
3951 if (isIndex) {
3952 /* See NOTE in spec: this variation is reserved for future use. */
3953 ReportBadXMLName(cx, id);
3954 return JS_FALSE;
3955 }
3957 nameqn = ToXMLName(cx, id, &funid);
3958 if (!nameqn)
3959 return JS_FALSE;
3960 if (funid)
3961 goto out;
3962 nameobj = nameqn->object;
3964 if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
3965 if (xml->xml_class != JSXML_CLASS_ELEMENT)
3966 goto out;
3967 array = &xml->xml_attrs;
3968 length = array->length;
3969 matcher = MatchAttrName;
3970 } else {
3971 matcher = MatchElemName;
3972 }
3973 if (length != 0) {
3974 deleteCount = 0;
3975 for (index = 0; index < length; index++) {
3976 kid = XMLARRAY_MEMBER(array, index, JSXML);
3977 if (kid && matcher(nameqn, kid)) {
3978 kid->parent = NULL;
3979 XMLArrayDelete(cx, array, index, JS_FALSE);
3980 ++deleteCount;
3981 } else if (deleteCount != 0) {
3982 XMLARRAY_SET_MEMBER(array,
3983 index - deleteCount,
3984 array->vector[index]);
3985 }
3986 }
3987 array->length -= deleteCount;
3988 }
3989 }
3991 out:
3992 *vp = JSVAL_TRUE;
3993 return JS_TRUE;
3994 }
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))
4006 static JSBool
4007 GetFunction(JSContext *cx, JSObject *obj, JSXML *xml, jsid id, jsval *vp)
4008 {
4009 JSFunction *fun;
4011 do {
4012 /* XXXbe really want a separate scope for function::*. */
4013 if (!js_GetProperty(cx, obj, id, vp))
4014 return JS_FALSE;
4015 if (JSVAL_IS_FUNCTION(cx, *vp)) {
4016 if (xml && OBJECT_IS_XML(cx, obj)) {
4017 fun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*vp));
4018 if (fun->spare &&
4019 (fun->spare & CLASS_TO_MASK(xml->xml_class)) == 0) {
4020 /* XML method called on XMLList or vice versa. */
4021 *vp = JSVAL_VOID;
4022 }
4023 }
4024 break;
4025 }
4026 } while ((obj = OBJ_GET_PROTO(cx, obj)) != NULL);
4027 return JS_TRUE;
4028 }
4030 static JSBool
4031 SyncInScopeNamespaces(JSContext *cx, JSXML *xml)
4032 {
4033 JSXMLArray *nsarray;
4034 uint32 i, n;
4035 JSXMLNamespace *ns;
4037 nsarray = &xml->xml_namespaces;
4038 while ((xml = xml->parent) != NULL) {
4039 for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
4040 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
4041 if (ns && !XMLARRAY_HAS_MEMBER(nsarray, ns, namespace_identity)) {
4042 if (!XMLARRAY_APPEND(cx, nsarray, ns))
4043 return JS_FALSE;
4044 }
4045 }
4046 }
4047 return JS_TRUE;
4048 }
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 {
4054 JSXML *xml, *list, *kid;
4055 uint32 index;
4056 JSObject *kidobj, *listobj, *nameobj;
4057 JSXMLQName *nameqn;
4058 jsid funid;
4059 JSBool ok;
4060 JSXMLArrayCursor cursor;
4061 jsval kidval;
4062 JSXMLArray *array;
4063 JSXMLNameMatcher matcher;
4065 xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
4066 if (!xml)
4067 return JS_TRUE;
4069 #ifdef __GNUC__
4070 list = NULL; /* quell GCC overwarning */
4071 #endif
4073 retry:
4074 if (xml->xml_class == JSXML_CLASS_LIST) {
4075 /* ECMA-357 9.2.1.1 starts here. */
4076 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 if (index < xml->xml_kids.length) {
4083 kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
4084 if (!kid) {
4085 *vp = JSVAL_VOID;
4086 return JS_TRUE;
4087 }
4088 kidobj = js_GetXMLObject(cx, kid);
4089 if (!kidobj)
4090 return JS_FALSE;
4092 *vp = OBJECT_TO_JSVAL(kidobj);
4093 } else {
4094 *vp = JSVAL_VOID;
4095 }
4096 return JS_TRUE;
4097 }
4099 nameqn = ToXMLName(cx, id, &funid);
4100 if (!nameqn)
4101 return JS_FALSE;
4102 if (funid)
4103 return GetFunction(cx, obj, xml, funid, vp);
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 ok = JS_EnterLocalRootScope(cx);
4111 if (!ok)
4112 return JS_FALSE;
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 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4120 if (!listobj) {
4121 ok = JS_FALSE;
4122 } else {
4123 list = (JSXML *) JS_GetPrivate(cx, listobj);
4124 list->xml_target = xml;
4126 XMLArrayCursorInit(&cursor, &xml->xml_kids);
4127 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
4128 if (kid->xml_class == JSXML_CLASS_ELEMENT) {
4129 kidobj = js_GetXMLObject(cx, kid);
4130 if (!kidobj) {
4131 ok = JS_FALSE;
4132 break;
4133 }
4134 ok = GetProperty(cx, kidobj, id, &kidval);
4135 if (!ok)
4136 break;
4137 kidobj = JSVAL_TO_OBJECT(kidval);
4138 kid = (JSXML *) JS_GetPrivate(cx, kidobj);
4139 if (JSXML_LENGTH(kid) > 0) {
4140 ok = Append(cx, list, kid);
4141 if (!ok)
4142 break;
4143 }
4144 }
4145 }
4146 XMLArrayCursorFinish(&cursor);
4147 }
4148 } else {
4149 /* ECMA-357 9.1.1.1 starts here. */
4150 if (js_IdIsIndex(id, &index)) {
4151 obj = ToXMLList(cx, OBJECT_TO_JSVAL(obj));
4152 if (!obj)
4153 return JS_FALSE;
4154 xml = (JSXML *) JS_GetPrivate(cx, obj);
4155 goto retry;
4156 }
4158 nameqn = ToXMLName(cx, id, &funid);
4159 if (!nameqn)
4160 return JS_FALSE;
4161 if (funid)
4162 return GetFunction(cx, obj, xml, funid, vp);
4163 nameobj = nameqn->object;
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 ok = JS_EnterLocalRootScope(cx);
4171 if (!ok)
4172 return JS_FALSE;
4174 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4175 if (!listobj) {
4176 ok = JS_FALSE;
4177 } else {
4178 list = (JSXML *) JS_GetPrivate(cx, listobj);
4179 list->xml_target = xml;
4181 if (JSXML_HAS_KIDS(xml)) {
4182 if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
4183 array = &xml->xml_attrs;
4184 matcher = MatchAttrName;
4185 } else {
4186 array = &xml->xml_kids;
4187 matcher = MatchElemName;
4188 }
4189 XMLArrayCursorInit(&cursor, array);
4190 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
4191 if (matcher(nameqn, kid)) {
4192 if (array == &xml->xml_kids &&
4193 kid->xml_class == JSXML_CLASS_ELEMENT) {
4194 ok = SyncInScopeNamespaces(cx, kid);
4195 if (!ok)
4196 break;
4197 }
4198 ok = Append(cx, list, kid);
4199 if (!ok)
4200 break;
4201 }
4202 }
4203 XMLArrayCursorFinish(&cursor);
4204 }
4205 }
4206 }
4208 /* Common tail code for list and non-list cases. */
4209 JS_LeaveLocalRootScope(cx);
4210 if (!ok)
4211 return JS_FALSE;
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 list->xml_targetprop = nameqn;
4221 *vp = OBJECT_TO_JSVAL(listobj);
4222 return JS_TRUE;
4223 }
4225 static JSXML *
4226 CopyOnWrite(JSContext *cx, JSXML *xml, JSObject *obj)
4227 {
4228 JS_ASSERT(xml->object != obj);
4230 xml = DeepCopy(cx, xml, obj, 0);
4231 if (!xml)
4232 return NULL;
4234 JS_ASSERT(xml->object == obj);
4235 return xml;
4236 }
4238 #define CHECK_COPY_ON_WRITE(cx,xml,obj) \
4239 (xml->object == obj ? xml : CopyOnWrite(cx, xml, obj))
4241 static JSString *
4242 KidToString(JSContext *cx, JSXML *xml, uint32 index)
4243 {
4244 JSXML *kid;
4245 JSObject *kidobj;
4247 kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
4248 if (!kid)
4249 return cx->runtime->emptyString;
4250 kidobj = js_GetXMLObject(cx, kid);
4251 if (!kidobj)
4252 return NULL;
4253 return js_ValueToString(cx, OBJECT_TO_JSVAL(kidobj));
4254 }
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 {
4260 JSBool ok, primitiveAssign;
4261 enum { OBJ_ROOT, ID_ROOT, VAL_ROOT };
4262 jsval roots[3];
4263 JSTempValueRooter tvr;
4264 JSXML *xml, *vxml, *rxml, *kid, *attr, *parent, *copy, *kid2, *match;
4265 JSObject *vobj, *nameobj, *attrobj, *parentobj, *kidobj, *copyobj;
4266 JSXMLQName *targetprop, *nameqn, *attrqn;
4267 uint32 index, i, j, k, n, q;
4268 jsval attrval, nsval, junk;
4269 jsid funid;
4270 JSString *left, *right, *space;
4271 JSXMLNamespace *ns;
4273 xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
4274 if (!xml)
4275 return JS_TRUE;
4277 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
4278 if (!xml)
4279 return JS_FALSE;
4281 /* Precompute vxml for 9.2.1.2 2(c)(vii)(2-3) and 2(d) and 9.1.1.2 1. */
4282 vxml = NULL;
4283 if (!JSVAL_IS_PRIMITIVE(*vp)) {
4284 vobj = JSVAL_TO_OBJECT(*vp);
4285 if (OBJECT_IS_XML(cx, vobj))
4286 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4287 }
4289 /* Control flow after here must exit via label out. */
4290 ok = JS_EnterLocalRootScope(cx);
4291 if (!ok)
4292 return JS_FALSE;
4293 roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
4294 roots[ID_ROOT] = id;
4295 roots[VAL_ROOT] = *vp;
4296 JS_PUSH_TEMP_ROOT(cx, 3, roots, &tvr);
4298 if (xml->xml_class == JSXML_CLASS_LIST) {
4299 /* ECMA-357 9.2.1.2. */
4300 if (js_IdIsIndex(id, &index)) {
4301 /* Step 1 sets i to the property index. */
4302 i = index;
4304 /* 2(a-b). */
4305 if (xml->xml_target) {
4306 ok = ResolveValue(cx, xml->xml_target, &rxml);
4307 if (!ok)
4308 goto out;
4309 if (!rxml)
4310 goto out;
4311 JS_ASSERT(rxml->object);
4312 } else {
4313 rxml = NULL;
4314 }
4316 /* 2(c). */
4317 if (index >= xml->xml_kids.length) {
4318 /* 2(c)(i). */
4319 if (rxml) {
4320 if (rxml->xml_class == JSXML_CLASS_LIST) {
4321 if (rxml->xml_kids.length != 1)
4322 goto out;
4323 rxml = XMLARRAY_MEMBER(&rxml->xml_kids, 0, JSXML);
4324 if (!rxml)
4325 goto out;
4326 ok = js_GetXMLObject(cx, rxml) != NULL;
4327 if (!ok)
4328 goto out;
4329 }
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 if (!JSXML_HAS_KIDS(rxml))
4347 goto out;
4348 }
4350 /* 2(c)(ii) is distributed below as several js_NewXML calls. */
4351 targetprop = xml->xml_targetprop;
4352 if (!targetprop || IS_STAR(targetprop->localName)) {
4353 /* 2(c)(iv)(1-2), out of order w.r.t. 2(c)(iii). */
4354 kid = js_NewXML(cx, JSXML_CLASS_TEXT);
4355 if (!kid)
4356 goto bad;
4357 } else {
4358 nameobj = js_GetXMLQNameObject(cx, targetprop);
4359 if (!nameobj)
4360 goto bad;
4361 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 ok = GetProperty(cx, rxml->object, id, &attrval);
4368 if (!ok)
4369 goto out;
4370 attrobj = JSVAL_TO_OBJECT(attrval);
4371 attr = (JSXML *) JS_GetPrivate(cx, attrobj);
4372 if (JSXML_LENGTH(attr) != 0)
4373 goto out;
4375 kid = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4376 } else {
4377 /* 2(c)(v). */
4378 kid = js_NewXML(cx, JSXML_CLASS_ELEMENT);
4379 }
4380 if (!kid)
4381 goto bad;
4383 /* An important bit of 2(c)(ii). */
4384 kid->name = targetprop;
4385 }
4387 /* Final important bit of 2(c)(ii). */
4388 kid->parent = rxml;
4390 /* 2(c)(vi-vii). */
4391 i = xml->xml_kids.length;
4392 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 if (rxml) {
4400 JS_ASSERT(JSXML_HAS_KIDS(rxml));
4401 n = rxml->xml_kids.length;
4402 j = n - 1;
4403 if (n != 0 && i != 0) {
4404 for (n = j, j = 0; j < n; j++) {
4405 if (rxml->xml_kids.vector[j] ==
4406 xml->xml_kids.vector[i-1]) {
4407 break;
4408 }
4409 }
4410 }
4412 kidobj = js_GetXMLObject(cx, kid);
4413 if (!kidobj)
4414 goto bad;
4415 ok = Insert(cx, rxml, j + 1, OBJECT_TO_JSVAL(kidobj));
4416 if (!ok)
4417 goto out;
4418 }
4420 /*
4421 * 2(c)(vii)(2-3).
4422 * Erratum: [[PropertyName]] in 2(c)(vii)(3) must be a
4423 * typo for [[TargetProperty]].
4424 */
4425 if (vxml) {
4426 kid->name = (vxml->xml_class == JSXML_CLASS_LIST)
4427 ? vxml->xml_targetprop
4428 : vxml->name;
4429 }
4430 }
4432 /* 2(c)(viii). */
4433 ok = Append(cx, xml, kid);
4434 if (!ok)
4435 goto out;
4436 }
4438 /* 2(d). */
4439 if (!vxml ||
4440 vxml->xml_class == JSXML_CLASS_TEXT ||
4441 vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4442 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4443 if (!ok)
4444 goto out;
4445 roots[VAL_ROOT] = *vp;
4446 }
4448 /* 2(e). */
4449 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
4450 if (!kid)
4451 goto out;
4452 parent = kid->parent;
4453 if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
4454 nameobj = js_GetAttributeNameObject(cx, kid->name);
4455 if (!nameobj)
4456 goto bad;
4457 id = OBJECT_TO_JSVAL(nameobj);
4459 if (parent) {
4460 /* 2(e)(i). */
4461 parentobj = parent->object;
4462 ok = PutProperty(cx, parentobj, id, vp);
4463 if (!ok)
4464 goto out;
4466 /* 2(e)(ii). */
4467 ok = GetProperty(cx, parentobj, id, vp);
4468 if (!ok)
4469 goto out;
4470 attr = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*vp));
4472 /* 2(e)(iii). */
4473 xml->xml_kids.vector[i] = attr->xml_kids.vector[0];
4474 }
4475 }
4477 /* 2(f). */
4478 else if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4479 /* 2(f)(i) Create a shallow copy _c_ of _V_. */
4480 copyobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4481 if (!copyobj)
4482 goto bad;
4483 copy = (JSXML *) JS_GetPrivate(cx, copyobj);
4484 n = vxml->xml_kids.length;
4485 ok = XMLArraySetCapacity(cx, ©->xml_kids, n);
4486 if (!ok)
4487 goto out;
4488 for (k = 0; k < n; k++) {
4489 kid2 = XMLARRAY_MEMBER(&vxml->xml_kids, k, JSXML);
4490 XMLARRAY_SET_MEMBER(©->xml_kids, k, kid2);
4491 }
4493 JS_ASSERT(parent != xml);
4494 if (parent) {
4495 q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
4496 JS_ASSERT(q != XML_NOT_FOUND);
4498 ok = IndexToIdVal(cx, q, &id);
4499 if (!ok)
4500 goto out;
4501 ok = Replace(cx, parent, id, OBJECT_TO_JSVAL(copyobj));
4502 if (!ok)
4503 goto out;
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(©->xml_kids, j, JSXML)
4510 == kid2);
4511 }
4512 #endif
4513 }
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 if (n == 0) {
4521 XMLArrayDelete(cx, &xml->xml_kids, i, JS_TRUE);
4522 } else {
4523 ok = XMLArrayInsert(cx, &xml->xml_kids, i + 1, n - 1);
4524 if (!ok)
4525 goto out;
4527 for (j = 0; j < n; j++)
4528 xml->xml_kids.vector[i + j] = copy->xml_kids.vector[j];
4529 }
4530 }
4532 /* 2(g). */
4533 else if (vxml || JSXML_HAS_VALUE(kid)) {
4534 if (parent) {
4535 q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
4536 JS_ASSERT(q != XML_NOT_FOUND);
4538 ok = IndexToIdVal(cx, q, &id);
4539 if (!ok)
4540 goto out;
4541 ok = Replace(cx, parent, id, *vp);
4542 if (!ok)
4543 goto out;
4545 vxml = XMLARRAY_MEMBER(&parent->xml_kids, q, JSXML);
4546 if (!vxml)
4547 goto out;
4548 roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vxml->object);
4549 }
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 if (!vxml) {
4562 JS_ASSERT(JSVAL_IS_STRING(*vp));
4563 vobj = ToXML(cx, *vp);
4564 if (!vobj)
4565 goto bad;
4566 roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vobj);
4567 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4568 }
4569 XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
4570 }
4572 /* 2(h). */
4573 else {
4574 kidobj = js_GetXMLObject(cx, kid);
4575 if (!kidobj)
4576 goto bad;
4577 id = ATOM_KEY(cx->runtime->atomState.starAtom);
4578 ok = PutProperty(cx, kidobj, id, vp);
4579 if (!ok)
4580 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 n = JSXML_LENGTH(xml);
4589 if (n > 1)
4590 goto type_error;
4591 if (n == 0) {
4592 ok = ResolveValue(cx, xml, &rxml);
4593 if (!ok)
4594 goto out;
4595 if (!rxml || JSXML_LENGTH(rxml) != 1)
4596 goto type_error;
4597 ok = Append(cx, xml, rxml);
4598 if (!ok)
4599 goto out;
4600 }
4601 JS_ASSERT(JSXML_LENGTH(xml) == 1);
4602 kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
4603 if (!kid)
4604 goto out;
4605 kidobj = js_GetXMLObject(cx, kid);
4606 if (!kidobj)
4607 goto bad;
4608 ok = PutProperty(cx, kidobj, id, vp);
4609 if (!ok)
4610 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 if (js_IdIsIndex(id, &index)) {
4619 /* See NOTE in spec: this variation is reserved for future use. */
4620 ReportBadXMLName(cx, id);
4621 goto bad;
4622 }
4624 nameqn = ToXMLName(cx, id, &funid);
4625 if (!nameqn)
4626 goto bad;
4627 if (funid) {
4628 ok = js_SetProperty(cx, obj, funid, vp);
4629 goto out;
4630 }
4631 nameobj = nameqn->object;
4633 if (JSXML_HAS_VALUE(xml))
4634 goto out;
4636 if (!vxml ||
4637 vxml->xml_class == JSXML_CLASS_TEXT ||
4638 vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4639 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4640 if (!ok)
4641 goto out;
4642 } else {
4643 rxml = DeepCopyInLRS(cx, vxml, 0);
4644 if (!rxml || !js_GetXMLObject(cx, rxml))
4645 goto bad;
4646 vxml = rxml;
4647 *vp = OBJECT_TO_JSVAL(vxml->object);
4648 }
4649 roots[VAL_ROOT] = *vp;
4651 /*
4652 * 6.
4653 * Erratum: why is this done here, so early? use is way later....
4654 */
4655 ok = js_GetDefaultXMLNamespace(cx, &nsval);
4656 if (!ok)
4657 goto out;
4659 if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
4660 /* 7(a). */
4661 if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)))
4662 goto out;
4664 /* 7(b-c). */
4665 if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4666 n = vxml->xml_kids.length;
4667 if (n == 0) {
4668 *vp = STRING_TO_JSVAL(cx->runtime->emptyString);
4669 } else {
4670 left = KidToString(cx, vxml, 0);
4671 if (!left)
4672 goto bad;
4674 space = ATOM_TO_STRING(cx->runtime->atomState.spaceAtom);
4675 for (i = 1; i < n; i++) {
4676 left = js_ConcatStrings(cx, left, space);
4677 if (!left)
4678 goto bad;
4679 right = KidToString(cx, vxml, i);
4680 if (!right)
4681 goto bad;
4682 left = js_ConcatStrings(cx, left, right);
4683 if (!left)
4684 goto bad;
4685 }
4687 roots[VAL_ROOT] = *vp = STRING_TO_JSVAL(left);
4688 }
4689 } else {
4690 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4691 if (!ok)
4692 goto out;
4693 roots[VAL_ROOT] = *vp;
4694 }
4696 /* 7(d-e). */
4697 match = NULL;
4698 for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
4699 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
4700 if (!attr)
4701 continue;
4702 attrqn = attr->name;
4703 if (!js_CompareStrings(attrqn->localName, nameqn->localName) &&
4704 (!nameqn->uri ||
4705 !js_CompareStrings(attrqn->uri, nameqn->uri))) {
4706 if (!match) {
4707 match = attr;
4708 } else {
4709 nameobj = js_GetAttributeNameObject(cx, attrqn);
4710 if (!nameobj)
4711 goto bad;
4713 id = OBJECT_TO_JSVAL(nameobj);
4714 ok = DeleteProperty(cx, obj, id, &junk);
4715 if (!ok)
4716 goto out;
4717 --i;
4718 }
4719 }
4720 }
4722 /* 7(f). */
4723 attr = match;
4724 if (!attr) {
4725 /* 7(f)(i-ii). */
4726 if (!nameqn->uri) {
4727 left = right = cx->runtime->emptyString;
4728 } else {
4729 left = nameqn->uri;
4730 right = nameqn->prefix;
4731 }
4732 nameqn = js_NewXMLQName(cx, left, right, nameqn->localName);
4733 if (!nameqn)
4734 goto bad;
4736 /* 7(f)(iii). */
4737 attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4738 if (!attr)
4739 goto bad;
4740 attr->parent = xml;
4741 attr->name = nameqn;
4743 /* 7(f)(iv). */
4744 ok = XMLARRAY_ADD_MEMBER(cx, &xml->xml_attrs, n, attr);
4745 if (!ok)
4746 goto out;
4748 /* 7(f)(v-vi). */
4749 ns = GetNamespace(cx, nameqn, NULL);
4750 if (!ns)
4751 goto bad;
4752 ok = AddInScopeNamespace(cx, xml, ns);
4753 if (!ok)
4754 goto out;
4755 }
4757 /* 7(g). */
4758 attr->xml_value = JSVAL_TO_STRING(*vp);
4759 goto out;
4760 }
4762 /* 8-9. */
4763 if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)) &&
4764 !IS_STAR(nameqn->localName)) {
4765 goto out;
4766 }
4768 /* 10-11. */
4769 id = JSVAL_VOID;
4770 primitiveAssign = !vxml && !IS_STAR(nameqn->localName);
4772 /* 12. */
4773 k = n = xml->xml_kids.length;
4774 kid2 = NULL;
4775 while (k != 0) {
4776 --k;
4777 kid = XMLARRAY_MEMBER(&xml->xml_kids, k, JSXML);
4778 if (kid && MatchElemName(nameqn, kid)) {
4779 if (!JSVAL_IS_VOID(id)) {
4780 ok = DeleteByIndex(cx, xml, id, &junk);
4781 if (!ok)
4782 goto out;
4783 }
4784 ok = IndexToIdVal(cx, k, &id);
4785 if (!ok)
4786 goto out;
4787 kid2 = kid;
4788 }
4789 }
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 if (kid2) {
4808 JS_ASSERT(kid2->parent == xml || !kid2->parent);
4809 if (!kid2->parent)
4810 kid2->parent = xml;
4811 }
4813 /* 13. */
4814 if (JSVAL_IS_VOID(id)) {
4815 /* 13(a). */
4816 ok = IndexToIdVal(cx, n, &id);
4817 if (!ok)
4818 goto out;
4820 /* 13(b). */
4821 if (primitiveAssign) {
4822 if (!nameqn->uri) {
4823 ns = (JSXMLNamespace *)
4824 JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
4825 left = ns->uri;
4826 right = ns->prefix;
4827 } else {
4828 left = nameqn->uri;
4829 right = nameqn->prefix;
4830 }
4831 nameqn = js_NewXMLQName(cx, left, right, nameqn->localName);
4832 if (!nameqn)
4833 goto bad;
4835 /* 13(b)(iii). */
4836 vobj = js_NewXMLObject(cx, JSXML_CLASS_ELEMENT);
4837 if (!vobj)
4838 goto bad;
4839 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4840 vxml->parent = xml;
4841 vxml->name = nameqn;
4843 /* 13(b)(iv-vi). */
4844 ns = GetNamespace(cx, nameqn, NULL);
4845 if (!ns)
4846 goto bad;
4847 ok = Replace(cx, xml, id, OBJECT_TO_JSVAL(vobj));
4848 if (!ok)
4849 goto out;
4850 ok = AddInScopeNamespace(cx, vxml, ns);
4851 if (!ok)
4852 goto out;
4853 }
4854 }
4856 /* 14. */
4857 if (primitiveAssign) {
4858 JSXMLArrayCursor cursor;
4860 js_IdIsIndex(id, &index);
4861 XMLArrayCursorInit(&cursor, &xml->xml_kids);
4862 cursor.index = index;
4863 kid = (JSXML *) XMLArrayCursorItem(&cursor);
4864 if (JSXML_HAS_KIDS(kid)) {
4865 XMLArrayFinish(cx, &kid->xml_kids);
4866 ok = XMLArrayInit(cx, &kid->xml_kids, 1);
4867 }
4869 /* 14(b-c). */
4870 /* XXXbe Erratum? redundant w.r.t. 7(b-c) else clause above */
4871 if (ok) {
4872 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4873 if (ok && !IS_EMPTY(JSVAL_TO_STRING(*vp))) {
4874 roots[VAL_ROOT] = *vp;
4875 if ((JSXML *) XMLArrayCursorItem(&cursor) == kid)
4876 ok = Replace(cx, kid, JSVAL_ZERO, *vp);
4877 }
4878 }
4879 XMLArrayCursorFinish(&cursor);
4880 } else {
4881 /* 15(a). */
4882 ok = Replace(cx, xml, id, *vp);
4883 }
4884 }
4886 out:
4887 JS_POP_TEMP_ROOT(cx, &tvr);
4888 JS_LeaveLocalRootScope(cx);
4889 return ok;
4891 type_error:
4892 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4893 JSMSG_BAD_XMLLIST_PUT,
4894 js_ValueToPrintableString(cx, id));
4895 bad:
4896 ok = JS_FALSE;
4897 goto out;
4898 }
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 {
4904 JSXML *target, *base;
4905 JSXMLQName *targetprop;
4906 JSObject *targetpropobj;
4907 jsval id, tv;
4909 /* Our caller must be protecting newborn objects. */
4910 JS_ASSERT(cx->localRootStack);
4912 if (list->xml_class != JSXML_CLASS_LIST || list->xml_kids.length != 0) {
4913 if (!js_GetXMLObject(cx, list))
4914 return JS_FALSE;
4915 *result = list;
4916 return JS_TRUE;
4917 }
4919 target = list->xml_target;
4920 targetprop = list->xml_targetprop;
4921 if (!target || !targetprop || IS_STAR(targetprop->localName)) {
4922 *result = NULL;
4923 return JS_TRUE;
4924 }
4926 targetpropobj = js_GetXMLQNameObject(cx, targetprop);
4927 if (!targetpropobj)
4928 return JS_FALSE;
4929 if (OBJ_GET_CLASS(cx, targetpropobj) == &js_AttributeNameClass) {
4930 *result = NULL;
4931 return JS_TRUE;
4932 }
4934 if (!ResolveValue(cx, target, &base))
4935 return JS_FALSE;
4936 if (!base) {
4937 *result = NULL;
4938 return JS_TRUE;
4939 }
4940 if (!js_GetXMLObject(cx, base))
4941 return JS_FALSE;
4943 id = OBJECT_TO_JSVAL(targetpropobj);
4944 if (!GetProperty(cx, base->object, id, &tv))
4945 return JS_FALSE;
4946 target = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(tv));
4948 if (JSXML_LENGTH(target) == 0) {
4949 if (base->xml_class == JSXML_CLASS_LIST && JSXML_LENGTH(base) > 1) {
4950 *result = NULL;
4951 return JS_TRUE;
4952 }
4953 tv = STRING_TO_JSVAL(cx->runtime->emptyString);
4954 if (!PutProperty(cx, base->object, id, &tv))
4955 return JS_FALSE;
4956 if (!GetProperty(cx, base->object, id, &tv))
4957 return JS_FALSE;
4958 target = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(tv));
4959 }
4961 *result = target;
4962 return JS_TRUE;
4963 }
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)
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 {
4984 JSXML *xml, *kid;
4985 JSXMLArrayCursor cursor;
4986 JSObject *kidobj;
4987 JSXMLQName *qn;
4988 jsid funid;
4989 JSXMLArray *array;
4990 JSXMLNameMatcher matcher;
4991 uint32 i, n;
4993 *objp = NULL;
4994 *propp = NULL;
4996 xml = (JSXML *) JS_GetPrivate(cx, obj);
4997 if (xml->xml_class == JSXML_CLASS_LIST) {
4998 n = JSXML_LENGTH(xml);
4999 if (js_IdIsIndex(id, &i)) {
5000 if (i < n)
5001 *propp = FOUND_XML_PROPERTY;
5002 return JS_TRUE;
5003 }
5005 XMLArrayCursorInit(&cursor, &xml->xml_kids);
5006 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5007 if (kid->xml_class == JSXML_CLASS_ELEMENT) {
5008 kidobj = js_GetXMLObject(cx, kid);
5009 if (!kidobj || !HasProperty(cx, kidobj, id, objp, propp))
5010 break;
5011 if (*propp)
5012 break;
5013 }
5014 }
5015 XMLArrayCursorFinish(&cursor);
5016 if (kid)
5017 return *propp != NULL;
5018 } else {
5019 if (xml->xml_class == JSXML_CLASS_ELEMENT && js_IdIsIndex(id, &i)) {
5020 if (i == 0)
5021 *propp = FOUND_XML_PROPERTY;
5022 return JS_TRUE;
5023 }
5025 qn = ToXMLName(cx, id, &funid);
5026 if (!qn)
5027 return JS_FALSE;
5028 if (funid)
5029 return js_LookupProperty(cx, obj, funid, objp, propp);
5031 if (xml->xml_class != JSXML_CLASS_ELEMENT)
5032 return JS_TRUE;
5034 if (OBJ_GET_CLASS(cx, qn->object) == &js_AttributeNameClass) {
5035 array = &xml->xml_attrs;
5036 matcher = MatchAttrName;
5037 } else {
5038 array = &xml->xml_kids;
5039 matcher = MatchElemName;
5040 }
5041 for (i = 0, n = array->length; i < n; i++) {
5042 kid = XMLARRAY_MEMBER(array, i, JSXML);
5043 if (kid && matcher(qn, kid)) {
5044 *propp = FOUND_XML_PROPERTY;
5045 return JS_TRUE;
5046 }
5047 }
5048 }
5050 return JS_TRUE;
5051 }
5053 static void
5054 xml_finalize(JSContext *cx, JSObject *obj)
5055 {
5056 JSXML *xml;
5058 xml = (JSXML *) JS_GetPrivate(cx, obj);
5059 if (!xml)
5060 return;
5061 if (xml->object == obj)
5062 xml->object = NULL;
5063 UNMETER(xml_stats.livexmlobj);
5064 }
5066 static void
5067 xml_mark_vector(JSContext *cx, JSXML **vec, uint32 len, void *arg)
5068 {
5069 uint32 i;
5070 JSXML *elt;
5072 for (i = 0; i < len; i++) {
5073 elt = vec[i];
5074 {
5075 #ifdef GC_MARK_DEBUG
5076 char buf[120];
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;
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;
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 const char *buf = NULL;
5098 #endif
5099 JS_MarkGCThing(cx, elt, buf, arg);
5100 }
5101 }
5102 }
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 {
5126 JSScopeProperty *sprop;
5128 if (!HasProperty(cx, obj, ID_TO_VALUE(id), objp, propp))
5129 return JS_FALSE;
5131 if (*propp == FOUND_XML_PROPERTY) {
5132 sprop = js_AddNativeProperty(cx, obj, id, GetProperty, PutProperty,
5133 SPROP_INVALID_SLOT, JSPROP_ENUMERATE,
5134 0, 0);
5135 if (!sprop)
5136 return JS_FALSE;
5138 JS_LOCK_OBJ(cx, obj);
5139 *objp = obj;
5140 *propp = (JSProperty *) sprop;
5141 }
5142 return JS_TRUE;
5143 }
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 {
5150 if (JSVAL_IS_FUNCTION(cx, value) || getter || setter ||
5151 (attrs & JSPROP_ENUMERATE) == 0 ||
5152 (attrs & (JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED))) {
5153 return js_DefineProperty(cx, obj, id, value, getter, setter, attrs,
5154 propp);
5155 }
5157 if (!PutProperty(cx, obj, ID_TO_VALUE(id), &value))
5158 return JS_FALSE;
5159 if (propp)
5160 *propp = NULL;
5161 return JS_TRUE;
5162 }
5164 static JSBool
5165 xml_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5166 {
5167 if (id == JS_DEFAULT_XML_NAMESPACE_ID) {
5168 *vp = JSVAL_VOID;
5169 return JS_TRUE;
5170 }
5172 return GetProperty(cx, obj, ID_TO_VALUE(id), vp);
5173 }
5175 static JSBool
5176 xml_setProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5177 {
5178 return PutProperty(cx, obj, ID_TO_VALUE(id), vp);
5179 }
5181 static JSBool
5182 FoundProperty(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5183 JSBool *foundp)
5184 {
5185 JSObject *pobj;
5187 if (prop) {
5188 *foundp = JS_TRUE;
5189 } else {
5190 if (!HasProperty(cx, obj, ID_TO_VALUE(id), &pobj, &prop))
5191 return JS_FALSE;
5192 if (prop)
5193 DROP_PROPERTY(cx, pobj, prop);
5194 *foundp = (prop != NULL);
5195 }
5196 return JS_TRUE;
5197 }
5199 static JSBool
5200 xml_getAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5201 uintN *attrsp)
5202 {
5203 JSBool found;
5205 if (!FoundProperty(cx, obj, id, prop, &found))
5206 return JS_FALSE;
5207 *attrsp = found ? JSPROP_ENUMERATE : 0;
5208 return JS_TRUE;
5209 }
5211 static JSBool
5212 xml_setAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5213 uintN *attrsp)
5214 {
5215 JSBool found;
5217 if (!FoundProperty(cx, obj, id, prop, &found))
5218 return JS_FALSE;
5219 if (found) {
5220 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
5221 JSMSG_CANT_SET_XML_ATTRS);
5222 }
5223 return !found;
5224 }
5226 static JSBool
5227 xml_deleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval)
5228 {
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 if (OBJ_SCOPE(obj)->object == obj && !JSID_IS_INT(id)) {
5241 if (!js_DeleteProperty(cx, obj, id, rval))
5242 return JS_FALSE;
5243 }
5245 return DeleteProperty(cx, obj, ID_TO_VALUE(id), rval);
5246 }
5248 static JSBool
5249 xml_defaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp)
5250 {
5251 JSXML *xml;
5253 if (hint == JSTYPE_OBJECT) {
5254 /* Called from for..in code in js_Interpret: return an XMLList. */
5255 xml = (JSXML *) JS_GetPrivate(cx, obj);
5256 if (xml->xml_class != JSXML_CLASS_LIST) {
5257 obj = ToXMLList(cx, OBJECT_TO_JSVAL(obj));
5258 if (!obj)
5259 return JS_FALSE;
5260 }
5261 *vp = OBJECT_TO_JSVAL(obj);
5262 return JS_TRUE;
5263 }
5265 return JS_CallFunctionName(cx, obj, js_toString_str, 0, NULL, vp);
5266 }
5268 static JSBool
5269 xml_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
5270 jsval *statep, jsid *idp)
5271 {
5272 JSXML *xml;
5273 uint32 length, index;
5274 JSXMLArrayCursor *cursor;
5276 xml = (JSXML *) JS_GetPrivate(cx, obj);
5277 length = JSXML_LENGTH(xml);
5279 switch (enum_op) {
5280 case JSENUMERATE_INIT:
5281 if (length == 0) {
5282 cursor = NULL;
5283 } else {
5284 cursor = (JSXMLArrayCursor *) JS_malloc(cx, sizeof *cursor);
5285 if (!cursor)
5286 return JS_FALSE;
5287 XMLArrayCursorInit(cursor, &xml->xml_kids);
5288 }
5289 *statep = PRIVATE_TO_JSVAL(cursor);
5290 if (idp)
5291 *idp = INT_TO_JSID(length);
5292 break;
5294 case JSENUMERATE_NEXT:
5295 cursor = JSVAL_TO_PRIVATE(*statep);
5296 if (cursor && cursor->array && (index = cursor->index) < length) {
5297 *idp = INT_TO_JSID(index);
5298 cursor->index = index + 1;
5299 break;
5300 }
5301 /* FALL THROUGH */
5303 case JSENUMERATE_DESTROY:
5304 cursor = JSVAL_TO_PRIVATE(*statep);
5305 if (cursor) {
5306 XMLArrayCursorFinish(cursor);
5307 JS_free(cx, cursor);
5308 }
5309 *statep = JSVAL_NULL;
5310 break;
5311 }
5312 return JS_TRUE;
5313 }
5315 static JSBool
5316 xml_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
5317 {
5318 return JS_TRUE;
5319 }
5321 static uint32
5322 xml_mark(JSContext *cx, JSObject *obj, void *arg)
5323 {
5324 JSXML *xml;
5326 xml = (JSXML *) JS_GetPrivate(cx, obj);
5327 JS_MarkGCThing(cx, xml, js_private_str, arg);
5328 return js_Mark(cx, obj, arg);
5329 }
5331 static void
5332 xml_clear(JSContext *cx, JSObject *obj)
5333 {
5334 }
5336 static JSBool
5337 HasSimpleContent(JSXML *xml)
5338 {
5339 JSXML *kid;
5340 JSBool simple;
5341 uint32 i, n;
5343 again:
5344 switch (xml->xml_class) {
5345 case JSXML_CLASS_COMMENT:
5346 case JSXML_CLASS_PROCESSING_INSTRUCTION:
5347 return JS_FALSE;
5348 case JSXML_CLASS_LIST:
5349 if (xml->xml_kids.length == 0)
5350 return JS_TRUE;
5351 if (xml->xml_kids.length == 1) {
5352 kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5353 if (kid) {
5354 xml = kid;
5355 goto again;
5356 }
5357 }
5358 /* FALL THROUGH */
5359 default:
5360 simple = JS_TRUE;
5361 for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5362 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5363 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5364 simple = JS_FALSE;
5365 break;
5366 }
5367 }
5368 return simple;
5369 }
5370 }
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 {
5378 JSXML *xml;
5379 JSTempValueRooter tvr;
5380 jsval roots[2];
5381 enum {
5382 FUN_ROOT = 0,
5383 OBJ_ROOT = 1
5384 };
5386 JS_ASSERT(JS_InstanceOf(cx, obj, &js_XMLClass, NULL));
5387 xml = (JSXML *) JS_GetPrivate(cx, obj);
5388 memset(roots, 0, sizeof(roots));
5389 JS_PUSH_TEMP_ROOT(cx, sizeof roots / sizeof *roots, roots, &tvr);
5391 /* From this point the control must flow through out: or bad: */
5392 retry:
5393 if (!GetFunction(cx, obj, xml, id, &roots[FUN_ROOT]))
5394 goto bad;
5395 if (JSVAL_IS_VOID(roots[FUN_ROOT]) && OBJECT_IS_XML(cx, obj)) {
5396 if (xml->xml_class == JSXML_CLASS_LIST) {
5397 if (xml->xml_kids.length == 1) {
5398 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5399 if (xml) {
5400 obj = js_GetXMLObject(cx, xml);
5401 if (!obj)
5402 goto bad;
5403 roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
5404 goto retry;
5405 }
5406 }
5407 } else if (HasSimpleContent(xml)) {
5408 JSString *str;
5410 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5411 if (!str)
5412 goto bad;
5413 if (!js_ValueToObject(cx, STRING_TO_JSVAL(str), &obj))
5414 goto bad;
5415 roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
5416 if (!js_GetProperty(cx, obj, id, &roots[FUN_ROOT]))
5417 goto bad;
5418 }
5419 }
5420 out:
5421 *vp = roots[FUN_ROOT];
5422 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 cx->newborn[GCX_OBJECT] = (JSGCThing *)obj;
5431 cx->lastInternalResult = roots[FUN_ROOT];
5432 }
5433 JS_POP_TEMP_ROOT(cx, &tvr);
5434 return obj;
5435 bad:
5436 obj = NULL;
5437 goto out;
5438 }
5440 static JSBool
5441 xml_setMethod(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5442 {
5443 return js_SetProperty(cx, obj, id, vp);
5444 }
5446 static JSBool
5447 xml_enumerateValues(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
5448 jsval *statep, jsid *idp, jsval *vp)
5449 {
5450 JSXML *xml, *kid;
5451 uint32 length, index;
5452 JSXMLArrayCursor *cursor;
5453 JSObject *kidobj;
5455 xml = (JSXML *) JS_GetPrivate(cx, obj);
5456 length = JSXML_LENGTH(xml);
5457 JS_ASSERT(INT_FITS_IN_JSVAL(length));
5459 switch (enum_op) {
5460 case JSENUMERATE_INIT:
5461 if (length == 0) {
5462 cursor = NULL;
5463 } else {
5464 cursor = (JSXMLArrayCursor *) JS_malloc(cx, sizeof *cursor);
5465 if (!cursor)
5466 return JS_FALSE;
5467 XMLArrayCursorInit(cursor, &xml->xml_kids);
5468 }
5469 *statep = PRIVATE_TO_JSVAL(cursor);
5470 if (idp)
5471 *idp = INT_TO_JSID(length);
5472 if (vp)
5473 *vp = JSVAL_VOID;
5474 break;
5476 case JSENUMERATE_NEXT:
5477 cursor = JSVAL_TO_PRIVATE(*statep);
5478 if (cursor && cursor->array && (index = cursor->index) < length) {
5479 while (!(kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML))) {
5480 if (++index == length)
5481 goto destroy;
5482 }
5483 kidobj = js_GetXMLObject(cx, kid);
5484 if (!kidobj)
5485 return JS_FALSE;
5486 JS_ASSERT(INT_FITS_IN_JSVAL(index));
5487 *idp = INT_TO_JSID(index);
5488 *vp = OBJECT_TO_JSVAL(kidobj);
5489 cursor->index = index + 1;
5490 break;
5491 }
5492 /* FALL THROUGH */
5494 case JSENUMERATE_DESTROY:
5495 cursor = JSVAL_TO_PRIVATE(*statep);
5496 if (cursor) {
5497 destroy:
5498 XMLArrayCursorFinish(cursor);
5499 JS_free(cx, cursor);
5500 }
5501 *statep = JSVAL_NULL;
5502 break;
5503 }
5504 return JS_TRUE;
5505 }
5507 static JSBool
5508 xml_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
5509 {
5510 JSXML *xml, *vxml;
5511 JSObject *vobj;
5512 JSBool ok;
5513 JSString *str, *vstr;
5514 jsdouble d, d2;
5516 xml = (JSXML *) JS_GetPrivate(cx, obj);
5517 vxml = NULL;
5518 if (!JSVAL_IS_PRIMITIVE(v)) {
5519 vobj = JSVAL_TO_OBJECT(v);
5520 if (OBJECT_IS_XML(cx, vobj))
5521 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
5522 }
5524 if (xml->xml_class == JSXML_CLASS_LIST) {
5525 ok = Equals(cx, xml, v, bp);
5526 } else if (vxml) {
5527 if (vxml->xml_class == JSXML_CLASS_LIST) {
5528 ok = Equals(cx, vxml, OBJECT_TO_JSVAL(obj), bp);
5529 } else {
5530 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 ok = JS_EnterLocalRootScope(cx);
5537 if (ok) {
5538 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5539 vstr = js_ValueToString(cx, v);
5540 ok = str && vstr;
5541 if (ok)
5542 *bp = !js_CompareStrings(str, vstr);
5543 JS_LeaveLocalRootScope(cx);
5544 }
5545 } else {
5546 ok = XMLEquals(cx, xml, vxml, bp);
5547 }
5548 }
5549 } else {
5550 ok = JS_EnterLocalRootScope(cx);
5551 if (ok) {
5552 if (HasSimpleContent(xml)) {
5553 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5554 vstr = js_ValueToString(cx, v);
5555 ok = str && vstr;
5556 if (ok)
5557 *bp = !js_CompareStrings(str, vstr);
5558 } else if (JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v)) {
5559 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5560 if (!str) {
5561 ok = JS_FALSE;
5562 } else if (JSVAL_IS_STRING(v)) {
5563 *bp = !js_CompareStrings(str, JSVAL_TO_STRING(v));
5564 } else {
5565 ok = js_ValueToNumber(cx, STRING_TO_JSVAL(str), &d);
5566 if (ok) {
5567 d2 = JSVAL_IS_INT(v) ? JSVAL_TO_INT(v)
5568 : *JSVAL_TO_DOUBLE(v);
5569 *bp = JSDOUBLE_COMPARE(d, ==, d2, JS_FALSE);
5570 }
5571 }
5572 } else {
5573 *bp = JS_FALSE;
5574 }
5575 JS_LeaveLocalRootScope(cx);
5576 }
5577 }
5578 return ok;
5579 }
5581 static JSBool
5582 xml_concatenate(JSContext *cx, JSObject *obj, jsval v, jsval *vp)
5583 {
5584 JSBool ok;
5585 JSObject *listobj, *robj;
5586 JSXML *list, *lxml, *rxml;
5588 ok = JS_EnterLocalRootScope(cx);
5589 if (!ok)
5590 return JS_FALSE;
5592 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5593 if (!listobj) {
5594 ok = JS_FALSE;
5595 goto out;
5596 }
5598 list = (JSXML *) JS_GetPrivate(cx, listobj);
5599 lxml = (JSXML *) JS_GetPrivate(cx, obj);
5600 ok = Append(cx, list, lxml);
5601 if (!ok)
5602 goto out;
5604 if (VALUE_IS_XML(cx, v)) {
5605 rxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5606 } else {
5607 robj = ToXML(cx, v);
5608 if (!robj) {
5609 ok = JS_FALSE;
5610 goto out;
5611 }
5612 rxml = (JSXML *) JS_GetPrivate(cx, robj);
5613 }
5614 ok = Append(cx, list, rxml);
5615 if (!ok)
5616 goto out;
5618 *vp = OBJECT_TO_JSVAL(listobj);
5619 out:
5620 JS_LeaveLocalRootScope(cx);
5621 return ok;
5622 }
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 };
5643 static JSObjectOps *
5644 xml_getObjectOps(JSContext *cx, JSClass *clasp)
5645 {
5646 return &js_XMLObjectOps.base;
5647 }
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 };
5657 static JSObject *
5658 CallConstructorFunction(JSContext *cx, JSObject *obj, JSClass *clasp,
5659 uintN argc, jsval *argv)
5660 {
5661 JSObject *tmp;
5662 jsval rval;
5664 while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL)
5665 obj = tmp;
5666 if (!JS_CallFunctionName(cx, obj, clasp->name, argc, argv, &rval))
5667 return NULL;
5668 JS_ASSERT(!JSVAL_IS_PRIMITIVE(rval));
5669 return JSVAL_TO_OBJECT(rval);
5670 }
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
5679 static JSBool
5680 xml_addNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5681 jsval *rval)
5682 {
5683 JSXML *xml;
5684 JSObject *nsobj;
5685 JSXMLNamespace *ns;
5687 XML_METHOD_PROLOG;
5688 if (xml->xml_class != JSXML_CLASS_ELEMENT)
5689 return JS_TRUE;
5690 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5691 if (!xml)
5692 return JS_FALSE;
5694 nsobj = CallConstructorFunction(cx, obj, &js_NamespaceClass.base, 1, argv);
5695 if (!nsobj)
5696 return JS_FALSE;
5697 argv[0] = OBJECT_TO_JSVAL(nsobj);
5699 ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
5700 if (!AddInScopeNamespace(cx, xml, ns))
5701 return JS_FALSE;
5702 ns->declared = JS_TRUE;
5703 *rval = OBJECT_TO_JSVAL(obj);
5704 return JS_TRUE;
5705 }
5707 static JSBool
5708 xml_appendChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5709 jsval *rval)
5710 {
5711 JSXML *xml, *vxml;
5712 jsval name, v;
5713 JSObject *vobj;
5715 XML_METHOD_PROLOG;
5716 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5717 if (!xml)
5718 return JS_FALSE;
5720 if (!js_GetAnyName(cx, &name))
5721 return JS_FALSE;
5723 if (!GetProperty(cx, obj, name, &v))
5724 return JS_FALSE;
5726 JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5727 vobj = JSVAL_TO_OBJECT(v);
5728 JS_ASSERT(OBJECT_IS_XML(cx, vobj));
5729 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
5730 JS_ASSERT(vxml->xml_class == JSXML_CLASS_LIST);
5732 if (!IndexToIdVal(cx, vxml->xml_kids.length, &name))
5733 return JS_FALSE;
5734 if (!PutProperty(cx, JSVAL_TO_OBJECT(v), name, &argv[0]))
5735 return JS_FALSE;
5737 *rval = OBJECT_TO_JSVAL(obj);
5738 return JS_TRUE;
5739 }
5741 /* XML and XMLList */
5742 static JSBool
5743 xml_attribute(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5744 jsval *rval)
5745 {
5746 JSXMLQName *qn;
5748 qn = ToAttributeName(cx, argv[0]);
5749 if (!qn)
5750 return JS_FALSE;
5751 argv[0] = OBJECT_TO_JSVAL(qn->object); /* local root */
5752 return GetProperty(cx, obj, argv[0], rval);
5753 }
5755 /* XML and XMLList */
5756 static JSBool
5757 xml_attributes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5758 jsval *rval)
5759 {
5760 jsval name;
5761 JSXMLQName *qn;
5762 JSTempValueRooter tvr;
5763 JSBool ok;
5765 name = ATOM_KEY(cx->runtime->atomState.starAtom);
5766 qn = ToAttributeName(cx, name);
5767 if (!qn)
5768 return JS_FALSE;
5769 name = OBJECT_TO_JSVAL(qn->object);
5770 JS_PUSH_SINGLE_TEMP_ROOT(cx, name, &tvr);
5771 ok = GetProperty(cx, obj, name, rval);
5772 JS_POP_TEMP_ROOT(cx, &tvr);
5773 return ok;
5774 }
5776 /* XML and XMLList */
5777 static JSBool
5778 xml_child_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval name,
5779 jsval *rval)
5780 {
5781 uint32 index;
5782 JSXML *kid;
5783 JSObject *kidobj;
5785 /* ECMA-357 13.4.4.6 */
5786 JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
5788 if (js_IdIsIndex(name, &index)) {
5789 if (index >= JSXML_LENGTH(xml)) {
5790 *rval = JSVAL_VOID;
5791 } else {
5792 kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
5793 if (!kid) {
5794 *rval = JSVAL_VOID;
5795 } else {
5796 kidobj = js_GetXMLObject(cx, kid);
5797 if (!kidobj)
5798 return JS_FALSE;
5799 *rval = OBJECT_TO_JSVAL(kidobj);
5800 }
5801 }
5802 return JS_TRUE;
5803 }
5805 return GetProperty(cx, obj, name, rval);
5806 }
5808 static JSBool
5809 xml_child(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
5810 {
5811 JSXML *xml, *list, *kid, *vxml;
5812 JSXMLArrayCursor cursor;
5813 jsval name, v;
5814 JSObject *listobj, *kidobj;
5816 XML_METHOD_PROLOG;
5817 name = argv[0];
5818 if (xml->xml_class == JSXML_CLASS_LIST) {
5819 /* ECMA-357 13.5.4.4 */
5820 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5821 if (!listobj)
5822 return JS_FALSE;
5824 *rval = OBJECT_TO_JSVAL(listobj);
5825 list = (JSXML *) JS_GetPrivate(cx, listobj);
5826 list->xml_target = xml;
5828 XMLArrayCursorInit(&cursor, &xml->xml_kids);
5829 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5830 kidobj = js_GetXMLObject(cx, kid);
5831 if (!kidobj)
5832 break;
5833 if (!xml_child_helper(cx, kidobj, kid, name, &v))
5834 break;
5835 if (JSVAL_IS_VOID(v)) {
5836 /* The property didn't exist in this kid. */
5837 continue;
5838 }
5840 JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5841 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5842 if ((!JSXML_HAS_KIDS(vxml) || vxml->xml_kids.length != 0) &&
5843 !Append(cx, list, vxml)) {
5844 break;
5845 }
5846 }
5847 XMLArrayCursorFinish(&cursor);
5848 return !kid;
5849 }
5851 return xml_child_helper(cx, obj, xml, name, rval);
5852 }
5854 static JSBool
5855 xml_childIndex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5856 jsval *rval)
5857 {
5858 JSXML *xml, *parent;
5859 uint32 i, n;
5861 XML_METHOD_PROLOG;
5862 parent = xml->parent;
5863 if (!parent || xml->xml_class == JSXML_CLASS_ATTRIBUTE) {
5864 *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN);
5865 return JS_TRUE;
5866 }
5867 for (i = 0, n = JSXML_LENGTH(parent); i < n; i++) {
5868 if (XMLARRAY_MEMBER(&parent->xml_kids, i, JSXML) == xml)
5869 break;
5870 }
5871 JS_ASSERT(i < n);
5872 return js_NewNumberValue(cx, i, rval);
5873 }
5875 /* XML and XMLList */
5876 static JSBool
5877 xml_children(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5878 jsval *rval)
5879 {
5880 jsval name;
5882 name = ATOM_KEY(cx->runtime->atomState.starAtom);
5883 return GetProperty(cx, obj, name, rval);
5884 }
5886 /* XML and XMLList */
5887 static JSBool
5888 xml_comments(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5889 jsval *rval)
5890 {
5891 JSXML *xml, *list, *kid, *vxml;
5892 JSObject *listobj, *kidobj;
5893 JSBool ok;
5894 uint32 i, n;
5895 jsval v;
5897 XML_METHOD_PROLOG;
5898 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5899 if (!listobj)
5900 return JS_FALSE;
5902 *rval = OBJECT_TO_JSVAL(listobj);
5903 list = (JSXML *) JS_GetPrivate(cx, listobj);
5904 list->xml_target = xml;
5906 ok = JS_TRUE;
5908 if (xml->xml_class == JSXML_CLASS_LIST) {
5909 /* 13.5.4.6 Step 2. */
5910 for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5911 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5912 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5913 ok = JS_EnterLocalRootScope(cx);
5914 if (!ok)
5915 break;
5916 kidobj = js_GetXMLObject(cx, kid);
5917 ok = kidobj
5918 ? xml_comments(cx, kidobj, argc, argv, &v)
5919 : JS_FALSE;
5920 JS_LeaveLocalRootScope(cx);
5921 if (!ok)
5922 break;
5923 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5924 if (JSXML_LENGTH(vxml) != 0) {
5925 ok = Append(cx, list, vxml);
5926 if (!ok)
5927 break;
5928 }
5929 }
5930 }
5931 } else {
5932 /* 13.4.4.9 Step 2. */
5933 for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5934 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5935 if (kid && kid->xml_class == JSXML_CLASS_COMMENT) {
5936 ok = Append(cx, list, kid);
5937 if (!ok)
5938 break;
5939 }
5940 }
5941 }
5943 return ok;
5944 }
5946 /* XML and XMLList */
5947 static JSBool
5948 xml_contains(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5949 jsval *rval)
5950 {
5951 JSXML *xml, *kid;
5952 jsval value;
5953 JSBool eq;
5954 JSXMLArrayCursor cursor;
5955 JSObject *kidobj;
5957 XML_METHOD_PROLOG;
5958 value = argv[0];
5959 if (xml->xml_class == JSXML_CLASS_LIST) {
5960 eq = JS_FALSE;
5961 XMLArrayCursorInit(&cursor, &xml->xml_kids);
5962 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5963 kidobj = js_GetXMLObject(cx, kid);
5964 if (!kidobj || !xml_equality(cx, kidobj, value, &eq))
5965 break;
5966 if (eq)
5967 break;
5968 }
5969 XMLArrayCursorFinish(&cursor);
5970 if (kid)
5971 return JS_FALSE;
5972 } else {
5973 if (!xml_equality(cx, obj, value, &eq))
5974 return JS_FALSE;
5975 }
5976 *rval = BOOLEAN_TO_JSVAL(eq);
5977 return JS_TRUE;
5978 }
5980 /* XML and XMLList */
5981 static JSBool
5982 xml_copy(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
5983 {
5984 JSXML *xml, *copy;
5986 XML_METHOD_PROLOG;
5987 copy = DeepCopy(cx, xml, NULL, 0);
5988 if (!copy)
5989 return JS_FALSE;
5990 *rval = OBJECT_TO_JSVAL(copy->object);
5991 return JS_TRUE;
5992 }
5994 /* XML and XMLList */
5995 static JSBool
5996 xml_descendants(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5997 jsval *rval)
5998 {
5999 JSXML *xml, *list;
6000 jsval name;
6002 XML_METHOD_PROLOG;
6003 name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6004 list = Descendants(cx, xml, name);
6005 if (!list)
6006 return JS_FALSE;
6007 *rval = OBJECT_TO_JSVAL(list->object);
6008 return JS_TRUE;
6009 }
6011 /* XML and XMLList */
6012 static JSBool
6013 xml_elements(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6014 jsval *rval)
6015 {
6016 JSXML *xml, *list, *kid, *vxml;
6017 jsval name, v;
6018 JSXMLQName *nameqn;
6019 jsid funid;
6020 JSObject *listobj, *kidobj;
6021 JSBool ok;
6022 JSXMLArrayCursor cursor;
6023 uint32 i, n;
6025 XML_METHOD_PROLOG;
6026 name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6027 nameqn = ToXMLName(cx, name, &funid);
6028 if (!nameqn)
6029 return JS_FALSE;
6030 argv[0] = OBJECT_TO_JSVAL(nameqn->object);
6032 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
6033 if (!listobj)
6034 return JS_FALSE;
6035 *rval = OBJECT_TO_JSVAL(listobj);
6036 if (funid)
6037 return JS_TRUE;
6039 list = (JSXML *) JS_GetPrivate(cx, listobj);
6040 list->xml_target = xml;
6041 list->xml_targetprop = nameqn;
6042 ok = JS_TRUE;
6044 if (xml->xml_class == JSXML_CLASS_LIST) {
6045 /* 13.5.4.6 */
6046 XMLArrayCursorInit(&cursor, &xml->xml_kids);
6047 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
6048 if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6049 ok = JS_EnterLocalRootScope(cx);
6050 if (!ok)
6051 break;
6052 kidobj = js_GetXMLObject(cx, kid);
6053 ok = kidobj
6054 ? xml_elements(cx, kidobj, argc, argv, &v)
6055 : JS_FALSE;
6056 JS_LeaveLocalRootScope(cx);
6057 if (!ok)
6058 break;
6059 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
6060 if (JSXML_LENGTH(vxml) != 0) {
6061 ok = Append(cx, list, vxml);
6062 if (!ok)
6063 break;
6064 }
6065 }
6066 }
6067 XMLArrayCursorFinish(&cursor);
6068 } else {
6069 for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
6070 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6071 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT &&
6072 MatchElemName(nameqn, kid)) {
6073 ok = Append(cx, list, kid);
6074 if (!ok)
6075 break;
6076 }
6077 }
6078 }
6080 return ok;
6081 }
6083 /* XML and XMLList */
6084 static JSBool
6085 xml_hasOwnProperty(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6086 jsval *rval)
6087 {
6088 jsval name;
6089 JSObject *pobj;
6090 JSProperty *prop;
6092 if (!JS_InstanceOf(cx, obj, &js_XMLClass, argv))
6093 return JS_FALSE;
6095 name = argv[0];
6096 if (!HasProperty(cx, obj, name, &pobj, &prop))
6097 return JS_FALSE;
6098 if (!prop) {
6099 return js_HasOwnPropertyHelper(cx, obj, js_LookupProperty, argc, argv,
6100 rval);
6101 }
6102 DROP_PROPERTY(cx, pobj, prop);
6103 *rval = JSVAL_TRUE;
6104 return JS_TRUE;
6105 }
6107 /* XML and XMLList */
6108 static JSBool
6109 xml_hasComplexContent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6110 jsval *rval)
6111 {
6112 JSXML *xml, *kid;
6113 JSObject *kidobj;
6114 uint32 i, n;
6116 XML_METHOD_PROLOG;
6117 again:
6118 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 *rval = JSVAL_FALSE;
6124 break;
6125 case JSXML_CLASS_LIST:
6126 if (xml->xml_kids.length == 0) {
6127 *rval = JSVAL_TRUE;
6128 } else if (xml->xml_kids.length == 1) {
6129 kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
6130 if (kid) {
6131 kidobj = js_GetXMLObject(cx, kid);
6132 if (!kidobj)
6133 return JS_FALSE;
6134 obj = kidobj;
6135 xml = (JSXML *) JS_GetPrivate(cx, obj);
6136 goto again;
6137 }
6138 }
6139 /* FALL THROUGH */
6140 default:
6141 *rval = JSVAL_FALSE;
6142 for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6143 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6144 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
6145 *rval = JSVAL_TRUE;
6146 break;
6147 }
6148 }
6149 break;
6150 }
6151 return JS_TRUE;
6152 }
6154 /* XML and XMLList */
6155 static JSBool
6156 xml_hasSimpleContent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6157 jsval *rval)
6158 {
6159 JSXML *xml;
6161 XML_METHOD_PROLOG;
6162 *rval = BOOLEAN_TO_JSVAL(HasSimpleContent(xml));
6163 return JS_TRUE;
6164 }
6166 typedef struct JSTempRootedNSArray {
6167 JSTempValueRooter tvr;
6168 JSXMLArray array;
6169 jsval value; /* extra root for temporaries */
6170 } JSTempRootedNSArray;
6172 JS_STATIC_DLL_CALLBACK(void)
6173 mark_temp_ns_array(JSContext *cx, JSTempValueRooter *tvr)
6174 {
6175 JSTempRootedNSArray *tmp = (JSTempRootedNSArray *)tvr;
6177 namespace_mark_vector(cx,
6178 (JSXMLNamespace **)tmp->array.vector,
6179 tmp->array.length, NULL);
6180 XMLArrayCursorMark(cx, tmp->array.cursors);
6181 if (JSVAL_IS_GCTHING(tmp->value))
6182 GC_MARK(cx, JSVAL_TO_GCTHING(tmp->value), "temp_ns_array_value", NULL);
6183 }
6185 static void
6186 InitTempNSArray(JSContext *cx, JSTempRootedNSArray *tmp)
6187 {
6188 XMLArrayInit(cx, &tmp->array, 0);
6189 tmp->value = JSVAL_NULL;
6190 JS_PUSH_TEMP_ROOT_MARKER(cx, mark_temp_ns_array, &tmp->tvr);
6191 }
6193 static void
6194 FinishTempNSArray(JSContext *cx, JSTempRootedNSArray *tmp)
6195 {
6196 JS_ASSERT(tmp->tvr.u.marker == mark_temp_ns_array);
6197 JS_POP_TEMP_ROOT(cx, &tmp->tvr);
6198 XMLArrayFinish(cx, &tmp->array);
6199 }
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 {
6208 JSObject *arrayobj;
6209 uint32 i, n;
6210 JSXMLNamespace *ns;
6211 JSObject *nsobj;
6213 arrayobj = js_NewArrayObject(cx, 0, NULL);
6214 if (!arrayobj)
6215 return JS_FALSE;
6216 *rval = OBJECT_TO_JSVAL(arrayobj);
6217 for (i = 0, n = tmp->array.length; i < n; i++) {
6218 ns = XMLARRAY_MEMBER(&tmp->array, i, JSXMLNamespace);
6219 if (!ns)
6220 continue;
6221 nsobj = js_GetXMLNamespaceObject(cx, ns);
6222 if (!nsobj)
6223 return JS_FALSE;
6224 tmp->value = OBJECT_TO_JSVAL(nsobj);
6225 if (!OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(i), &tmp->value))
6226 return JS_FALSE;
6227 }
6228 return JS_TRUE;
6229 }
6231 static JSBool
6232 FindInScopeNamespaces(JSContext *cx, JSXML *xml, JSXMLArray *nsarray)
6233 {
6234 uint32 length, i, j, n;
6235 JSXMLNamespace *ns, *ns2;
6237 length = nsarray->length;
6238 do {
6239 if (xml->xml_class != JSXML_CLASS_ELEMENT)
6240 continue;
6241 for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
6242 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
6243 if (!ns)
6244 continue;
6246 for (j = 0; j < length; j++) {
6247 ns2 = XMLARRAY_MEMBER(nsarray, j, JSXMLNamespace);
6248 if (ns2 &&
6249 ((ns2->prefix && ns->prefix)
6250 ? !js_CompareStrings(ns2->prefix, ns->prefix)
6251 : !js_CompareStrings(ns2->uri, ns->uri))) {
6252 break;
6253 }
6254 }
6256 if (j == length) {
6257 if (!XMLARRAY_APPEND(cx, nsarray, ns))
6258 return JS_FALSE;
6259 ++length;
6260 }
6261 }
6262 } while ((xml = xml->parent) != NULL);
6263 JS_ASSERT(length == nsarray->length);
6265 return JS_TRUE;
6266 }
6268 static JSBool
6269 xml_inScopeNamespaces(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6270 jsval *rval)
6271 {
6272 JSXML *xml;
6273 JSTempRootedNSArray namespaces;
6274 JSBool ok;
6276 XML_METHOD_PROLOG;
6278 InitTempNSArray(cx, &namespaces);
6279 ok = FindInScopeNamespaces(cx, xml, &namespaces.array) &&
6280 TempNSArrayToJSArray(cx, &namespaces, rval);
6281 FinishTempNSArray(cx, &namespaces);
6282 return ok;
6283 }
6285 static JSBool
6286 xml_insertChildAfter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6287 jsval *rval)
6288 {
6289 JSXML *xml, *kid;
6290 jsval arg;
6291 uint32 i;
6293 XML_METHOD_PROLOG;
6294 if (!JSXML_HAS_KIDS(xml))
6295 return JS_TRUE;
6297 arg = argv[0];
6298 if (JSVAL_IS_NULL(arg)) {
6299 kid = NULL;
6300 i = 0;
6301 } else {
6302 if (!VALUE_IS_XML(cx, arg))
6303 return JS_TRUE;
6304 kid = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(arg));
6305 i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
6306 if (i == XML_NOT_FOUND)
6307 return JS_TRUE;
6308 ++i;
6309 }
6311 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6312 if (!xml)
6313 return JS_FALSE;
6314 if (!Insert(cx, xml, i, argv[1]))
6315 return JS_FALSE;
6316 *rval = OBJECT_TO_JSVAL(obj);
6317 return JS_TRUE;
6318 }
6320 static JSBool
6321 xml_insertChildBefore(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6322 jsval *rval)
6323 {
6324 JSXML *xml, *kid;
6325 jsval arg;
6326 uint32 i;
6328 XML_METHOD_PROLOG;
6329 if (!JSXML_HAS_KIDS(xml))
6330 return JS_TRUE;
6332 arg = argv[0];
6333 if (JSVAL_IS_NULL(arg)) {
6334 kid = NULL;
6335 i = xml->xml_kids.length;
6336 } else {
6337 if (!VALUE_IS_XML(cx, arg))
6338 return JS_TRUE;
6339 kid = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(arg));
6340 i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
6341 if (i == XML_NOT_FOUND)
6342 return JS_TRUE;
6343 }
6345 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6346 if (!xml)
6347 return JS_FALSE;
6348 if (!Insert(cx, xml, i, argv[1]))
6349 return JS_FALSE;
6350 *rval = OBJECT_TO_JSVAL(obj);
6351 return JS_TRUE;
6352 }
6354 /* XML and XMLList */
6355 static JSBool
6356 xml_length(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6357 {
6358 JSXML *xml;
6360 XML_METHOD_PROLOG;
6361 if (xml->xml_class != JSXML_CLASS_LIST) {
6362 *rval = JSVAL_ONE;
6363 } else {
6364 if (!js_NewNumberValue(cx, xml->xml_kids.length, rval))
6365 return JS_FALSE;
6366 }
6367 return JS_TRUE;
6368 }
6370 static JSBool
6371 xml_localName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6372 jsval *rval)
6373 {
6374 JSXML *xml;
6376 XML_METHOD_PROLOG;
6377 *rval = xml->name ? STRING_TO_JSVAL(xml->name->localName) : JSVAL_NULL;
6378 return JS_TRUE;
6379 }
6381 static JSBool
6382 xml_name(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6383 {
6384 JSXML *xml;
6385 JSObject *nameobj;
6387 XML_METHOD_PROLOG;
6388 if (!xml->name) {
6389 *rval = JSVAL_NULL;
6390 } else {
6391 nameobj = js_GetXMLQNameObject(cx, xml->name);
6392 if (!nameobj)
6393 return JS_FALSE;
6394 *rval = OBJECT_TO_JSVAL(nameobj);
6395 }
6396 return JS_TRUE;
6397 }
6399 static JSBool
6400 xml_namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6401 jsval *rval)
6402 {
6403 JSXML *xml;
6404 JSString *prefix;
6405 JSTempRootedNSArray inScopeNSes;
6406 JSBool ok;
6407 jsuint i, length;
6408 JSXMLNamespace *ns;
6409 JSObject *nsobj;
6411 XML_METHOD_PROLOG;
6412 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 *rval = JSVAL_NULL;
6417 return JS_TRUE;
6418 }
6420 if (argc == 0) {
6421 prefix = NULL;
6422 } else {
6423 prefix = js_ValueToString(cx, argv[0]);
6424 if (!prefix)
6425 return JS_FALSE;
6426 argv[0] = STRING_TO_JSVAL(prefix); /* local root */
6427 }
6429 /* After this point the control must flow through label out. */
6430 InitTempNSArray(cx, &inScopeNSes);
6431 ok = FindInScopeNamespaces(cx, xml, &inScopeNSes.array);
6432 if (!ok)
6433 goto out;
6435 if (!prefix) {
6436 ns = GetNamespace(cx, xml->name, &inScopeNSes.array);
6437 if (!ns) {
6438 ok = JS_FALSE;
6439 goto out;
6440 }
6441 } else {
6442 ns = NULL;
6443 for (i = 0, length = inScopeNSes.array.length; i < length; i++) {
6444 ns = XMLARRAY_MEMBER(&inScopeNSes.array, i, JSXMLNamespace);
6445 if (ns && ns->prefix && !js_CompareStrings(ns->prefix, prefix))
6446 break;
6447 ns = NULL;
6448 }
6449 }
6451 if (!ns) {
6452 *rval = JSVAL_VOID;
6453 } else {
6454 nsobj = js_GetXMLNamespaceObject(cx, ns);
6455 if (!nsobj) {
6456 ok = JS_FALSE;
6457 goto out;
6458 }
6459 *rval = OBJECT_TO_JSVAL(nsobj);
6460 }
6462 out:
6463 FinishTempNSArray(cx, &inScopeNSes);
6464 return JS_TRUE;
6465 }
6467 static JSBool
6468 xml_namespaceDeclarations(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6469 jsval *rval)
6470 {
6471 JSXML *xml, *yml;
6472 JSBool ok;
6473 JSTempRootedNSArray ancestors, declared;
6474 uint32 i, n;
6475 JSXMLNamespace *ns;
6477 XML_METHOD_PROLOG;
6478 if (JSXML_HAS_VALUE(xml) || xml->xml_class == JSXML_CLASS_LIST)
6479 return JS_TRUE;
6481 /* From here, control flow must goto out to finish these arrays. */
6482 ok = JS_TRUE;
6483 InitTempNSArray(cx, &ancestors);
6484 InitTempNSArray(cx, &declared);
6485 yml = xml;
6487 while ((yml = yml->parent) != NULL) {
6488 JS_ASSERT(yml->xml_class == JSXML_CLASS_ELEMENT);
6489 for (i = 0, n = yml->xml_namespaces.length; i < n; i++) {
6490 ns = XMLARRAY_MEMBER(&yml->xml_namespaces, i, JSXMLNamespace);
6491 if (ns &&
6492 !XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
6493 ok = XMLARRAY_APPEND(cx, &ancestors.array, ns);
6494 if (!ok)
6495 goto out;
6496 }
6497 }
6498 }
6500 for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
6501 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
6502 if (!ns)
6503 continue;
6504 if (!ns->declared)
6505 continue;
6506 if (!XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
6507 ok = XMLARRAY_APPEND(cx, &declared.array, ns);
6508 if (!ok)
6509 goto out;
6510 }
6511 }
6513 ok = TempNSArrayToJSArray(cx, &declared, rval);
6515 out:
6516 /* Finishing must be in reverse order of initialization to follow LIFO. */
6517 FinishTempNSArray(cx, &declared);
6518 FinishTempNSArray(cx, &ancestors);
6519 return ok;
6520 }
6522 static const char js_attribute_str[] = "attribute";
6523 static const char js_text_str[] = "text";
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 };
6535 static JSBool
6536 xml_nodeKind(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6537 jsval *rval)
6538 {
6539 JSXML *xml;
6540 JSString *str;
6542 XML_METHOD_PROLOG;
6543 str = JS_InternString(cx, js_xml_class_str[xml->xml_class]);
6544 if (!str)
6545 return JS_FALSE;
6546 *rval = STRING_TO_JSVAL(str);
6547 return JS_TRUE;
6548 }
6550 static JSBool
6551 NormalizingDelete(JSContext *cx, JSObject *obj, JSXML *xml, jsval id)
6552 {
6553 jsval junk;
6555 if (xml->xml_class == JSXML_CLASS_LIST)
6556 return DeleteProperty(cx, obj, id, &junk);
6557 return DeleteByIndex(cx, xml, id, &junk);
6558 }
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 {
6567 const jschar *cp, *end;
6569 cp = JSSTRING_CHARS(str);
6570 end = cp + JSSTRING_LENGTH(str);
6571 while (cp < end) {
6572 if (!JS_ISXMLSPACE(*cp))
6573 return JS_FALSE;
6574 ++cp;
6575 }
6576 return JS_TRUE;
6577 }
6579 /* XML and XMLList */
6580 static JSBool
6581 xml_normalize(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6582 jsval *rval)
6583 {
6584 JSXML *xml, *kid, *kid2;
6585 uint32 i, n;
6586 JSObject *kidobj;
6587 JSString *str;
6588 jsval junk;
6590 XML_METHOD_PROLOG;
6591 *rval = OBJECT_TO_JSVAL(obj);
6592 if (!JSXML_HAS_KIDS(xml))
6593 return JS_TRUE;
6595 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6596 if (!xml)
6597 return JS_FALSE;
6599 for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6600 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6601 if (!kid)
6602 continue;
6603 if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6604 kidobj = js_GetXMLObject(cx, kid);
6605 if (!kidobj || !xml_normalize(cx, kidobj, argc, argv, &junk))
6606 return JS_FALSE;
6607 } else if (kid->xml_class == JSXML_CLASS_TEXT) {
6608 while (i + 1 < n &&
6609 (kid2 = XMLARRAY_MEMBER(&xml->xml_kids, i + 1, JSXML)) &&
6610 kid2->xml_class == JSXML_CLASS_TEXT) {
6611 str = js_ConcatStrings(cx, kid->xml_value, kid2->xml_value);
6612 if (!str)
6613 return JS_FALSE;
6614 if (!NormalizingDelete(cx, obj, xml, INT_TO_JSVAL(i + 1)))
6615 return JS_FALSE;
6616 n = xml->xml_kids.length;
6617 kid->xml_value = str;
6618 }
6619 if (IS_EMPTY(kid->xml_value) || IsXMLSpace(kid->xml_value)) {
6620 if (!NormalizingDelete(cx, obj, xml, INT_TO_JSVAL(i)))
6621 return JS_FALSE;
6622 n = xml->xml_kids.length;
6623 --i;
6624 }
6625 }
6626 }
6628 return JS_TRUE;
6629 }
6631 /* XML and XMLList */
6632 static JSBool
6633 xml_parent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6634 {
6635 JSXML *xml, *parent, *kid;
6636 uint32 i, n;
6637 JSObject *parentobj;
6639 XML_METHOD_PROLOG;
6640 parent = xml->parent;
6641 if (xml->xml_class == JSXML_CLASS_LIST) {
6642 *rval = JSVAL_VOID;
6643 n = xml->xml_kids.length;
6644 if (n == 0)
6645 return JS_TRUE;
6647 kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
6648 if (!kid)
6649 return JS_TRUE;
6650 parent = kid->parent;
6651 for (i = 1; i < n; i++) {
6652 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6653 if (kid && kid->parent != parent)
6654 return JS_TRUE;
6655 }
6656 }
6658 if (!parent) {
6659 *rval = JSVAL_NULL;
6660 return JS_TRUE;
6661 }
6663 parentobj = js_GetXMLObject(cx, parent);
6664 if (!parentobj)
6665 return JS_FALSE;
6666 *rval = OBJECT_TO_JSVAL(parentobj);
6667 return JS_TRUE;
6668 }
6670 /* XML and XMLList */
6671 static JSBool
6672 xml_processingInstructions(JSContext *cx, JSObject *obj, uintN argc,
6673 jsval *argv, jsval *rval)
6674 {
6675 JSXML *xml, *list, *kid, *vxml;
6676 jsval name, v;
6677 JSXMLQName *nameqn;
6678 jsid funid;
6679 JSObject *listobj, *kidobj;
6680 JSBool ok;
6681 JSXMLArrayCursor cursor;
6682 uint32 i, n;
6684 XML_METHOD_PROLOG;
6685 name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6686 nameqn = ToXMLName(cx, name, &funid);
6687 if (!nameqn)
6688 return JS_FALSE;
6689 argv[0] = OBJECT_TO_JSVAL(nameqn->object);
6691 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
6692 if (!listobj)
6693 return JS_FALSE;
6694 *rval = OBJECT_TO_JSVAL(listobj);
6695 if (funid)
6696 return JS_TRUE;
6698 list = (JSXML *) JS_GetPrivate(cx, listobj);
6699 list->xml_target = xml;
6700 list->xml_targetprop = nameqn;
6701 ok = JS_TRUE;
6703 if (xml->xml_class == JSXML_CLASS_LIST) {
6704 /* 13.5.4.17 Step 4 (misnumbered 9 -- Erratum?). */
6705 XMLArrayCursorInit(&cursor, &xml->xml_kids);
6706 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
6707 if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6708 ok = JS_EnterLocalRootScope(cx);
6709 if (!ok)
6710 break;
6711 kidobj = js_GetXMLObject(cx, kid);
6712 ok = kidobj
6713 ? xml_processingInstructions(cx, kidobj, argc, argv, &v)
6714 : JS_FALSE;
6715 JS_LeaveLocalRootScope(cx);
6716 if (!ok)
6717 break;
6718 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
6719 if (JSXML_LENGTH(vxml) != 0) {
6720 ok = Append(cx, list, vxml);
6721 if (!ok)
6722 break;
6723 }
6724 }
6725 }
6726 XMLArrayCursorFinish(&cursor);
6727 } else {
6728 /* 13.4.4.28 Step 4. */
6729 for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
6730 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6731 if (kid && kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
6732 (IS_STAR(nameqn->localName) ||
6733 !js_CompareStrings(nameqn->localName, kid->name->localName))) {
6734 ok = Append(cx, list, kid);
6735 if (!ok)
6736 break;
6737 }
6738 }
6739 }
6741 return ok;
6742 }
6744 static JSBool
6745 xml_prependChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6746 jsval *rval)
6747 {
6748 JSXML *xml;
6750 XML_METHOD_PROLOG;
6751 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6752 if (!xml)
6753 return JS_FALSE;
6754 *rval = OBJECT_TO_JSVAL(obj);
6755 return Insert(cx, xml, 0, argv[0]);
6756 }
6758 /* XML and XMLList */
6759 static JSBool
6760 xml_propertyIsEnumerable(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6761 jsval *rval)
6762 {
6763 JSXML *xml;
6764 jsval name;
6765 uint32 index;
6767 XML_METHOD_PROLOG;
6768 name = argv[0];
6769 *rval = JSVAL_FALSE;
6770 if (js_IdIsIndex(name, &index)) {
6771 if (xml->xml_class == JSXML_CLASS_LIST) {
6772 /* 13.5.4.18. */
6773 *rval = BOOLEAN_TO_JSVAL(index < xml->xml_kids.length);
6774 } else {
6775 /* 13.4.4.30. */
6776 *rval = BOOLEAN_TO_JSVAL(index == 0);
6777 }
6778 }
6779 return JS_TRUE;
6780 }
6782 static JSBool
6783 namespace_full_match(const void *a, const void *b)
6784 {
6785 const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
6786 const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
6788 if (nsa->prefix && nsb->prefix &&
6789 js_CompareStrings(nsa->prefix, nsb->prefix)) {
6790 return JS_FALSE;
6791 }
6792 return !js_CompareStrings(nsa->uri, nsb->uri);
6793 }
6795 static JSBool
6796 xml_removeNamespace_helper(JSContext *cx, JSXML *xml, JSXMLNamespace *ns)
6797 {
6798 JSXMLNamespace *thisns, *attrns;
6799 uint32 i, n;
6800 JSXML *attr, *kid;
6802 thisns = GetNamespace(cx, xml->name, &xml->xml_namespaces);
6803 JS_ASSERT(thisns);
6804 if (thisns == ns)
6805 return JS_TRUE;
6807 for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
6808 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
6809 if (!attr)
6810 continue;
6811 attrns = GetNamespace(cx, attr->name, &xml->xml_namespaces);
6812 JS_ASSERT(attrns);
6813 if (attrns == ns)
6814 return JS_TRUE;
6815 }
6817 i = XMLARRAY_FIND_MEMBER(&xml->xml_namespaces, ns, namespace_full_match);
6818 if (i != XML_NOT_FOUND)
6819 XMLArrayDelete(cx, &xml->xml_namespaces, i, JS_TRUE);
6821 for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6822 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6823 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
6824 if (!xml_removeNamespace_helper(cx, kid, ns))
6825 return JS_FALSE;
6826 }
6827 }
6828 return JS_TRUE;
6829 }
6831 static JSBool
6832 xml_removeNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6833 jsval *rval)
6834 {
6835 JSXML *xml;
6836 JSObject *nsobj;
6837 JSXMLNamespace *ns;
6839 XML_METHOD_PROLOG;
6840 *rval = OBJECT_TO_JSVAL(obj);
6841 if (xml->xml_class != JSXML_CLASS_ELEMENT)
6842 return JS_TRUE;
6843 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6844 if (!xml)
6845 return JS_FALSE;
6847 nsobj = CallConstructorFunction(cx, obj, &js_NamespaceClass.base, 1, argv);
6848 if (!nsobj)
6849 return JS_FALSE;
6850 argv[0] = OBJECT_TO_JSVAL(nsobj);
6851 ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
6853 /* NOTE: remove ns from each ancestor if not used by that ancestor. */
6854 return xml_removeNamespace_helper(cx, xml, ns);
6855 }
6857 static JSBool
6858 xml_replace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6859 {
6860 JSXML *xml, *vxml, *kid;
6861 jsval name, value, id, junk;
6862 uint32 index;
6863 JSObject *nameobj;
6864 JSXMLQName *nameqn;
6866 XML_METHOD_PROLOG;
6867 *rval = OBJECT_TO_JSVAL(obj);
6868 if (xml->xml_class != JSXML_CLASS_ELEMENT)
6869 return JS_TRUE;
6871 value = argv[1];
6872 vxml = VALUE_IS_XML(cx, value)
6873 ? (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(value))
6874 : NULL;
6875 if (!vxml) {
6876 if (!JS_ConvertValue(cx, value, JSTYPE_STRING, &argv[1]))
6877 return JS_FALSE;
6878 value = argv[1];
6879 } else {
6880 vxml = DeepCopy(cx, vxml, NULL, 0);
6881 if (!vxml)
6882 return JS_FALSE;
6883 value = argv[1] = OBJECT_TO_JSVAL(vxml->object);
6884 }
6886 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6887 if (!xml)
6888 return JS_FALSE;
6890 name = argv[0];
6891 if (js_IdIsIndex(name, &index))
6892 return Replace(cx, xml, name, value);
6894 /* Call function QName per spec, not ToXMLName, to avoid attribute names. */
6895 nameobj = CallConstructorFunction(cx, obj, &js_QNameClass.base, 1, &name);
6896 if (!nameobj)
6897 return JS_FALSE;
6898 argv[0] = OBJECT_TO_JSVAL(nameobj);
6899 nameqn = (JSXMLQName *) JS_GetPrivate(cx, nameobj);
6901 id = JSVAL_VOID;
6902 index = xml->xml_kids.length;
6903 while (index != 0) {
6904 --index;
6905 kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
6906 if (kid && MatchElemName(nameqn, kid)) {
6907 if (!JSVAL_IS_VOID(id) && !DeleteByIndex(cx, xml, id, &junk))
6908 return JS_FALSE;
6909 if (!IndexToIdVal(cx, index, &id))
6910 return JS_FALSE;
6911 }
6912 }
6913 if (JSVAL_IS_VOID(id))
6914 return JS_TRUE;
6915 return Replace(cx, xml, id, value);
6916 }
6918 static JSBool
6919 xml_setChildren(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6920 jsval *rval)
6921 {
6922 if (!PutProperty(cx, obj, ATOM_KEY(cx->runtime->atomState.starAtom),
6923 &argv[0])) {
6924 return JS_FALSE;
6925 }
6927 *rval = OBJECT_TO_JSVAL(obj);
6928 return JS_TRUE;
6929 }
6931 static JSBool
6932 xml_setLocalName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6933 jsval *rval)
6934 {
6935 JSXML *xml;
6936 jsval name;
6937 JSXMLQName *nameqn;
6938 JSString *namestr;
6940 XML_METHOD_PROLOG;
6941 if (!JSXML_HAS_NAME(xml))
6942 return JS_TRUE;
6944 name = argv[0];
6945 if (!JSVAL_IS_PRIMITIVE(name) &&
6946 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(name)) == &js_QNameClass.base) {
6947 nameqn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(name));
6948 namestr = nameqn->localName;
6949 } else {
6950 if (!JS_ConvertValue(cx, name, JSTYPE_STRING, &argv[0]))
6951 return JS_FALSE;
6952 name = argv[0];
6953 namestr = JSVAL_TO_STRING(name);
6954 }
6956 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6957 if (!xml)
6958 return JS_FALSE;
6959 xml->name->localName = namestr;
6960 return JS_TRUE;
6961 }
6963 static JSBool
6964 xml_setName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6965 {
6966 JSXML *xml, *nsowner;
6967 jsval name;
6968 JSXMLQName *nameqn;
6969 JSObject *nameobj;
6970 JSXMLArray *nsarray;
6971 uint32 i, n;
6972 JSXMLNamespace *ns;
6974 XML_METHOD_PROLOG;
6975 if (!JSXML_HAS_NAME(xml))
6976 return JS_TRUE;
6978 name = argv[0];
6979 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 name = argv[0] = STRING_TO_JSVAL(nameqn->localName);
6984 }
6986 nameobj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &name);
6987 if (!nameobj)
6988 return JS_FALSE;
6989 nameqn = (JSXMLQName *) JS_GetPrivate(cx, nameobj);
6991 /* ECMA-357 13.4.4.35 Step 4. */
6992 if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
6993 nameqn->uri = cx->runtime->emptyString;
6995 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6996 if (!xml)
6997 return JS_FALSE;
6998 xml->name = nameqn;
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 if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7008 nsowner = xml;
7009 } else {
7010 if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
7011 return JS_TRUE;
7012 nsowner = xml->parent;
7013 }
7015 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 ns = GetNamespace(cx, nameqn, &nsowner->xml_namespaces);
7027 if (!ns)
7028 return JS_FALSE;
7030 /* XXXbe have to test membership to see whether GetNamespace added */
7031 if (XMLARRAY_HAS_MEMBER(&nsowner->xml_namespaces, ns, NULL))
7032 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 JS_ASSERT(!IS_EMPTY(nameqn->uri));
7050 nsarray = &nsowner->xml_namespaces;
7051 for (i = 0, n = nsarray->length; i < n; i++) {
7052 ns = XMLARRAY_MEMBER(nsarray, i, JSXMLNamespace);
7053 if (ns && !js_CompareStrings(ns->uri, nameqn->uri)) {
7054 nameqn->prefix = ns->prefix;
7055 return JS_TRUE;
7056 }
7057 }
7059 ns = js_NewXMLNamespace(cx, NULL, nameqn->uri, JS_TRUE);
7060 if (!ns)
7061 return JS_FALSE;
7062 }
7064 return AddInScopeNamespace(cx, nsowner, ns);
7065 }
7067 static JSBool
7068 xml_setNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7069 jsval *rval)
7070 {
7071 JSXML *xml, *nsowner;
7072 JSObject *nsobj, *qnobj;
7073 JSXMLNamespace *ns;
7074 jsval qnargv[2];
7076 XML_METHOD_PROLOG;
7077 if (xml->xml_class != JSXML_CLASS_ELEMENT &&
7078 xml->xml_class != JSXML_CLASS_ATTRIBUTE) {
7079 return JS_TRUE;
7080 }
7082 xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
7083 if (!xml || !js_GetXMLQNameObject(cx, xml->name))
7084 return JS_FALSE;
7086 nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, obj, 1, argv);
7087 if (!nsobj)
7088 return JS_FALSE;
7089 ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
7090 ns->declared = JS_TRUE;
7092 qnargv[0] = argv[0] = OBJECT_TO_JSVAL(nsobj);
7093 qnargv[1] = OBJECT_TO_JSVAL(xml->name->object);
7094 qnobj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, qnargv);
7095 if (!qnobj)
7096 return JS_FALSE;
7098 xml->name = (JSXMLQName *) JS_GetPrivate(cx, qnobj);
7100 /*
7101 * Erratum: the spec fails to update the governing in-scope namespaces.
7102 * See the erratum noted in xml_setName, above.
7103 */
7104 if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7105 nsowner = xml;
7106 } else {
7107 if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
7108 return JS_TRUE;
7109 nsowner = xml->parent;
7110 }
7111 return AddInScopeNamespace(cx, nsowner, ns);
7112 }
7114 /* XML and XMLList */
7115 static JSBool
7116 xml_text(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7117 {
7118 JSXML *xml, *list, *kid, *vxml;
7119 JSObject *listobj, *kidobj;
7120 uint32 i, n;
7121 JSBool ok;
7122 jsval v;
7124 XML_METHOD_PROLOG;
7125 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
7126 if (!listobj)
7127 return JS_FALSE;
7129 *rval = OBJECT_TO_JSVAL(listobj);
7130 list = (JSXML *) JS_GetPrivate(cx, listobj);
7131 list->xml_target = xml;
7133 if (xml->xml_class == JSXML_CLASS_LIST) {
7134 ok = JS_TRUE;
7135 for (i = 0, n = xml->xml_kids.length; i < n; i++) {
7136 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
7137 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
7138 ok = JS_EnterLocalRootScope(cx);
7139 if (!ok)
7140 break;
7141 kidobj = js_GetXMLObject(cx, kid);
7142 ok = kidobj
7143 ? xml_text(cx, kidobj, argc, argv, &v)
7144 : JS_FALSE;
7145 JS_LeaveLocalRootScope(cx);
7146 if (!ok)
7147 return JS_FALSE;
7148 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
7149 if (JSXML_LENGTH(vxml) != 0 && !Append(cx, list, vxml))
7150 return JS_FALSE;
7151 }
7152 }
7153 } else {
7154 for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
7155 kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
7156 if (kid && kid->xml_class == JSXML_CLASS_TEXT) {
7157 if (!Append(cx, list, kid))
7158 return JS_FALSE;
7159 }
7160 }
7161 }
7162 return JS_TRUE;
7163 }
7165 /* XML and XMLList */
7166 static JSBool
7167 xml_toXMLString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7168 jsval *rval)
7169 {
7170 JSString *str;
7172 str = ToXMLString(cx, OBJECT_TO_JSVAL(obj));
7173 if (!str)
7174 return JS_FALSE;
7175 *rval = STRING_TO_JSVAL(str);
7176 return JS_TRUE;
7177 }
7179 /* XML and XMLList */
7180 static JSString *
7181 xml_toString_helper(JSContext *cx, JSXML *xml)
7182 {
7183 JSString *str, *kidstr;
7184 JSXML *kid;
7185 JSXMLArrayCursor cursor;
7187 if (xml->xml_class == JSXML_CLASS_ATTRIBUTE ||
7188 xml->xml_class == JSXML_CLASS_TEXT) {
7189 return xml->xml_value;
7190 }
7192 if (!HasSimpleContent(xml))
7193 return ToXMLString(cx, OBJECT_TO_JSVAL(xml->object));
7195 str = cx->runtime->emptyString;
7196 JS_EnterLocalRootScope(cx);
7197 XMLArrayCursorInit(&cursor, &xml->xml_kids);
7198 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
7199 if (kid->xml_class != JSXML_CLASS_COMMENT &&
7200 kid->xml_class != JSXML_CLASS_PROCESSING_INSTRUCTION) {
7201 kidstr = xml_toString_helper(cx, kid);
7202 if (!kidstr) {
7203 str = NULL;
7204 break;
7205 }
7206 str = js_ConcatStrings(cx, str, kidstr);
7207 if (!str)
7208 break;
7209 }
7210 }
7211 XMLArrayCursorFinish(&cursor);
7212 JS_LeaveLocalRootScope(cx);
7213 return str;
7214 }
7216 static JSBool
7217 xml_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7218 jsval *rval)
7219 {
7220 JSXML *xml;
7221 JSString *str;
7223 XML_METHOD_PROLOG;
7224 str = xml_toString_helper(cx, xml);
7225 if (!str)
7226 return JS_FALSE;
7227 *rval = STRING_TO_JSVAL(str);
7228 return JS_TRUE;
7229 }
7231 /* XML and XMLList */
7232 static JSBool
7233 xml_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7234 {
7235 *rval = OBJECT_TO_JSVAL(obj);
7236 return JS_TRUE;
7237 }
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 };
7283 static JSBool
7284 CopyXMLSettings(JSContext *cx, JSObject *from, JSObject *to)
7285 {
7286 int i;
7287 const char *name;
7288 jsval v;
7290 for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
7291 name = xml_static_props[i].name;
7292 if (!JS_GetProperty(cx, from, name, &v))
7293 return JS_FALSE;
7294 if (JSVAL_IS_BOOLEAN(v) && !JS_SetProperty(cx, to, name, &v))
7295 return JS_FALSE;
7296 }
7298 name = xml_static_props[i].name;
7299 if (!JS_GetProperty(cx, from, name, &v))
7300 return JS_FALSE;
7301 if (JSVAL_IS_NUMBER(v) && !JS_SetProperty(cx, to, name, &v))
7302 return JS_FALSE;
7303 return JS_TRUE;
7304 }
7306 static JSBool
7307 SetDefaultXMLSettings(JSContext *cx, JSObject *obj)
7308 {
7309 int i;
7310 jsval v;
7312 for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
7313 v = JSVAL_TRUE;
7314 if (!JS_SetProperty(cx, obj, xml_static_props[i].name, &v))
7315 return JS_FALSE;
7316 }
7317 v = INT_TO_JSVAL(2);
7318 return JS_SetProperty(cx, obj, xml_static_props[i].name, &v);
7319 }
7321 static JSBool
7322 xml_settings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7323 {
7324 JSObject *settings;
7326 settings = JS_NewObject(cx, NULL, NULL, NULL);
7327 if (!settings)
7328 return JS_FALSE;
7329 *rval = OBJECT_TO_JSVAL(settings);
7330 return CopyXMLSettings(cx, obj, settings);
7331 }
7333 static JSBool
7334 xml_setSettings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7335 jsval *rval)
7336 {
7337 jsval v;
7338 JSBool ok;
7339 JSObject *settings;
7341 v = argv[0];
7342 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
7343 cx->xmlSettingFlags = 0;
7344 ok = SetDefaultXMLSettings(cx, obj);
7345 } else {
7346 if (JSVAL_IS_PRIMITIVE(v))
7347 return JS_TRUE;
7348 settings = JSVAL_TO_OBJECT(v);
7349 cx->xmlSettingFlags = 0;
7350 ok = CopyXMLSettings(cx, settings, obj);
7351 }
7352 if (ok)
7353 cx->xmlSettingFlags |= XSF_CACHE_VALID;
7354 return ok;
7355 }
7357 static JSBool
7358 xml_defaultSettings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7359 jsval *rval)
7360 {
7361 JSObject *settings;
7363 settings = JS_NewObject(cx, NULL, NULL, NULL);
7364 if (!settings)
7365 return JS_FALSE;
7366 *rval = OBJECT_TO_JSVAL(settings);
7367 return SetDefaultXMLSettings(cx, settings);
7368 }
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 };
7377 static JSBool
7378 XML(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7379 {
7380 jsval v;
7381 JSXML *xml, *copy;
7382 JSObject *xobj, *vobj;
7383 JSClass *clasp;
7385 v = argv[0];
7386 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
7387 v = STRING_TO_JSVAL(cx->runtime->emptyString);
7389 xobj = ToXML(cx, v);
7390 if (!xobj)
7391 return JS_FALSE;
7392 *rval = OBJECT_TO_JSVAL(xobj);
7393 xml = (JSXML *) JS_GetPrivate(cx, xobj);
7395 if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && !JSVAL_IS_PRIMITIVE(v)) {
7396 vobj = JSVAL_TO_OBJECT(v);
7397 clasp = OBJ_GET_CLASS(cx, vobj);
7398 if (clasp == &js_XMLClass ||
7399 (clasp->flags & JSCLASS_DOCUMENT_OBSERVER)) {
7400 /* No need to lock obj, it's newly constructed and thread local. */
7401 copy = DeepCopy(cx, xml, obj, 0);
7402 if (!copy)
7403 return JS_FALSE;
7404 JS_ASSERT(copy->object == obj);
7405 *rval = OBJECT_TO_JSVAL(obj);
7406 return JS_TRUE;
7407 }
7408 }
7409 return JS_TRUE;
7410 }
7412 static JSBool
7413 XMLList(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7414 {
7415 jsval v;
7416 JSObject *vobj, *listobj;
7417 JSXML *xml, *list;
7419 v = argv[0];
7420 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
7421 v = STRING_TO_JSVAL(cx->runtime->emptyString);
7423 if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && !JSVAL_IS_PRIMITIVE(v)) {
7424 vobj = JSVAL_TO_OBJECT(v);
7425 if (OBJECT_IS_XML(cx, vobj)) {
7426 xml = (JSXML *) JS_GetPrivate(cx, vobj);
7427 if (xml->xml_class == JSXML_CLASS_LIST) {
7428 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
7429 if (!listobj)
7430 return JS_FALSE;
7431 *rval = OBJECT_TO_JSVAL(listobj);
7433 list = (JSXML *) JS_GetPrivate(cx, listobj);
7434 if (!Append(cx, list, xml))
7435 return JS_FALSE;
7436 return JS_TRUE;
7437 }
7438 }
7439 }
7441 /* Toggle on XML support since the script has explicitly requested it. */
7442 listobj = ToXMLList(cx, v);
7443 if (!listobj)
7444 return JS_FALSE;
7446 *rval = OBJECT_TO_JSVAL(listobj);
7447 return JS_TRUE;
7448 }
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 *))
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 };
7463 #ifdef DEBUG_notme
7464 JSCList xml_leaks = JS_INIT_STATIC_CLIST(&xml_leaks);
7465 uint32 xml_serial;
7466 #endif
7468 JSXML *
7469 js_NewXML(JSContext *cx, JSXMLClass xml_class)
7470 {
7471 JSXML *xml;
7473 xml = (JSXML *) js_NewGCThing(cx, GCX_XML, sizeof_JSXML[xml_class]);
7474 if (!xml)
7475 return NULL;
7477 xml->object = NULL;
7478 xml->domnode = NULL;
7479 xml->parent = NULL;
7480 xml->name = NULL;
7481 xml->xml_class = xml_class;
7482 xml->xml_flags = 0;
7483 if (JSXML_CLASS_HAS_VALUE(xml_class)) {
7484 xml->xml_value = cx->runtime->emptyString;
7485 } else {
7486 XMLArrayInit(cx, &xml->xml_kids, 0);
7487 if (xml_class == JSXML_CLASS_LIST) {
7488 xml->xml_target = NULL;
7489 xml->xml_targetprop = NULL;
7490 } else {
7491 XMLArrayInit(cx, &xml->xml_namespaces, 0);
7492 XMLArrayInit(cx, &xml->xml_attrs, 0);
7493 }
7494 }
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 return xml;
7503 }
7505 static void
7506 xml_mark_tail(JSContext *cx, JSXML *xml, void *arg)
7507 {
7508 XMLArrayTrim(&xml->xml_kids);
7510 if (xml->xml_class == JSXML_CLASS_LIST) {
7511 if (xml->xml_target)
7512 JS_MarkGCThing(cx, xml->xml_target, "target", arg);
7513 if (xml->xml_targetprop)
7514 JS_MarkGCThing(cx, xml->xml_targetprop, "targetprop", arg);
7515 } else {
7516 namespace_mark_vector(cx,
7517 (JSXMLNamespace **) xml->xml_namespaces.vector,
7518 xml->xml_namespaces.length,
7519 arg);
7520 XMLArrayCursorMark(cx, xml->xml_namespaces.cursors);
7521 XMLArrayTrim(&xml->xml_namespaces);
7523 xml_mark_vector(cx,
7524 (JSXML **) xml->xml_attrs.vector,
7525 xml->xml_attrs.length,
7526 arg);
7527 XMLArrayCursorMark(cx, xml->xml_attrs.cursors);
7528 XMLArrayTrim(&xml->xml_attrs);
7529 }
7530 }
7532 void
7533 js_MarkXML(JSContext *cx, JSXML *xml, void *arg)
7534 {
7535 JS_MarkGCThing(cx, xml->object, js_object_str, arg);
7536 JS_MarkGCThing(cx, xml->name, js_name_str, arg);
7537 JS_MarkGCThing(cx, xml->parent, js_xml_parent_str, arg);
7539 if (JSXML_HAS_VALUE(xml)) {
7540 JS_MarkGCThing(cx, xml->xml_value, "value", arg);
7541 } else {
7542 xml_mark_vector(cx,
7543 (JSXML **) xml->xml_kids.vector,
7544 xml->xml_kids.length,
7545 arg);
7546 XMLArrayCursorMark(cx, xml->xml_kids.cursors);
7548 xml_mark_tail(cx, xml, arg);
7549 }
7550 }
7552 void
7553 js_FinalizeXML(JSContext *cx, JSXML *xml)
7554 {
7555 if (JSXML_HAS_KIDS(xml)) {
7556 XMLArrayFinish(cx, &xml->xml_kids);
7557 if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7558 XMLArrayFinish(cx, &xml->xml_namespaces);
7559 XMLArrayFinish(cx, &xml->xml_attrs);
7560 }
7561 }
7563 #ifdef DEBUG_notme
7564 JS_REMOVE_LINK(&xml->links);
7565 #endif
7567 UNMETER(xml_stats.livexml);
7568 }
7570 JSObject *
7571 js_ParseNodeToXMLObject(JSContext *cx, JSParseNode *pn)
7572 {
7573 jsval nsval;
7574 JSXMLNamespace *ns;
7575 JSXMLArray nsarray;
7576 JSXML *xml;
7578 if (!js_GetDefaultXMLNamespace(cx, &nsval))
7579 return NULL;
7580 JS_ASSERT(!JSVAL_IS_PRIMITIVE(nsval));
7581 ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
7583 if (!XMLArrayInit(cx, &nsarray, 1))
7584 return NULL;
7586 XMLARRAY_APPEND(cx, &nsarray, ns);
7587 xml = ParseNodeToXML(cx, pn, &nsarray, XSF_PRECOMPILED_ROOT);
7588 XMLArrayFinish(cx, &nsarray);
7589 if (!xml)
7590 return NULL;
7592 return xml->object;
7593 }
7595 JSObject *
7596 js_NewXMLObject(JSContext *cx, JSXMLClass xml_class)
7597 {
7598 JSXML *xml;
7599 JSObject *obj;
7600 JSTempValueRooter tvr;
7602 xml = js_NewXML(cx, xml_class);
7603 if (!xml)
7604 return NULL;
7605 JS_PUSH_SINGLE_TEMP_ROOT(cx, OBJECT_TO_JSVAL(xml), &tvr);
7606 obj = js_GetXMLObject(cx, xml);
7607 JS_POP_TEMP_ROOT(cx, &tvr);
7608 return obj;
7609 }
7611 static JSObject *
7612 NewXMLObject(JSContext *cx, JSXML *xml)
7613 {
7614 JSObject *obj;
7616 obj = js_NewObject(cx, &js_XMLClass, NULL, NULL);
7617 if (!obj || !JS_SetPrivate(cx, obj, xml)) {
7618 cx->newborn[GCX_OBJECT] = NULL;
7619 return NULL;
7620 }
7621 METER(xml_stats.xmlobj);
7622 METER(xml_stats.livexmlobj);
7623 return obj;
7624 }
7626 JSObject *
7627 js_GetXMLObject(JSContext *cx, JSXML *xml)
7628 {
7629 JSObject *obj;
7631 obj = xml->object;
7632 if (obj) {
7633 JS_ASSERT(JS_GetPrivate(cx, obj) == xml);
7634 return obj;
7635 }
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 JS_ASSERT(!xml->parent ||
7647 !xml->parent->object ||
7648 JS_IS_OBJ_LOCKED(cx, xml->parent->object));
7650 obj = NewXMLObject(cx, xml);
7651 if (!obj)
7652 return NULL;
7653 xml->object = obj;
7654 return obj;
7655 }
7657 JSObject *
7658 js_InitNamespaceClass(JSContext *cx, JSObject *obj)
7659 {
7660 return JS_InitClass(cx, obj, NULL, &js_NamespaceClass.base, Namespace, 2,
7661 namespace_props, namespace_methods, NULL, NULL);
7662 }
7664 JSObject *
7665 js_InitQNameClass(JSContext *cx, JSObject *obj)
7666 {
7667 return JS_InitClass(cx, obj, NULL, &js_QNameClass.base, QName, 2,
7668 qname_props, qname_methods, NULL, NULL);
7669 }
7671 JSObject *
7672 js_InitAttributeNameClass(JSContext *cx, JSObject *obj)
7673 {
7674 return JS_InitClass(cx, obj, NULL, &js_AttributeNameClass, AttributeName, 2,
7675 qname_props, qname_methods, NULL, NULL);
7676 }
7678 JSObject *
7679 js_InitAnyNameClass(JSContext *cx, JSObject *obj)
7680 {
7681 jsval v;
7683 if (!js_GetAnyName(cx, &v))
7684 return NULL;
7685 return JSVAL_TO_OBJECT(v);
7686 }
7688 JSObject *
7689 js_InitXMLClass(JSContext *cx, JSObject *obj)
7690 {
7691 JSObject *proto, *pobj, *ctor;
7692 JSFunctionSpec *fs;
7693 JSFunction *fun;
7694 JSXML *xml;
7695 JSProperty *prop;
7696 JSScopeProperty *sprop;
7697 jsval cval, argv[1], junk;
7699 /* Define the isXMLName function. */
7700 if (!JS_DefineFunction(cx, obj, js_isXMLName_str, xml_isXMLName, 1, 0))
7701 return NULL;
7703 /* Define the XML class constructor and prototype. */
7704 proto = JS_InitClass(cx, obj, NULL, &js_XMLClass, XML, 1,
7705 NULL, NULL,
7706 xml_static_props, xml_static_methods);
7707 if (!proto)
7708 return NULL;
7710 /*
7711 * XXX Hack alert: expand JS_DefineFunctions here to copy fs->extra into
7712 * fun->spare, clearing fun->extra. No xml_methods require extra local GC
7713 * roots allocated after actual arguments on the VM stack, but we need a
7714 * way to tell which methods work only on XML objects, which work only on
7715 * XMLList objects, and which work on either.
7716 */
7717 for (fs = xml_methods; fs->name; fs++) {
7718 fun = JS_DefineFunction(cx, proto, fs->name, fs->call, fs->nargs,
7719 fs->flags);
7720 if (!fun)
7721 return NULL;
7722 fun->extra = 0;
7723 fun->spare = fs->extra;
7724 }
7726 xml = js_NewXML(cx, JSXML_CLASS_TEXT);
7727 if (!xml || !JS_SetPrivate(cx, proto, xml))
7728 return NULL;
7729 xml->object = proto;
7730 METER(xml_stats.xmlobj);
7731 METER(xml_stats.livexmlobj);
7733 /*
7734 * Prepare to set default settings on the XML constructor we just made.
7735 * NB: We can't use JS_GetConstructor, because it calls OBJ_GET_PROPERTY,
7736 * which is xml_getProperty, which creates a new XMLList every time! We
7737 * must instead call js_LookupProperty directly.
7738 */
7739 if (!js_LookupProperty(cx, proto,
7740 ATOM_TO_JSID(cx->runtime->atomState.constructorAtom),
7741 &pobj, &prop)) {
7742 return NULL;
7743 }
7744 JS_ASSERT(prop);
7745 sprop = (JSScopeProperty *) prop;
7746 JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj)));
7747 cval = OBJ_GET_SLOT(cx, pobj, sprop->slot);
7748 OBJ_DROP_PROPERTY(cx, pobj, prop);
7749 JS_ASSERT(JSVAL_IS_FUNCTION(cx, cval));
7751 /* Set default settings. */
7752 ctor = JSVAL_TO_OBJECT(cval);
7753 argv[0] = JSVAL_VOID;
7754 if (!xml_setSettings(cx, ctor, 1, argv, &junk))
7755 return NULL;
7757 /* Define the XMLList function and give it the same prototype as XML. */
7758 fun = JS_DefineFunction(cx, obj, js_XMLList_str, XMLList, 1, 0);
7759 if (!fun)
7760 return NULL;
7761 if (!js_SetClassPrototype(cx, fun->object, proto,
7762 JSPROP_READONLY | JSPROP_PERMANENT)) {
7763 return NULL;
7764 }
7765 return proto;
7766 }
7768 JSObject *
7769 js_InitXMLClasses(JSContext *cx, JSObject *obj)
7770 {
7771 if (!js_InitNamespaceClass(cx, obj))
7772 return NULL;
7773 if (!js_InitQNameClass(cx, obj))
7774 return NULL;
7775 if (!js_InitAttributeNameClass(cx, obj))
7776 return NULL;
7777 if (!js_InitAnyNameClass(cx, obj))
7778 return NULL;
7779 return js_InitXMLClass(cx, obj);
7780 }
7782 JSBool
7783 js_GetFunctionNamespace(JSContext *cx, jsval *vp)
7784 {
7785 JSRuntime *rt;
7786 JSObject *obj;
7787 JSAtom *atom;
7788 JSString *prefix, *uri;
7790 /* An invalid URI, for internal use only, guaranteed not to collide. */
7791 static const char anti_uri[] = "@mozilla.org/js/function";
7793 rt = cx->runtime;
7794 obj = rt->functionNamespaceObject;
7795 if (!obj) {
7796 atom = js_Atomize(cx, js_function_str, 8, 0);
7797 JS_ASSERT(atom);
7798 prefix = ATOM_TO_STRING(atom);
7800 atom = js_Atomize(cx, anti_uri, sizeof anti_uri - 1, ATOM_PINNED);
7801 if (!atom)
7802 return JS_FALSE;
7803 rt->atomState.lazy.functionNamespaceURIAtom = atom;
7805 uri = ATOM_TO_STRING(atom);
7806 obj = js_NewXMLNamespaceObject(cx, prefix, uri, JS_FALSE);
7807 if (!obj)
7808 return JS_FALSE;
7810 /*
7811 * Avoid entraining any in-scope Object.prototype. The loss of
7812 * Namespace.prototype is not detectable, as there is no way to
7813 * refer to this instance in scripts. When used to qualify method
7814 * names, its prefix and uri references are copied to the QName.
7815 */
7816 OBJ_SET_PROTO(cx, obj, NULL);
7817 OBJ_SET_PARENT(cx, obj, NULL);
7818 rt->functionNamespaceObject = obj;
7819 }
7820 *vp = OBJECT_TO_JSVAL(obj);
7821 return JS_TRUE;
7822 }
7824 /*
7825 * Note the asymmetry between js_GetDefaultXMLNamespace and js_SetDefaultXML-
7826 * Namespace. Get searches fp->scopeChain for JS_DEFAULT_XML_NAMESPACE_ID,
7827 * while Set sets JS_DEFAULT_XML_NAMESPACE_ID in fp->varobj (unless fp is a
7828 * lightweight function activation). There's no requirement that fp->varobj
7829 * lie directly on fp->scopeChain, although it should be reachable using the
7830 * prototype chain from a scope object (cf. JSOPTION_VAROBJFIX in jsapi.h).
7831 *
7832 * If Get can't find JS_DEFAULT_XML_NAMESPACE_ID along the scope chain, it
7833 * creates a default namespace via 'new Namespace()'. In contrast, Set uses
7834 * its v argument as the uri of a new Namespace, with "" as the prefix. See
7835 * ECMA-357 12.1 and 12.1.1. Note that if Set is called with a Namespace n,
7836 * the default XML namespace will be set to ("", n.uri). So the uri string
7837 * is really the only usefully stored value of the default namespace.
7838 */
7839 JSBool
7840 js_GetDefaultXMLNamespace(JSContext *cx, jsval *vp)
7841 {
7842 JSStackFrame *fp;
7843 JSObject *nsobj, *obj, *tmp;
7844 jsval v;
7846 fp = cx->fp;
7847 nsobj = fp->xmlNamespace;
7848 if (nsobj) {
7849 *vp = OBJECT_TO_JSVAL(nsobj);
7850 return JS_TRUE;
7851 }
7853 obj = NULL;
7854 for (tmp = fp->scopeChain; tmp; tmp = OBJ_GET_PARENT(cx, obj)) {
7855 obj = tmp;
7856 if (!OBJ_GET_PROPERTY(cx, obj, JS_DEFAULT_XML_NAMESPACE_ID, &v))
7857 return JS_FALSE;
7858 if (!JSVAL_IS_PRIMITIVE(v)) {
7859 fp->xmlNamespace = JSVAL_TO_OBJECT(v);
7860 *vp = v;
7861 return JS_TRUE;
7862 }
7863 }
7865 nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, obj, 0, NULL);
7866 if (!nsobj)
7867 return JS_FALSE;
7868 v = OBJECT_TO_JSVAL(nsobj);
7869 if (obj &&
7870 !OBJ_DEFINE_PROPERTY(cx, obj, JS_DEFAULT_XML_NAMESPACE_ID, v,
7871 JS_PropertyStub, JS_PropertyStub,
7872 JSPROP_PERMANENT, NULL)) {
7873 return JS_FALSE;
7874 }
7875 fp->xmlNamespace = nsobj;
7876 *vp = v;
7877 return JS_TRUE;
7878 }
7880 JSBool
7881 js_SetDefaultXMLNamespace(JSContext *cx, jsval v)
7882 {
7883 jsval argv[2];
7884 JSObject *nsobj, *varobj;
7885 JSStackFrame *fp;
7887 argv[0] = STRING_TO_JSVAL(cx->runtime->emptyString);
7888 argv[1] = v;
7889 nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL,
7890 2, argv);
7891 if (!nsobj)
7892 return JS_FALSE;
7893 v = OBJECT_TO_JSVAL(nsobj);
7895 fp = cx->fp;
7896 varobj = fp->varobj;
7897 if (varobj) {
7898 if (!OBJ_DEFINE_PROPERTY(cx, varobj, JS_DEFAULT_XML_NAMESPACE_ID, v,
7899 JS_PropertyStub, JS_PropertyStub,
7900 JSPROP_PERMANENT, NULL)) {
7901 return JS_FALSE;
7902 }
7903 } else {
7904 JS_ASSERT(fp->fun && !(fp->fun->flags & JSFUN_HEAVYWEIGHT));
7905 }
7906 fp->xmlNamespace = JSVAL_TO_OBJECT(v);
7907 return JS_TRUE;
7908 }
7910 JSBool
7911 js_ToAttributeName(JSContext *cx, jsval *vp)
7912 {
7913 JSXMLQName *qn;
7915 qn = ToAttributeName(cx, *vp);
7916 if (!qn)
7917 return JS_FALSE;
7918 *vp = OBJECT_TO_JSVAL(qn->object);
7919 return JS_TRUE;
7920 }
7922 JSString *
7923 js_EscapeAttributeValue(JSContext *cx, JSString *str)
7924 {
7925 return EscapeAttributeValue(cx, NULL, str);
7926 }
7928 JSString *
7929 js_AddAttributePart(JSContext *cx, JSBool isName, JSString *str, JSString *str2)
7930 {
7931 size_t len, len2, newlen;
7932 jschar *chars;
7934 if (JSSTRING_IS_DEPENDENT(str) ||
7935 !(*js_GetGCThingFlags(str) & GCF_MUTABLE)) {
7936 str = js_NewStringCopyN(cx, JSSTRING_CHARS(str), JSSTRING_LENGTH(str),
7937 0);
7938 if (!str)
7939 return NULL;
7940 }
7942 len = str->length;
7943 len2 = JSSTRING_LENGTH(str2);
7944 newlen = (isName) ? len + 1 + len2 : len + 2 + len2 + 1;
7945 chars = (jschar *) JS_realloc(cx, str->chars, (newlen+1) * sizeof(jschar));
7946 if (!chars)
7947 return NULL;
7949 /*
7950 * Reallocating str (because we know it has no other references) requires
7951 * purging any deflated string cached for it.
7952 */
7953 js_PurgeDeflatedStringCache(str);
7955 str->chars = chars;
7956 str->length = newlen;
7957 chars += len;
7958 if (isName) {
7959 *chars++ = ' ';
7960 js_strncpy(chars, JSSTRING_CHARS(str2), len2);
7961 chars += len2;
7962 } else {
7963 *chars++ = '=';
7964 *chars++ = '"';
7965 js_strncpy(chars, JSSTRING_CHARS(str2), len2);
7966 chars += len2;
7967 *chars++ = '"';
7968 }
7969 *chars = 0;
7970 return str;
7971 }
7973 JSString *
7974 js_EscapeElementValue(JSContext *cx, JSString *str)
7975 {
7976 return EscapeElementValue(cx, NULL, str);
7977 }
7979 JSString *
7980 js_ValueToXMLString(JSContext *cx, jsval v)
7981 {
7982 return ToXMLString(cx, v);
7983 }
7985 static JSBool
7986 anyname_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7987 jsval *rval)
7988 {
7989 *rval = ATOM_KEY(cx->runtime->atomState.starAtom);
7990 return JS_TRUE;
7991 }
7993 JSBool
7994 js_GetAnyName(JSContext *cx, jsval *vp)
7995 {
7996 JSRuntime *rt;
7997 JSObject *obj;
7998 JSXMLQName *qn;
8000 rt = cx->runtime;
8001 obj = rt->anynameObject;
8002 if (!obj) {
8003 qn = js_NewXMLQName(cx, rt->emptyString, rt->emptyString,
8004 ATOM_TO_STRING(rt->atomState.starAtom));
8005 if (!qn)
8006 return JS_FALSE;
8008 obj = js_NewObject(cx, &js_AnyNameClass, NULL, NULL);
8009 if (!obj || !JS_SetPrivate(cx, obj, qn)) {
8010 cx->newborn[GCX_OBJECT] = NULL;
8011 return JS_FALSE;
8012 }
8013 qn->object = obj;
8014 METER(xml_stats.qnameobj);
8015 METER(xml_stats.liveqnameobj);
8017 /*
8018 * Avoid entraining any in-scope Object.prototype. This loses the
8019 * default toString inheritance, but no big deal: we want a better
8020 * custom one for clearer diagnostics.
8021 */
8022 if (!JS_DefineFunction(cx, obj, js_toString_str, anyname_toString,
8023 0, 0)) {
8024 return JS_FALSE;
8025 }
8026 OBJ_SET_PROTO(cx, obj, NULL);
8027 JS_ASSERT(!OBJ_GET_PARENT(cx, obj));
8028 rt->anynameObject = obj;
8029 }
8030 *vp = OBJECT_TO_JSVAL(obj);
8031 return JS_TRUE;
8032 }
8034 JSBool
8035 js_FindXMLProperty(JSContext *cx, jsval name, JSObject **objp, jsval *namep)
8036 {
8037 JSXMLQName *qn;
8038 jsid funid, id;
8039 JSObject *obj, *pobj, *lastobj;
8040 JSProperty *prop;
8041 const char *printable;
8043 qn = ToXMLName(cx, name, &funid);
8044 if (!qn)
8045 return JS_FALSE;
8046 id = OBJECT_TO_JSID(qn->object);
8048 obj = cx->fp->scopeChain;
8049 do {
8050 if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
8051 return JS_FALSE;
8052 if (prop) {
8053 OBJ_DROP_PROPERTY(cx, pobj, prop);
8055 /*
8056 * Call OBJ_THIS_OBJECT to skip any With object that wraps an XML
8057 * object to carry scope chain linkage in js_FilterXMLList.
8058 */
8059 pobj = OBJ_THIS_OBJECT(cx, obj);
8060 if (OBJECT_IS_XML(cx, pobj)) {
8061 *objp = pobj;
8062 *namep = ID_TO_VALUE(id);
8063 return JS_TRUE;
8064 }
8065 }
8067 lastobj = obj;
8068 } while ((obj = OBJ_GET_PARENT(cx, obj)) != NULL);
8070 printable = js_ValueToPrintableString(cx, name);
8071 if (printable) {
8072 JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR,
8073 js_GetErrorMessage, NULL,
8074 JSMSG_UNDEFINED_XML_NAME, printable);
8075 }
8076 return JS_FALSE;
8077 }
8079 JSBool
8080 js_GetXMLProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
8081 {
8082 return GetProperty(cx, obj, name, vp);
8083 }
8085 JSBool
8086 js_SetXMLProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
8087 {
8088 return PutProperty(cx, obj, name, vp);
8089 }
8091 static JSXML *
8092 GetPrivate(JSContext *cx, JSObject *obj, const char *method)
8093 {
8094 JSXML *xml;
8096 xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
8097 if (!xml) {
8098 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
8099 JSMSG_INCOMPATIBLE_METHOD,
8100 js_XML_str, method, OBJ_GET_CLASS(cx, obj)->name);
8101 }
8102 return xml;
8103 }
8105 JSBool
8106 js_GetXMLDescendants(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
8107 {
8108 JSXML *xml, *list;
8110 xml = GetPrivate(cx, obj, "descendants internal method");
8111 if (!xml)
8112 return JS_FALSE;
8114 list = Descendants(cx, xml, id);
8115 if (!list)
8116 return JS_FALSE;
8117 *vp = OBJECT_TO_JSVAL(list->object);
8118 return JS_TRUE;
8119 }
8121 JSBool
8122 js_DeleteXMLListElements(JSContext *cx, JSObject *listobj)
8123 {
8124 JSXML *list;
8125 uint32 n;
8126 jsval junk;
8128 list = (JSXML *) JS_GetPrivate(cx, listobj);
8129 for (n = list->xml_kids.length; n != 0; --n) {
8130 if (!DeleteProperty(cx, listobj, INT_TO_JSID(0), &junk))
8131 return JS_FALSE;
8132 }
8133 return JS_TRUE;
8134 }
8136 JSBool
8137 js_FilterXMLList(JSContext *cx, JSObject *obj, jsbytecode *pc, jsval *vp)
8138 {
8139 JSBool ok, match;
8140 JSStackFrame *fp;
8141 JSObject *scobj, *listobj, *resobj, *withobj, *kidobj;
8142 JSXML *xml, *list, *result, *kid;
8143 JSXMLArrayCursor cursor;
8145 ok = JS_EnterLocalRootScope(cx);
8146 if (!ok)
8147 return JS_FALSE;
8149 /* All control flow after this point must exit via label out or bad. */
8150 fp = cx->fp;
8151 scobj = fp->scopeChain;
8152 withobj = NULL;
8153 xml = GetPrivate(cx, obj, "filtering predicate operator");
8154 if (!xml)
8155 goto bad;
8157 if (xml->xml_class == JSXML_CLASS_LIST) {
8158 list = xml;
8159 } else {
8160 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
8161 if (!listobj)
8162 goto bad;
8163 list = (JSXML *) JS_GetPrivate(cx, listobj);
8164 ok = Append(cx, list, xml);
8165 if (!ok)
8166 goto out;
8167 }
8169 resobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
8170 if (!resobj)
8171 goto bad;
8172 result = (JSXML *) JS_GetPrivate(cx, resobj);
8174 /* Hoist the scope chain update out of the loop over kids. */
8175 withobj = js_NewWithObject(cx, NULL, scobj, -1);
8176 if (!withobj)
8177 goto bad;
8178 fp->scopeChain = withobj;
8180 XMLArrayCursorInit(&cursor, &list->xml_kids);
8181 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
8182 kidobj = js_GetXMLObject(cx, kid);
8183 if (!kidobj)
8184 break;
8185 OBJ_SET_PROTO(cx, withobj, kidobj);
8186 ok = js_Interpret(cx, pc, vp) && js_ValueToBoolean(cx, *vp, &match);
8187 if (ok && match)
8188 ok = Append(cx, result, kid);
8189 if (!ok)
8190 break;
8191 }
8192 XMLArrayCursorFinish(&cursor);
8193 if (!ok)
8194 goto out;
8195 if (kid)
8196 goto bad;
8198 *vp = OBJECT_TO_JSVAL(resobj);
8200 out:
8201 if (withobj) {
8202 fp->scopeChain = scobj;
8203 JS_SetPrivate(cx, withobj, NULL);
8204 }
8205 JS_LeaveLocalRootScope(cx);
8206 return ok;
8207 bad:
8208 ok = JS_FALSE;
8209 goto out;
8210 }
8212 JSObject *
8213 js_ValueToXMLObject(JSContext *cx, jsval v)
8214 {
8215 return ToXML(cx, v);
8216 }
8218 JSObject *
8219 js_ValueToXMLListObject(JSContext *cx, jsval v)
8220 {
8221 return ToXMLList(cx, v);
8222 }
8224 JSObject *
8225 js_CloneXMLObject(JSContext *cx, JSObject *obj)
8226 {
8227 uintN flags;
8228 JSXML *xml;
8230 if (!GetXMLSettingFlags(cx, &flags))
8231 return NULL;
8232 xml = (JSXML *) JS_GetPrivate(cx, obj);
8233 if (flags & (XSF_IGNORE_COMMENTS |
8234 XSF_IGNORE_PROCESSING_INSTRUCTIONS |
8235 XSF_IGNORE_WHITESPACE)) {
8236 xml = DeepCopy(cx, xml, NULL, flags);
8237 if (!xml)
8238 return NULL;
8239 return xml->object;
8240 }
8241 return NewXMLObject(cx, xml);
8242 }
8244 JSObject *
8245 js_NewXMLSpecialObject(JSContext *cx, JSXMLClass xml_class, JSString *name,
8246 JSString *value)
8247 {
8248 uintN flags;
8249 JSObject *obj;
8250 JSXML *xml;
8251 JSXMLQName *qn;
8253 if (!GetXMLSettingFlags(cx, &flags))
8254 return NULL;
8256 if ((xml_class == JSXML_CLASS_COMMENT &&
8257 (flags & XSF_IGNORE_COMMENTS)) ||
8258 (xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
8259 (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS))) {
8260 return js_NewXMLObject(cx, JSXML_CLASS_TEXT);
8261 }
8263 obj = js_NewXMLObject(cx, xml_class);
8264 if (!obj)
8265 return NULL;
8266 xml = (JSXML *) JS_GetPrivate(cx, obj);
8267 if (name) {
8268 qn = js_NewXMLQName(cx, cx->runtime->emptyString, NULL, name);
8269 if (!qn)
8270 return NULL;
8271 xml->name = qn;
8272 }
8273 xml->xml_value = value;
8274 return obj;
8275 }
8277 JSString *
8278 js_MakeXMLCDATAString(JSContext *cx, JSString *str)
8279 {
8280 return MakeXMLCDATAString(cx, NULL, str);
8281 }
8283 JSString *
8284 js_MakeXMLCommentString(JSContext *cx, JSString *str)
8285 {
8286 return MakeXMLCommentString(cx, NULL, str);
8287 }
8289 JSString *
8290 js_MakeXMLPIString(JSContext *cx, JSString *name, JSString *str)
8291 {
8292 return MakeXMLPIString(cx, NULL, name, str);
8293 }
8295 #endif /* JS_HAS_XML_SUPPORT */