/[jscoverage]/trunk/js/jsxml.cpp
ViewVC logotype

Contents of /trunk/js/jsxml.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 507 - (show annotations)
Sun Jan 10 07:23:34 2010 UTC (9 years, 10 months ago) by siliconforks
File size: 244916 byte(s)
Update SpiderMonkey from Firefox 3.6rc1.

</
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=4 sw=4 et tw=78:
3 *
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is SpiderMonkey E4X code, released August, 2004.
18 *
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
39
40 #include "jsversion.h"
41
42 #if JS_HAS_XML_SUPPORT
43
44 #include <math.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include "jstypes.h"
48 #include "jsstdint.h"
49 #include "jsbit.h"
50 #include "jsprf.h"
51 #include "jsutil.h"
52 #include "jsapi.h"
53 #include "jsarray.h"
54 #include "jsatom.h"
55 #include "jsbool.h"
56 #include "jscntxt.h"
57 #include "jsfun.h"
58 #include "jsgc.h"
59 #include "jsinterp.h"
60 #include "jslock.h"
61 #include "jsnum.h"
62 #include "jsobj.h"
63 #include "jsopcode.h"
64 #include "jsparse.h"
65 #include "jsscan.h"
66 #include "jsscope.h"
67 #include "jsscript.h"
68 #include "jsstr.h"
69 #include "jsxml.h"
70 #include "jsstaticcheck.h"
71 #include "jsvector.h"
72
73 #ifdef DEBUG
74 #include <string.h> /* for #ifdef DEBUG memset calls */
75 #endif
76
77 /*
78 * NOTES
79 * - in the js shell, you must use the -x command line option, or call
80 * options('xml') before compiling anything that uses XML literals
81 *
82 * TODO
83 * - XXXbe patrol
84 * - Fuse objects and their JSXML* private data into single GC-things
85 * - fix function::foo vs. x.(foo == 42) collision using proper namespacing
86 * - JSCLASS_DOCUMENT_OBSERVER support -- live two-way binding to Gecko's DOM!
87 * - JS_TypeOfValue sure could use a cleaner interface to "types"
88 */
89
90 #ifdef XML_METERING
91 static struct {
92 jsrefcount qname;
93 jsrefcount xmlnamespace;
94 jsrefcount xml;
95 jsrefcount xmlobj;
96 } xml_stats;
97
98 #define METER(x) JS_ATOMIC_INCREMENT(&(x))
99 #define UNMETER(x) JS_ATOMIC_DECREMENT(&(x))
100 #else
101 #define METER(x) /* nothing */
102 #define UNMETER(x) /* nothing */
103 #endif
104
105 /*
106 * Random utilities and global functions.
107 */
108 const char js_isXMLName_str[] = "isXMLName";
109 const char js_XMLList_str[] = "XMLList";
110 const char js_localName_str[] = "localName";
111 const char js_xml_parent_str[] = "parent";
112 const char js_prefix_str[] = "prefix";
113 const char js_toXMLString_str[] = "toXMLString";
114 const char js_uri_str[] = "uri";
115
116 const char js_amp_entity_str[] = "&amp;";
117 const char js_gt_entity_str[] = "&gt;";
118 const char js_lt_entity_str[] = "&lt;";
119 const char js_quot_entity_str[] = "&quot;";
120
121 #define IS_STAR(str) ((str)->length() == 1 && *(str)->chars() == '*')
122
123 /* Slot indexes shared between Namespace and QName objects. */
124 const uint32 JSSLOT_PREFIX = JSSLOT_PRIVATE;
125 const uint32 JSSLOT_URI = JSSLOT_PRIVATE + 1;
126
127 /* Namespace-specific slot. */
128 const uint32 JSSLOT_DECLARED = JSSLOT_PRIVATE + 2;
129
130 /* QName-specific slot. */
131 const uint32 JSSLOT_LOCAL_NAME = JSSLOT_PRIVATE + 2;
132
133 const uint32 NAMESPACE_RESERVED_SLOTS = 3;
134 const uint32 QNAME_RESERVED_SLOTS = 3;
135
136 static JSBool
137 GetXMLFunction(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
138
139 static JSBool
140 IsQNameClass(JSClass *clasp)
141 {
142 return clasp == &js_QNameClass.base ||
143 clasp == &js_AttributeNameClass ||
144 clasp == &js_AnyNameClass;
145 }
146
147 static JSString *
148 GetSlotString(const JSObject *obj, uint32 slot)
149 {
150 jsval v;
151
152 JS_ASSERT(slot == JSSLOT_PREFIX ||
153 slot == JSSLOT_URI ||
154 slot == JSSLOT_LOCAL_NAME);
155 JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_NamespaceClass.base ||
156 IsQNameClass(STOBJ_GET_CLASS(obj)));
157 JS_ASSERT_IF(STOBJ_GET_CLASS(obj) == &js_NamespaceClass.base,
158 slot != JSSLOT_LOCAL_NAME);
159
160 v = obj->fslots[slot];
161 if (JSVAL_IS_VOID(v))
162 return NULL;
163 JS_ASSERT(JSVAL_IS_STRING(v));
164 return JSVAL_TO_STRING(v);
165 }
166
167 static JS_INLINE JSString *
168 GetPrefix(const JSObject *obj)
169 {
170 return GetSlotString(obj, JSSLOT_PREFIX);
171 }
172
173 static JSString *
174 GetURI(const JSObject *obj)
175 {
176 return GetSlotString(obj, JSSLOT_URI);
177 }
178
179 static JSString *
180 GetLocalName(const JSObject *obj)
181 {
182 return GetSlotString(obj, JSSLOT_LOCAL_NAME);
183 }
184
185 static JSBool
186 IsDeclared(const JSObject *obj)
187 {
188 jsval v;
189
190 JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_NamespaceClass.base);
191 v = obj->fslots[JSSLOT_DECLARED];
192 JS_ASSERT(JSVAL_IS_VOID(v) || v == JSVAL_TRUE);
193 return v == JSVAL_TRUE;
194 }
195
196 static JSBool
197 xml_isXMLName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
198 jsval *rval)
199 {
200 *rval = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argv[0]));
201 return JS_TRUE;
202 }
203
204 static inline bool
205 AppendString(JSCharBuffer &cb, JSString *str)
206 {
207 const jschar *chars, *end;
208 str->getCharsAndEnd(chars, end);
209 return cb.append(chars, end);
210 }
211
212 /*
213 * Namespace class and library functions.
214 */
215 enum namespace_tinyid {
216 NAMESPACE_PREFIX = -1,
217 NAMESPACE_URI = -2
218 };
219
220 static JSBool
221 namespace_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
222 {
223 if (!JSVAL_IS_INT(id))
224 return JS_TRUE;
225
226 if (STOBJ_GET_CLASS(obj) != &js_NamespaceClass.base)
227 return JS_TRUE;
228
229 switch (JSVAL_TO_INT(id)) {
230 case NAMESPACE_PREFIX:
231 *vp = obj->fslots[JSSLOT_PREFIX];
232 break;
233 case NAMESPACE_URI:
234 *vp = obj->fslots[JSSLOT_URI];
235 break;
236 }
237 return JS_TRUE;
238 }
239
240 static void
241 namespace_finalize(JSContext *cx, JSObject *obj)
242 {
243 if (cx->runtime->functionNamespaceObject == obj)
244 cx->runtime->functionNamespaceObject = NULL;
245 }
246
247 static JSBool
248 namespace_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
249 {
250 JSObject *obj2;
251
252 JS_ASSERT(JSVAL_IS_OBJECT(v));
253 obj2 = JSVAL_TO_OBJECT(v);
254 *bp = (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_NamespaceClass.base)
255 ? JS_FALSE
256 : js_EqualStrings(GetURI(obj), GetURI(obj2));
257 return JS_TRUE;
258 }
259
260 JS_FRIEND_DATA(JSExtendedClass) js_NamespaceClass = {
261 { "Namespace",
262 JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED |
263 JSCLASS_HAS_RESERVED_SLOTS(NAMESPACE_RESERVED_SLOTS) |
264 JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Namespace),
265 JS_PropertyStub, JS_PropertyStub, namespace_getProperty, NULL,
266 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, namespace_finalize,
267 NULL, NULL, NULL, NULL,
268 NULL, NULL, NULL, NULL },
269 namespace_equality,NULL, NULL, NULL,
270 NULL, NULL, NULL, NULL
271 };
272
273 #define NAMESPACE_ATTRS \
274 (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
275
276 static JSPropertySpec namespace_props[] = {
277 {js_prefix_str, NAMESPACE_PREFIX, NAMESPACE_ATTRS, 0, 0},
278 {js_uri_str, NAMESPACE_URI, NAMESPACE_ATTRS, 0, 0},
279 {0,0,0,0,0}
280 };
281
282 static JSBool
283 namespace_toString(JSContext *cx, uintN argc, jsval *vp)
284 {
285 JSObject *obj;
286
287 obj = JS_THIS_OBJECT(cx, vp);
288 if (!JS_InstanceOf(cx, obj, &js_NamespaceClass.base, vp))
289 return JS_FALSE;
290 *vp = obj->fslots[JSSLOT_URI];
291 return JS_TRUE;
292 }
293
294 static JSFunctionSpec namespace_methods[] = {
295 JS_FN(js_toString_str, namespace_toString, 0,0),
296 JS_FS_END
297 };
298
299 static JSObject *
300 NewXMLNamespace(JSContext *cx, JSString *prefix, JSString *uri, JSBool declared)
301 {
302 JSObject *obj;
303
304 obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
305 if (!obj)
306 return JS_FALSE;
307 JS_ASSERT(JSVAL_IS_VOID(obj->fslots[JSSLOT_PREFIX]));
308 JS_ASSERT(JSVAL_IS_VOID(obj->fslots[JSSLOT_URI]));
309 JS_ASSERT(JSVAL_IS_VOID(obj->fslots[JSSLOT_DECLARED]));
310 if (prefix)
311 obj->fslots[JSSLOT_PREFIX] = STRING_TO_JSVAL(prefix);
312 if (uri)
313 obj->fslots[JSSLOT_URI] = STRING_TO_JSVAL(uri);
314 if (declared)
315 obj->fslots[JSSLOT_DECLARED] = JSVAL_TRUE;
316 METER(xml_stats.xmlnamespace);
317 return obj;
318 }
319
320 /*
321 * QName class and library functions.
322 */
323 enum qname_tinyid {
324 QNAME_URI = -1,
325 QNAME_LOCALNAME = -2
326 };
327
328 static JSBool
329 qname_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
330 {
331 if (!JSVAL_IS_INT(id))
332 return JS_TRUE;
333
334 if (STOBJ_GET_CLASS(obj) != &js_QNameClass.base)
335 return JS_TRUE;
336
337 switch (JSVAL_TO_INT(id)) {
338 case QNAME_URI:
339 *vp = obj->fslots[JSSLOT_URI];
340 if (*vp == JSVAL_VOID)
341 *vp = JSVAL_NULL;
342 break;
343 case QNAME_LOCALNAME:
344 *vp = obj->fslots[JSSLOT_LOCAL_NAME];
345 break;
346 }
347 return JS_TRUE;
348 }
349
350 static void
351 anyname_finalize(JSContext* cx, JSObject* obj)
352 {
353 /* Make sure the next call to js_GetAnyName doesn't try to use obj. */
354 if (cx->runtime->anynameObject == obj)
355 cx->runtime->anynameObject = NULL;
356 }
357
358 static JSBool
359 qname_identity(JSObject *qna, JSObject *qnb)
360 {
361 JSString *uri1 = GetURI(qna);
362 JSString *uri2 = GetURI(qnb);
363
364 if (!uri1 ^ !uri2)
365 return JS_FALSE;
366 if (uri1 && !js_EqualStrings(uri1, uri2))
367 return JS_FALSE;
368 return js_EqualStrings(GetLocalName(qna), GetLocalName(qnb));
369 }
370
371 static JSBool
372 qname_equality(JSContext *cx, JSObject *qn, jsval v, JSBool *bp)
373 {
374 JSObject *obj2;
375
376 JS_ASSERT(JSVAL_IS_OBJECT(v));
377 obj2 = JSVAL_TO_OBJECT(v);
378 *bp = (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_QNameClass.base)
379 ? JS_FALSE
380 : qname_identity(qn, obj2);
381 return JS_TRUE;
382 }
383
384 JS_FRIEND_DATA(JSExtendedClass) js_QNameClass = {
385 { "QName",
386 JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED |
387 JSCLASS_HAS_RESERVED_SLOTS(QNAME_RESERVED_SLOTS) |
388 JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_QName),
389 JS_PropertyStub, JS_PropertyStub, qname_getProperty, NULL,
390 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
391 NULL, NULL, NULL, NULL,
392 NULL, NULL, NULL, NULL },
393 qname_equality, NULL, NULL, NULL,
394 NULL, NULL, NULL, NULL
395 };
396
397 /*
398 * Classes for the ECMA-357-internal types AttributeName and AnyName, which
399 * are like QName, except that they have no property getters. They share the
400 * qname_toString method, and therefore are exposed as constructable objects
401 * in this implementation.
402 */
403 JS_FRIEND_DATA(JSClass) js_AttributeNameClass = {
404 js_AttributeName_str,
405 JSCLASS_CONSTRUCT_PROTOTYPE |
406 JSCLASS_HAS_RESERVED_SLOTS(QNAME_RESERVED_SLOTS) |
407 JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_AttributeName),
408 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
409 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, NULL,
410 NULL, NULL, NULL, NULL,
411 NULL, NULL, NULL, NULL
412 };
413
414 JS_FRIEND_DATA(JSClass) js_AnyNameClass = {
415 js_AnyName_str,
416 JSCLASS_CONSTRUCT_PROTOTYPE |
417 JSCLASS_HAS_RESERVED_SLOTS(QNAME_RESERVED_SLOTS) |
418 JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_AnyName),
419 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
420 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, anyname_finalize,
421 NULL, NULL, NULL, NULL,
422 NULL, NULL, NULL, NULL
423 };
424
425 #define QNAME_ATTRS \
426 (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
427
428 static JSPropertySpec qname_props[] = {
429 {js_uri_str, QNAME_URI, QNAME_ATTRS, 0, 0},
430 {js_localName_str, QNAME_LOCALNAME, QNAME_ATTRS, 0, 0},
431 {0,0,0,0,0}
432 };
433
434 static JSBool
435 qname_toString(JSContext *cx, uintN argc, jsval *vp)
436 {
437 JSObject *obj;
438 JSClass *clasp;
439 JSString *uri, *str, *qualstr;
440 size_t length;
441 jschar *chars;
442
443 obj = JS_THIS_OBJECT(cx, vp);
444 if (!obj)
445 return JS_FALSE;
446 clasp = OBJ_GET_CLASS(cx, obj);
447 if (clasp != &js_AttributeNameClass &&
448 clasp != &js_AnyNameClass &&
449 !JS_InstanceOf(cx, obj, &js_QNameClass.base, vp + 2)) {
450 return JS_FALSE;
451 }
452
453 uri = GetURI(obj);
454 if (!uri) {
455 /* No uri means wildcard qualifier. */
456 str = ATOM_TO_STRING(cx->runtime->atomState.starQualifierAtom);
457 } else if (uri->empty()) {
458 /* Empty string for uri means localName is in no namespace. */
459 str = cx->runtime->emptyString;
460 } else {
461 qualstr = ATOM_TO_STRING(cx->runtime->atomState.qualifierAtom);
462 str = js_ConcatStrings(cx, uri, qualstr);
463 if (!str)
464 return JS_FALSE;
465 }
466 str = js_ConcatStrings(cx, str, GetLocalName(obj));
467 if (!str)
468 return JS_FALSE;
469
470 if (str && clasp == &js_AttributeNameClass) {
471 length = str->length();
472 chars = (jschar *) cx->malloc((length + 2) * sizeof(jschar));
473 if (!chars)
474 return JS_FALSE;
475 *chars = '@';
476 js_strncpy(chars + 1, str->chars(), length);
477 chars[++length] = 0;
478 str = js_NewString(cx, chars, length);
479 if (!str) {
480 cx->free(chars);
481 return JS_FALSE;
482 }
483 }
484
485 *vp = STRING_TO_JSVAL(str);
486 return JS_TRUE;
487 }
488
489 static JSFunctionSpec qname_methods[] = {
490 JS_FN(js_toString_str, qname_toString, 0,0),
491 JS_FS_END
492 };
493
494
495 static void
496 InitXMLQName(JSObject *obj, JSString *uri, JSString *prefix,
497 JSString *localName)
498 {
499 JS_ASSERT(JSVAL_IS_VOID(obj->fslots[JSSLOT_PREFIX]));
500 JS_ASSERT(JSVAL_IS_VOID(obj->fslots[JSSLOT_URI]));
501 JS_ASSERT(JSVAL_IS_VOID(obj->fslots[JSSLOT_LOCAL_NAME]));
502 if (uri)
503 obj->fslots[JSSLOT_URI] = STRING_TO_JSVAL(uri);
504 if (prefix)
505 obj->fslots[JSSLOT_PREFIX] = STRING_TO_JSVAL(prefix);
506 if (localName)
507 obj->fslots[JSSLOT_LOCAL_NAME] = STRING_TO_JSVAL(localName);
508 }
509
510 static JSObject *
511 NewXMLQName(JSContext *cx, JSString *uri, JSString *prefix, JSString *localName,
512 JSClass *clasp = &js_QNameClass.base)
513 {
514 JSObject *obj;
515
516 JS_ASSERT(IsQNameClass(clasp));
517 obj = js_NewObject(cx, clasp, NULL, NULL);
518 if (!obj)
519 return NULL;
520 InitXMLQName(obj, uri, prefix, localName);
521 METER(xml_stats.qname);
522 return obj;
523 }
524
525 JSObject *
526 js_ConstructXMLQNameObject(JSContext *cx, jsval nsval, jsval lnval)
527 {
528 jsval argv[2];
529
530 /*
531 * ECMA-357 11.1.2,
532 * The _QualifiedIdentifier : PropertySelector :: PropertySelector_
533 * production, step 2.
534 */
535 if (!JSVAL_IS_PRIMITIVE(nsval) &&
536 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nsval)) == &js_AnyNameClass) {
537 nsval = JSVAL_NULL;
538 }
539
540 argv[0] = nsval;
541 argv[1] = lnval;
542 return js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, argv);
543 }
544
545 static JSBool
546 IsXMLName(const jschar *cp, size_t n)
547 {
548 JSBool rv;
549 jschar c;
550
551 rv = JS_FALSE;
552 if (n != 0 && JS_ISXMLNSSTART(*cp)) {
553 while (--n != 0) {
554 c = *++cp;
555 if (!JS_ISXMLNS(c))
556 return rv;
557 }
558 rv = JS_TRUE;
559 }
560 return rv;
561 }
562
563 JSBool
564 js_IsXMLName(JSContext *cx, jsval v)
565 {
566 JSString *name;
567 JSErrorReporter older;
568
569 /*
570 * Inline specialization of the QName constructor called with v passed as
571 * the only argument, to compute the localName for the constructed qname,
572 * without actually allocating the object or computing its uri and prefix.
573 * See ECMA-357 13.1.2.1 step 1 and 13.3.2.
574 */
575 if (!JSVAL_IS_PRIMITIVE(v) &&
576 IsQNameClass(OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)))) {
577 name = GetLocalName(JSVAL_TO_OBJECT(v));
578 } else {
579 older = JS_SetErrorReporter(cx, NULL);
580 name = js_ValueToString(cx, v);
581 JS_SetErrorReporter(cx, older);
582 if (!name) {
583 JS_ClearPendingException(cx);
584 return JS_FALSE;
585 }
586 }
587
588 return IsXMLName(name->chars(), name->length());
589 }
590
591 /*
592 * When argc is -1, it indicates argv is empty but the code should behave as
593 * if argc is 1 and argv[0] is JSVAL_VOID.
594 */
595 static JSBool
596 NamespaceHelper(JSContext *cx, JSObject *obj, intN argc, jsval *argv,
597 jsval *rval)
598 {
599 jsval urival, prefixval;
600 JSObject *uriobj;
601 JSBool isNamespace, isQName;
602 JSClass *clasp;
603 JSString *empty, *uri, *prefix;
604
605 isNamespace = isQName = JS_FALSE;
606 #ifdef __GNUC__ /* suppress bogus gcc warnings */
607 uriobj = NULL;
608 #endif
609 if (argc <= 0) {
610 urival = JSVAL_VOID;
611 } else {
612 urival = argv[argc > 1];
613 if (!JSVAL_IS_PRIMITIVE(urival)) {
614 uriobj = JSVAL_TO_OBJECT(urival);
615 clasp = OBJ_GET_CLASS(cx, uriobj);
616 isNamespace = (clasp == &js_NamespaceClass.base);
617 isQName = (clasp == &js_QNameClass.base);
618 }
619 }
620
621 if (!obj) {
622 /* Namespace called as function. */
623 if (argc == 1 && isNamespace) {
624 /* Namespace called with one Namespace argument is identity. */
625 *rval = urival;
626 return JS_TRUE;
627 }
628
629 obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
630 if (!obj)
631 return JS_FALSE;
632 *rval = OBJECT_TO_JSVAL(obj);
633 }
634 METER(xml_stats.xmlnamespace);
635
636 empty = cx->runtime->emptyString;
637 obj->fslots[JSSLOT_PREFIX] = STRING_TO_JSVAL(empty);
638 obj->fslots[JSSLOT_URI] = STRING_TO_JSVAL(empty);
639
640 if (argc == 1 || argc == -1) {
641 if (isNamespace) {
642 obj->fslots[JSSLOT_URI] = uriobj->fslots[JSSLOT_URI];
643 obj->fslots[JSSLOT_PREFIX] = uriobj->fslots[JSSLOT_PREFIX];
644 } else if (isQName && (uri = GetURI(uriobj))) {
645 obj->fslots[JSSLOT_URI] = STRING_TO_JSVAL(uri);
646 obj->fslots[JSSLOT_PREFIX] = uriobj->fslots[JSSLOT_PREFIX];
647 } else {
648 uri = js_ValueToString(cx, urival);
649 if (!uri)
650 return JS_FALSE;
651 obj->fslots[JSSLOT_URI] = STRING_TO_JSVAL(uri);
652 if (!uri->empty())
653 obj->fslots[JSSLOT_PREFIX] = JSVAL_VOID;
654 }
655 } else if (argc == 2) {
656 if (!isQName || !(uri = GetURI(uriobj))) {
657 uri = js_ValueToString(cx, urival);
658 if (!uri)
659 return JS_FALSE;
660 }
661 obj->fslots[JSSLOT_URI] = STRING_TO_JSVAL(uri);
662
663 prefixval = argv[0];
664 if (uri->empty()) {
665 if (!JSVAL_IS_VOID(prefixval)) {
666 prefix = js_ValueToString(cx, prefixval);
667 if (!prefix)
668 return JS_FALSE;
669 if (!prefix->empty()) {
670 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
671 JSMSG_BAD_XML_NAMESPACE,
672 js_ValueToPrintableString(cx,
673 STRING_TO_JSVAL(prefix)));
674 return JS_FALSE;
675 }
676 }
677 } else if (JSVAL_IS_VOID(prefixval) || !js_IsXMLName(cx, prefixval)) {
678 obj->fslots[JSSLOT_PREFIX] = JSVAL_VOID;
679 } else {
680 prefix = js_ValueToString(cx, prefixval);
681 if (!prefix)
682 return JS_FALSE;
683 obj->fslots[JSSLOT_PREFIX] = STRING_TO_JSVAL(prefix);
684 }
685 }
686
687 return JS_TRUE;
688 }
689
690 static JSBool
691 Namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
692 {
693 return NamespaceHelper(cx,
694 JS_IsConstructing(cx) ? obj : NULL,
695 argc, argv, rval);
696 }
697
698 /*
699 * When argc is -1, it indicates argv is empty but the code should behave as
700 * if argc is 1 and argv[0] is JSVAL_VOID.
701 */
702 static JSBool
703 QNameHelper(JSContext *cx, JSObject *obj, JSClass *clasp, intN argc,
704 jsval *argv, jsval *rval)
705 {
706 jsval nameval, nsval;
707 JSBool isQName, isNamespace;
708 JSObject *qn;
709 JSString *uri, *prefix, *name;
710 JSObject *obj2;
711
712 JS_ASSERT(clasp == &js_QNameClass.base ||
713 clasp == &js_AttributeNameClass);
714 if (argc <= 0) {
715 nameval = JSVAL_VOID;
716 isQName = JS_FALSE;
717 } else {
718 nameval = argv[argc > 1];
719 isQName =
720 !JSVAL_IS_PRIMITIVE(nameval) &&
721 OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nameval)) == &js_QNameClass.base;
722 }
723
724 if (!obj) {
725 /* QName called as function. */
726 if (argc == 1 && isQName) {
727 /* QName called with one QName argument is identity. */
728 *rval = nameval;
729 return JS_TRUE;
730 }
731
732 /*
733 * Create and return a new QName or AttributeName object exactly as if
734 * constructed.
735 */
736 obj = js_NewObject(cx, clasp, NULL, NULL);
737 if (!obj)
738 return JS_FALSE;
739 *rval = OBJECT_TO_JSVAL(obj);
740 }
741 METER(xml_stats.qname);
742
743 if (isQName) {
744 /* If namespace is not specified and name is a QName, clone it. */
745 qn = JSVAL_TO_OBJECT(nameval);
746 if (argc == 1) {
747 uri = GetURI(qn);
748 prefix = GetPrefix(qn);
749 name = GetLocalName(qn);
750 goto out;
751 }
752
753 /* Namespace and qname were passed -- use the qname's localName. */
754 nameval = qn->fslots[JSSLOT_LOCAL_NAME];
755 }
756
757 if (argc == 0) {
758 name = cx->runtime->emptyString;
759 } else if (argc < 0) {
760 name = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
761 } else {
762 name = js_ValueToString(cx, nameval);
763 if (!name)
764 return JS_FALSE;
765 argv[argc > 1] = STRING_TO_JSVAL(name);
766 }
767
768 if (argc > 1 && !JSVAL_IS_VOID(argv[0])) {
769 nsval = argv[0];
770 } else if (IS_STAR(name)) {
771 nsval = JSVAL_NULL;
772 } else {
773 if (!js_GetDefaultXMLNamespace(cx, &nsval))
774 return JS_FALSE;
775 JS_ASSERT(!JSVAL_IS_PRIMITIVE(nsval));
776 JS_ASSERT(OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nsval)) ==
777 &js_NamespaceClass.base);
778 }
779
780 if (JSVAL_IS_NULL(nsval)) {
781 /* NULL prefix represents *undefined* in ECMA-357 13.3.2 5(a). */
782 uri = prefix = NULL;
783 } else {
784 /*
785 * Inline specialization of the Namespace constructor called with
786 * nsval passed as the only argument, to compute the uri and prefix
787 * for the constructed namespace, without actually allocating the
788 * object or computing other members. See ECMA-357 13.3.2 6(a) and
789 * 13.2.2.
790 */
791 isNamespace = isQName = JS_FALSE;
792 if (!JSVAL_IS_PRIMITIVE(nsval)) {
793 obj2 = JSVAL_TO_OBJECT(nsval);
794 clasp = OBJ_GET_CLASS(cx, obj2);
795 isNamespace = (clasp == &js_NamespaceClass.base);
796 isQName = (clasp == &js_QNameClass.base);
797 }
798 #ifdef __GNUC__ /* suppress bogus gcc warnings */
799 else obj2 = NULL;
800 #endif
801
802 if (isNamespace) {
803 uri = GetURI(obj2);
804 prefix = GetPrefix(obj2);
805 } else if (isQName && (uri = GetURI(obj2))) {
806 JS_ASSERT(argc > 1);
807 prefix = GetPrefix(obj2);
808 } else {
809 JS_ASSERT(argc > 1);
810 uri = js_ValueToString(cx, nsval);
811 if (!uri)
812 return JS_FALSE;
813 argv[0] = STRING_TO_JSVAL(uri); /* local root */
814
815 /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
816 prefix = uri->empty() ? cx->runtime->emptyString : NULL;
817 }
818 }
819
820 out:
821 InitXMLQName(obj, uri, prefix, name);
822 return JS_TRUE;
823 }
824
825 static JSBool
826 QName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
827 {
828 return QNameHelper(cx, JS_IsConstructing(cx) ? obj : NULL,
829 &js_QNameClass.base, argc, argv, rval);
830 }
831
832 static JSBool
833 AttributeName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
834 jsval *rval)
835 {
836 return QNameHelper(cx, JS_IsConstructing(cx) ? obj : NULL,
837 &js_AttributeNameClass, argc, argv, rval);
838 }
839
840 /*
841 * XMLArray library functions.
842 */
843 static JSBool
844 namespace_identity(const void *a, const void *b)
845 {
846 const JSObject *nsa = (const JSObject *) a;
847 const JSObject *nsb = (const JSObject *) b;
848 JSString *prefixa = GetPrefix(nsa);
849 JSString *prefixb = GetPrefix(nsb);
850
851 if (prefixa && prefixb) {
852 if (!js_EqualStrings(prefixa, prefixb))
853 return JS_FALSE;
854 } else {
855 if (prefixa || prefixb)
856 return JS_FALSE;
857 }
858 return js_EqualStrings(GetURI(nsa), GetURI(nsb));
859 }
860
861 static JSBool
862 attr_identity(const void *a, const void *b)
863 {
864 const JSXML *xmla = (const JSXML *) a;
865 const JSXML *xmlb = (const JSXML *) b;
866
867 return qname_identity(xmla->name, xmlb->name);
868 }
869
870 struct JSXMLArrayCursor
871 {
872 JSXMLArray *array;
873 uint32 index;
874 JSXMLArrayCursor *next;
875 JSXMLArrayCursor **prevp;
876 void *root;
877
878 JSXMLArrayCursor(JSXMLArray *array)
879 : array(array), index(0), next(array->cursors), prevp(&array->cursors),
880 root(NULL)
881 {
882 if (next)
883 next->prevp = &next;
884 array->cursors = this;
885 }
886
887 ~JSXMLArrayCursor() { disconnect(); }
888
889 void disconnect() {
890 if (!array)
891 return;
892 if (next)
893 next->prevp = prevp;
894 *prevp = next;
895 array = NULL;
896 }
897
898 void *getNext() {
899 if (!array || index >= array->length)
900 return NULL;
901 return root = array->vector[index++];
902 }
903
904 void *getCurrent() {
905 if (!array || index >= array->length)
906 return NULL;
907 return root = array->vector[index];
908 }
909 };
910
911 static void
912 XMLArrayCursorTrace(JSTracer *trc, JSXMLArrayCursor *cursor)
913 {
914 void *root;
915 #ifdef DEBUG
916 size_t index = 0;
917 #endif
918
919 for (; cursor; cursor = cursor->next) {
920 root = cursor->root;
921 JS_SET_TRACING_INDEX(trc, "cursor_root", index++);
922 js_CallValueTracerIfGCThing(trc, (jsval)root);
923 }
924 }
925
926 /* NB: called with null cx from the GC, via xml_trace => XMLArrayTrim. */
927 static JSBool
928 XMLArraySetCapacity(JSContext *cx, JSXMLArray *array, uint32 capacity)
929 {
930 void **vector;
931
932 if (capacity == 0) {
933 /* We could let realloc(p, 0) free this, but purify gets confused. */
934 if (array->vector) {
935 if (cx)
936 cx->free(array->vector);
937 else
938 js_free(array->vector);
939 }
940 vector = NULL;
941 } else {
942 if (
943 #if JS_BITS_PER_WORD == 32
944 (size_t)capacity > ~(size_t)0 / sizeof(void *) ||
945 #endif
946 !(vector = (void **)
947 js_realloc(array->vector, capacity * sizeof(void *)))) {
948 if (cx)
949 JS_ReportOutOfMemory(cx);
950 return JS_FALSE;
951 }
952 }
953 array->capacity = JSXML_PRESET_CAPACITY | capacity;
954 array->vector = vector;
955 return JS_TRUE;
956 }
957
958 static void
959 XMLArrayTrim(JSXMLArray *array)
960 {
961 if (array->capacity & JSXML_PRESET_CAPACITY)
962 return;
963 if (array->length < array->capacity)
964 XMLArraySetCapacity(NULL, array, array->length);
965 }
966
967 static JSBool
968 XMLArrayInit(JSContext *cx, JSXMLArray *array, uint32 capacity)
969 {
970 array->length = array->capacity = 0;
971 array->vector = NULL;
972 array->cursors = NULL;
973 return capacity == 0 || XMLArraySetCapacity(cx, array, capacity);
974 }
975
976 static void
977 XMLArrayFinish(JSContext *cx, JSXMLArray *array)
978 {
979 cx->free(array->vector);
980
981 while (JSXMLArrayCursor *cursor = array->cursors)
982 cursor->disconnect();
983
984 #ifdef DEBUG
985 memset(array, 0xd5, sizeof *array);
986 #endif
987 }
988
989 #define XML_NOT_FOUND ((uint32) -1)
990
991 static uint32
992 XMLArrayFindMember(const JSXMLArray *array, void *elt, JSIdentityOp identity)
993 {
994 void **vector;
995 uint32 i, n;
996
997 /* The identity op must not reallocate array->vector. */
998 vector = array->vector;
999 if (identity) {
1000 for (i = 0, n = array->length; i < n; i++) {
1001 if (identity(vector[i], elt))
1002 return i;
1003 }
1004 } else {
1005 for (i = 0, n = array->length; i < n; i++) {
1006 if (vector[i] == elt)
1007 return i;
1008 }
1009 }
1010 return XML_NOT_FOUND;
1011 }
1012
1013 /*
1014 * Grow array vector capacity by powers of two to LINEAR_THRESHOLD, and after
1015 * that, grow by LINEAR_INCREMENT. Both must be powers of two, and threshold
1016 * should be greater than increment.
1017 */
1018 #define LINEAR_THRESHOLD 256
1019 #define LINEAR_INCREMENT 32
1020
1021 static JSBool
1022 XMLArrayAddMember(JSContext *cx, JSXMLArray *array, uint32 index, void *elt)
1023 {
1024 uint32 capacity, i;
1025 int log2;
1026 void **vector;
1027
1028 if (index >= array->length) {
1029 if (index >= JSXML_CAPACITY(array)) {
1030 /* Arrange to clear JSXML_PRESET_CAPACITY from array->capacity. */
1031 capacity = index + 1;
1032 if (index >= LINEAR_THRESHOLD) {
1033 capacity = JS_ROUNDUP(capacity, LINEAR_INCREMENT);
1034 } else {
1035 JS_CEILING_LOG2(log2, capacity);
1036 capacity = JS_BIT(log2);
1037 }
1038 if (
1039 #if JS_BITS_PER_WORD == 32
1040 (size_t)capacity > ~(size_t)0 / sizeof(void *) ||
1041 #endif
1042 !(vector = (void **)
1043 js_realloc(array->vector, capacity * sizeof(void *)))) {
1044 JS_ReportOutOfMemory(cx);
1045 return JS_FALSE;
1046 }
1047 array->capacity = capacity;
1048 array->vector = vector;
1049 for (i = array->length; i < index; i++)
1050 vector[i] = NULL;
1051 }
1052 array->length = index + 1;
1053 }
1054
1055 array->vector[index] = elt;
1056 return JS_TRUE;
1057 }
1058
1059 static JSBool
1060 XMLArrayInsert(JSContext *cx, JSXMLArray *array, uint32 i, uint32 n)
1061 {
1062 uint32 j;
1063 JSXMLArrayCursor *cursor;
1064
1065 j = array->length;
1066 JS_ASSERT(i <= j);
1067 if (!XMLArraySetCapacity(cx, array, j + n))
1068 return JS_FALSE;
1069
1070 array->length = j + n;
1071 JS_ASSERT(n != (uint32)-1);
1072 while (j != i) {
1073 --j;
1074 array->vector[j + n] = array->vector[j];
1075 }
1076
1077 for (cursor = array->cursors; cursor; cursor = cursor->next) {
1078 if (cursor->index > i)
1079 cursor->index += n;
1080 }
1081 return JS_TRUE;
1082 }
1083
1084 static void *
1085 XMLArrayDelete(JSContext *cx, JSXMLArray *array, uint32 index, JSBool compress)
1086 {
1087 uint32 length;
1088 void **vector, *elt;
1089 JSXMLArrayCursor *cursor;
1090
1091 length = array->length;
1092 if (index >= length)
1093 return NULL;
1094
1095 vector = array->vector;
1096 elt = vector[index];
1097 if (compress) {
1098 while (++index < length)
1099 vector[index-1] = vector[index];
1100 array->length = length - 1;
1101 array->capacity = JSXML_CAPACITY(array);
1102 } else {
1103 vector[index] = NULL;
1104 }
1105
1106 for (cursor = array->cursors; cursor; cursor = cursor->next) {
1107 if (cursor->index > index)
1108 --cursor->index;
1109 }
1110 return elt;
1111 }
1112
1113 static void
1114 XMLArrayTruncate(JSContext *cx, JSXMLArray *array, uint32 length)
1115 {
1116 void **vector;
1117
1118 JS_ASSERT(!array->cursors);
1119 if (length >= array->length)
1120 return;
1121
1122 if (length == 0) {
1123 if (array->vector)
1124 cx->free(array->vector);
1125 vector = NULL;
1126 } else {
1127 vector = (void **) js_realloc(array->vector, length * sizeof(void *));
1128 if (!vector)
1129 return;
1130 }
1131
1132 if (array->length > length)
1133 array->length = length;
1134 array->capacity = length;
1135 array->vector = vector;
1136 }
1137
1138 #define XMLARRAY_FIND_MEMBER(a,e,f) XMLArrayFindMember(a, (void *)(e), f)
1139 #define XMLARRAY_HAS_MEMBER(a,e,f) (XMLArrayFindMember(a, (void *)(e), f) != \
1140 XML_NOT_FOUND)
1141 #define XMLARRAY_MEMBER(a,i,t) (((i) < (a)->length) \
1142 ? (t *) (a)->vector[i] \
1143 : NULL)
1144 #define XMLARRAY_SET_MEMBER(a,i,e) JS_BEGIN_MACRO \
1145 if ((a)->length <= (i)) \
1146 (a)->length = (i) + 1; \
1147 ((a)->vector[i] = (void *)(e)); \
1148 JS_END_MACRO
1149 #define XMLARRAY_ADD_MEMBER(x,a,i,e)XMLArrayAddMember(x, a, i, (void *)(e))
1150 #define XMLARRAY_INSERT(x,a,i,n) XMLArrayInsert(x, a, i, n)
1151 #define XMLARRAY_APPEND(x,a,e) XMLARRAY_ADD_MEMBER(x, a, (a)->length, (e))
1152 #define XMLARRAY_DELETE(x,a,i,c,t) ((t *) XMLArrayDelete(x, a, i, c))
1153 #define XMLARRAY_TRUNCATE(x,a,n) XMLArrayTruncate(x, a, n)
1154
1155 /*
1156 * Define XML setting property strings and constants early, so everyone can
1157 * use the same names and their magic numbers (tinyids, flags).
1158 */
1159 static const char js_ignoreComments_str[] = "ignoreComments";
1160 static const char js_ignoreProcessingInstructions_str[]
1161 = "ignoreProcessingInstructions";
1162 static const char js_ignoreWhitespace_str[] = "ignoreWhitespace";
1163 static const char js_prettyPrinting_str[] = "prettyPrinting";
1164 static const char js_prettyIndent_str[] = "prettyIndent";
1165
1166 /*
1167 * NB: These XML static property tinyids must
1168 * (a) not collide with the generic negative tinyids at the top of jsfun.c;
1169 * (b) index their corresponding xml_static_props array elements.
1170 * Don't change 'em!
1171 */
1172 enum xml_static_tinyid {
1173 XML_IGNORE_COMMENTS,
1174 XML_IGNORE_PROCESSING_INSTRUCTIONS,
1175 XML_IGNORE_WHITESPACE,
1176 XML_PRETTY_PRINTING,
1177 XML_PRETTY_INDENT
1178 };
1179
1180 static JSBool
1181 xml_setting_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
1182 {
1183 return JS_TRUE;
1184 }
1185
1186 static JSBool
1187 xml_setting_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
1188 {
1189 uint8 flag;
1190
1191 JS_ASSERT(JSVAL_IS_INT(id));
1192
1193 flag = JS_BIT(JSVAL_TO_INT(id));
1194 if (js_ValueToBoolean(*vp))
1195 cx->xmlSettingFlags |= flag;
1196 else
1197 cx->xmlSettingFlags &= ~flag;
1198 return JS_TRUE;
1199 }
1200
1201 static JSPropertySpec xml_static_props[] = {
1202 {js_ignoreComments_str, XML_IGNORE_COMMENTS, JSPROP_PERMANENT,
1203 xml_setting_getter, xml_setting_setter},
1204 {js_ignoreProcessingInstructions_str,
1205 XML_IGNORE_PROCESSING_INSTRUCTIONS, JSPROP_PERMANENT,
1206 xml_setting_getter, xml_setting_setter},
1207 {js_ignoreWhitespace_str, XML_IGNORE_WHITESPACE, JSPROP_PERMANENT,
1208 xml_setting_getter, xml_setting_setter},
1209 {js_prettyPrinting_str, XML_PRETTY_PRINTING, JSPROP_PERMANENT,
1210 xml_setting_getter, xml_setting_setter},
1211 {js_prettyIndent_str, XML_PRETTY_INDENT, JSPROP_PERMANENT,
1212 xml_setting_getter, NULL},
1213 {0,0,0,0,0}
1214 };
1215
1216 /* Derive cx->xmlSettingFlags bits from xml_static_props tinyids. */
1217 #define XSF_IGNORE_COMMENTS JS_BIT(XML_IGNORE_COMMENTS)
1218 #define XSF_IGNORE_PROCESSING_INSTRUCTIONS \
1219 JS_BIT(XML_IGNORE_PROCESSING_INSTRUCTIONS)
1220 #define XSF_IGNORE_WHITESPACE JS_BIT(XML_IGNORE_WHITESPACE)
1221 #define XSF_PRETTY_PRINTING JS_BIT(XML_PRETTY_PRINTING)
1222 #define XSF_CACHE_VALID JS_BIT(XML_PRETTY_INDENT)
1223
1224 /*
1225 * Extra, unrelated but necessarily disjoint flag used by ParseNodeToXML.
1226 * This flag means a couple of things:
1227 *
1228 * - The top JSXML created for a parse tree must have an object owning it.
1229 *
1230 * - That the default namespace normally inherited from the temporary
1231 * <parent xmlns='...'> tag that wraps a runtime-concatenated XML source
1232 * string must, in the case of a precompiled XML object tree, inherit via
1233 * ad-hoc code in ParseNodeToXML.
1234 *
1235 * Because of the second purpose, we name this flag XSF_PRECOMPILED_ROOT.
1236 */
1237 #define XSF_PRECOMPILED_ROOT (XSF_CACHE_VALID << 1)
1238
1239 /* Macros for special-casing xml:, xmlns= and xmlns:foo= in ParseNodeToQName. */
1240 #define IS_XML(str) \
1241 (str->length() == 3 && IS_XML_CHARS(str->chars()))
1242
1243 #define IS_XMLNS(str) \
1244 (str->length() == 5 && IS_XMLNS_CHARS(str->chars()))
1245
1246 #define IS_XML_CHARS(chars) \
1247 (JS_TOLOWER((chars)[0]) == 'x' && \
1248 JS_TOLOWER((chars)[1]) == 'm' && \
1249 JS_TOLOWER((chars)[2]) == 'l')
1250
1251 #define HAS_NS_AFTER_XML(chars) \
1252 (JS_TOLOWER((chars)[3]) == 'n' && \
1253 JS_TOLOWER((chars)[4]) == 's')
1254
1255 #define IS_XMLNS_CHARS(chars) \
1256 (IS_XML_CHARS(chars) && HAS_NS_AFTER_XML(chars))
1257
1258 #define STARTS_WITH_XML(chars,length) \
1259 (length >= 3 && IS_XML_CHARS(chars))
1260
1261 static const char xml_namespace_str[] = "http://www.w3.org/XML/1998/namespace";
1262 static const char xmlns_namespace_str[] = "http://www.w3.org/2000/xmlns/";
1263
1264 static JSObject *
1265 ParseNodeToQName(JSCompiler *jsc, JSParseNode *pn,
1266 JSXMLArray *inScopeNSes, JSBool isAttributeName)
1267 {
1268 JSContext *cx = jsc->context;
1269 JSString *str, *uri, *prefix, *localName;
1270 size_t length, offset;
1271 const jschar *start, *limit, *colon;
1272 uint32 n;
1273 JSObject *ns;
1274 JSString *nsprefix;
1275
1276 JS_ASSERT(pn->pn_arity == PN_NULLARY);
1277 str = ATOM_TO_STRING(pn->pn_atom);
1278 str->getCharsAndLength(start, length);
1279 JS_ASSERT(length != 0 && *start != '@');
1280 JS_ASSERT(length != 1 || *start != '*');
1281
1282 uri = cx->runtime->emptyString;
1283 limit = start + length;
1284 colon = js_strchr_limit(start, ':', limit);
1285 if (colon) {
1286 offset = colon - start;
1287 prefix = js_NewDependentString(cx, str, 0, offset);
1288 if (!prefix)
1289 return NULL;
1290
1291 if (STARTS_WITH_XML(start, offset)) {
1292 if (offset == 3) {
1293 uri = JS_InternString(cx, xml_namespace_str);
1294 if (!uri)
1295 return NULL;
1296 } else if (offset == 5 && HAS_NS_AFTER_XML(start)) {
1297 uri = JS_InternString(cx, xmlns_namespace_str);
1298 if (!uri)
1299 return NULL;
1300 } else {
1301 uri = NULL;
1302 }
1303 } else {
1304 uri = NULL;
1305 n = inScopeNSes->length;
1306 while (n != 0) {
1307 --n;
1308 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSObject);
1309 nsprefix = GetPrefix(ns);
1310 if (nsprefix && js_EqualStrings(nsprefix, prefix)) {
1311 uri = GetURI(ns);
1312 break;
1313 }
1314 }
1315 }
1316
1317 if (!uri) {
1318 js_ReportCompileErrorNumber(jsc->context, &jsc->tokenStream, pn,
1319 JSREPORT_ERROR,
1320 JSMSG_BAD_XML_NAMESPACE,
1321 js_ValueToPrintableString(jsc->context,
1322 STRING_TO_JSVAL(prefix)));
1323 return NULL;
1324 }
1325
1326 localName = js_NewStringCopyN(jsc->context, colon + 1, length - (offset + 1));
1327 if (!localName)
1328 return NULL;
1329 } else {
1330 if (isAttributeName) {
1331 /*
1332 * An unprefixed attribute is not in any namespace, so set prefix
1333 * as well as uri to the empty string.
1334 */
1335 prefix = uri;
1336 } else {
1337 /*
1338 * Loop from back to front looking for the closest declared default
1339 * namespace.
1340 */
1341 n = inScopeNSes->length;
1342 while (n != 0) {
1343 --n;
1344 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSObject);
1345 nsprefix = GetPrefix(ns);
1346 if (!nsprefix || nsprefix->empty()) {
1347 uri = GetURI(ns);
1348 break;
1349 }
1350 }
1351 prefix = uri->empty() ? jsc->context->runtime->emptyString : NULL;
1352 }
1353 localName = str;
1354 }
1355
1356 return NewXMLQName(jsc->context, uri, prefix, localName);
1357 }
1358
1359 static JSString *
1360 ChompXMLWhitespace(JSContext *cx, JSString *str)
1361 {
1362 size_t length, newlength, offset;
1363 const jschar *cp, *start, *end;
1364 jschar c;
1365
1366 str->getCharsAndLength(start, length);
1367 for (cp = start, end = cp + length; cp < end; cp++) {
1368 c = *cp;
1369 if (!JS_ISXMLSPACE(c))
1370 break;
1371 }
1372 while (end > cp) {
1373 c = end[-1];
1374 if (!JS_ISXMLSPACE(c))
1375 break;
1376 --end;
1377 }
1378 newlength = end - cp;
1379 if (newlength == length)
1380 return str;
1381 offset = cp - start;
1382 return js_NewDependentString(cx, str, offset, newlength);
1383 }
1384
1385 static JSXML *
1386 ParseNodeToXML(JSCompiler *jsc, JSParseNode *pn,
1387 JSXMLArray *inScopeNSes, uintN flags)
1388 {
1389 JSContext *cx = jsc->context;
1390 JSXML *xml, *kid, *attr, *attrj;
1391 JSString *str;
1392 uint32 length, n, i, j;
1393 JSParseNode *pn2, *pn3, *head, **pnp;
1394 JSObject *ns;
1395 JSObject *qn, *attrjqn;
1396 JSXMLClass xml_class;
1397 int stackDummy;
1398
1399 if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
1400 js_ReportCompileErrorNumber(cx, &jsc->tokenStream, pn, JSREPORT_ERROR,
1401 JSMSG_OVER_RECURSED);
1402 return NULL;
1403 }
1404
1405 #define PN2X_SKIP_CHILD ((JSXML *) 1)
1406
1407 /*
1408 * Cases return early to avoid common code that gets an outermost xml's
1409 * object, which protects GC-things owned by xml and its descendants from
1410 * garbage collection.
1411 */
1412 xml = NULL;
1413 if (!js_EnterLocalRootScope(cx))
1414 return NULL;
1415 switch (pn->pn_type) {
1416 case TOK_XMLELEM:
1417 length = inScopeNSes->length;
1418 pn2 = pn->pn_head;
1419 xml = ParseNodeToXML(jsc, pn2, inScopeNSes, flags);
1420 if (!xml)
1421 goto fail;
1422
1423 flags &= ~XSF_PRECOMPILED_ROOT;
1424 n = pn->pn_count;
1425 JS_ASSERT(n >= 2);
1426 n -= 2;
1427 if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
1428 goto fail;
1429
1430 i = 0;
1431 while ((pn2 = pn2->pn_next) != NULL) {
1432 if (!pn2->pn_next) {
1433 /* Don't append the end tag! */
1434 JS_ASSERT(pn2->pn_type == TOK_XMLETAGO);
1435 break;
1436 }
1437
1438 if ((flags & XSF_IGNORE_WHITESPACE) &&
1439 n > 1 && pn2->pn_type == TOK_XMLSPACE) {
1440 --n;
1441 continue;
1442 }
1443
1444 kid = ParseNodeToXML(jsc, pn2, inScopeNSes, flags);
1445 if (kid == PN2X_SKIP_CHILD) {
1446 --n;
1447 continue;
1448 }
1449
1450 if (!kid)
1451 goto fail;
1452
1453 /* Store kid in xml right away, to protect it from GC. */
1454 XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1455 kid->parent = xml;
1456 ++i;
1457
1458 /* XXX where is this documented in an XML spec, or in E4X? */
1459 if ((flags & XSF_IGNORE_WHITESPACE) &&
1460 n > 1 && kid->xml_class == JSXML_CLASS_TEXT) {
1461 str = ChompXMLWhitespace(cx, kid->xml_value);
1462 if (!str)
1463 goto fail;
1464 kid->xml_value = str;
1465 }
1466 }
1467
1468 JS_ASSERT(i == n);
1469 if (n < pn->pn_count - 2)
1470 XMLArrayTrim(&xml->xml_kids);
1471 XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1472 break;
1473
1474 case TOK_XMLLIST:
1475 xml = js_NewXML(cx, JSXML_CLASS_LIST);
1476 if (!xml)
1477 goto fail;
1478
1479 n = pn->pn_count;
1480 if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
1481 goto fail;
1482
1483 i = 0;
1484 for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
1485 /*
1486 * Always ignore insignificant whitespace in lists -- we shouldn't
1487 * condition this on an XML.ignoreWhitespace setting when the list
1488 * constructor is XMLList (note XML/XMLList unification hazard).
1489 */
1490 if (pn2->pn_type == TOK_XMLSPACE) {
1491 --n;
1492 continue;
1493 }
1494
1495 kid = ParseNodeToXML(jsc, pn2, inScopeNSes, flags);
1496 if (kid == PN2X_SKIP_CHILD) {
1497 --n;
1498 continue;
1499 }
1500
1501 if (!kid)
1502 goto fail;
1503
1504 XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1505 ++i;
1506 }
1507
1508 if (n < pn->pn_count)
1509 XMLArrayTrim(&xml->xml_kids);
1510 break;
1511
1512 case TOK_XMLSTAGO:
1513 case TOK_XMLPTAGC:
1514 length = inScopeNSes->length;
1515 pn2 = pn->pn_head;
1516 JS_ASSERT(pn2->pn_type == TOK_XMLNAME);
1517 if (pn2->pn_arity == PN_LIST)
1518 goto syntax;
1519
1520 xml = js_NewXML(cx, JSXML_CLASS_ELEMENT);
1521 if (!xml)
1522 goto fail;
1523
1524 /* First pass: check syntax and process namespace declarations. */
1525 JS_ASSERT(pn->pn_count >= 1);
1526 n = pn->pn_count - 1;
1527 pnp = &pn2->pn_next;
1528 head = *pnp;
1529 while ((pn2 = *pnp) != NULL) {
1530 size_t length;
1531 const jschar *chars;
1532
1533 if (pn2->pn_type != TOK_XMLNAME || pn2->pn_arity != PN_NULLARY)
1534 goto syntax;
1535
1536 /* Enforce "Well-formedness constraint: Unique Att Spec". */
1537 for (pn3 = head; pn3 != pn2; pn3 = pn3->pn_next->pn_next) {
1538 if (pn3->pn_atom == pn2->pn_atom) {
1539 js_ReportCompileErrorNumber(cx, &jsc->tokenStream, pn2,
1540 JSREPORT_ERROR,
1541 JSMSG_DUPLICATE_XML_ATTR,
1542 js_ValueToPrintableString(cx,
1543 ATOM_KEY(pn2->pn_atom)));
1544 goto fail;
1545 }
1546 }
1547
1548 str = ATOM_TO_STRING(pn2->pn_atom);
1549 pn2 = pn2->pn_next;
1550 JS_ASSERT(pn2);
1551 if (pn2->pn_type != TOK_XMLATTR)
1552 goto syntax;
1553
1554 str->getCharsAndLength(chars, length);
1555 if (length >= 5 &&
1556 IS_XMLNS_CHARS(chars) &&
1557 (length == 5 || chars[5] == ':')) {
1558 JSString *uri, *prefix;
1559
1560 uri = ATOM_TO_STRING(pn2->pn_atom);
1561 if (length == 5) {
1562 /* 10.3.2.1. Step 6(h)(i)(1)(a). */
1563 prefix = cx->runtime->emptyString;
1564 } else {
1565 prefix = js_NewStringCopyN(cx, chars + 6, length - 6);
1566 if (!prefix)
1567 goto fail;
1568 }
1569
1570 /*
1571 * Once the new ns is appended to xml->xml_namespaces, it is
1572 * protected from GC by the object that owns xml -- which is
1573 * either xml->object if outermost, or the object owning xml's
1574 * oldest ancestor if !outermost.
1575 */
1576 ns = NewXMLNamespace(cx, prefix, uri, JS_TRUE);
1577 if (!ns)
1578 goto fail;
1579
1580 /*
1581 * Don't add a namespace that's already in scope. If someone
1582 * extracts a child property from its parent via [[Get]], then
1583 * we enforce the invariant, noted many times in ECMA-357, that
1584 * the child's namespaces form a possibly-improper superset of
1585 * its ancestors' namespaces.
1586 */
1587 if (!XMLARRAY_HAS_MEMBER(inScopeNSes, ns, namespace_identity)) {
1588 if (!XMLARRAY_APPEND(cx, inScopeNSes, ns) ||
1589 !XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns)) {
1590 goto fail;
1591 }
1592 }
1593
1594 JS_ASSERT(n >= 2);
1595 n -= 2;
1596 *pnp = pn2->pn_next;
1597 /* XXXbe recycle pn2 */
1598 continue;
1599 }
1600
1601 pnp = &pn2->pn_next;
1602 }
1603
1604 /*
1605 * If called from js_ParseNodeToXMLObject, emulate the effect of the
1606 * <parent xmlns='%s'>...</parent> wrapping done by "ToXML Applied to
1607 * the String Type" (ECMA-357 10.3.1).
1608 */
1609 if (flags & XSF_PRECOMPILED_ROOT) {
1610 JS_ASSERT(length >= 1);
1611 ns = XMLARRAY_MEMBER(inScopeNSes, 0, JSObject);
1612 JS_ASSERT(!XMLARRAY_HAS_MEMBER(&xml->xml_namespaces, ns,
1613 namespace_identity));
1614 ns = NewXMLNamespace(cx, GetPrefix(ns), GetURI(ns), JS_FALSE);
1615 if (!ns)
1616 goto fail;
1617 if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
1618 goto fail;
1619 }
1620 XMLArrayTrim(&xml->xml_namespaces);
1621
1622 /* Second pass: process tag name and attributes, using namespaces. */
1623 pn2 = pn->pn_head;
1624 qn = ParseNodeToQName(jsc, pn2, inScopeNSes, JS_FALSE);
1625 if (!qn)
1626 goto fail;
1627 xml->name = qn;
1628
1629 JS_ASSERT((n & 1) == 0);
1630 n >>= 1;
1631 if (!XMLArraySetCapacity(cx, &xml->xml_attrs, n))
1632 goto fail;
1633
1634 for (i = 0; (pn2 = pn2->pn_next) != NULL; i++) {
1635 qn = ParseNodeToQName(jsc, pn2, inScopeNSes, JS_TRUE);
1636 if (!qn) {
1637 xml->xml_attrs.length = i;
1638 goto fail;
1639 }
1640
1641 /*
1642 * Enforce "Well-formedness constraint: Unique Att Spec", part 2:
1643 * this time checking local name and namespace URI.
1644 */
1645 for (j = 0; j < i; j++) {
1646 attrj = XMLARRAY_MEMBER(&xml->xml_attrs, j, JSXML);
1647 attrjqn = attrj->name;
1648 if (js_EqualStrings(GetURI(attrjqn), GetURI(qn)) &&
1649 js_EqualStrings(GetLocalName(attrjqn), GetLocalName(qn))) {
1650 js_ReportCompileErrorNumber(cx, &jsc->tokenStream, pn2,
1651 JSREPORT_ERROR,
1652 JSMSG_DUPLICATE_XML_ATTR,
1653 js_ValueToPrintableString(cx,
1654 ATOM_KEY(pn2->pn_atom)));
1655 goto fail;
1656 }
1657 }
1658
1659 pn2 = pn2->pn_next;
1660 JS_ASSERT(pn2);
1661 JS_ASSERT(pn2->pn_type == TOK_XMLATTR);
1662
1663 attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
1664 if (!attr)
1665 goto fail;
1666
1667 XMLARRAY_SET_MEMBER(&xml->xml_attrs, i, attr);
1668 attr->parent = xml;
1669 attr->name = qn;
1670 attr->xml_value = ATOM_TO_STRING(pn2->pn_atom);
1671 }
1672
1673 /* Point tag closes its own namespace scope. */
1674 if (pn->pn_type == TOK_XMLPTAGC)
1675 XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1676 break;
1677
1678 case TOK_XMLSPACE:
1679 case TOK_XMLTEXT:
1680 case TOK_XMLCDATA:
1681 case TOK_XMLCOMMENT:
1682 case TOK_XMLPI:
1683 str = ATOM_TO_STRING(pn->pn_atom);
1684 qn = NULL;
1685 if (pn->pn_type == TOK_XMLCOMMENT) {
1686 if (flags & XSF_IGNORE_COMMENTS)
1687 goto skip_child;
1688 xml_class = JSXML_CLASS_COMMENT;
1689 } else if (pn->pn_type == TOK_XMLPI) {
1690 if (IS_XML(str)) {
1691 js_ReportCompileErrorNumber(cx, &jsc->tokenStream, pn,
1692 JSREPORT_ERROR,
1693 JSMSG_RESERVED_ID,
1694 js_ValueToPrintableString(cx,
1695 STRING_TO_JSVAL(str)));
1696 goto fail;
1697 }
1698
1699 if (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS)
1700 goto skip_child;
1701
1702 qn = ParseNodeToQName(jsc, pn, inScopeNSes, JS_FALSE);
1703 if (!qn)
1704 goto fail;
1705
1706 str = pn->pn_atom2
1707 ? ATOM_TO_STRING(pn->pn_atom2)
1708 : cx->runtime->emptyString;
1709 xml_class = JSXML_CLASS_PROCESSING_INSTRUCTION;
1710 } else {
1711 /* CDATA section content, or element text. */
1712 xml_class = JSXML_CLASS_TEXT;
1713 }
1714
1715 xml = js_NewXML(cx, xml_class);
1716 if (!xml)
1717 goto fail;
1718 xml->name = qn;
1719 if (pn->pn_type == TOK_XMLSPACE)
1720 xml->xml_flags |= XMLF_WHITESPACE_TEXT;
1721 xml->xml_value = str;
1722 break;
1723
1724 default:
1725 goto syntax;
1726 }
1727
1728 js_LeaveLocalRootScopeWithResult(cx, (jsval) xml);
1729 if ((flags & XSF_PRECOMPILED_ROOT) && !js_GetXMLObject(cx, xml))
1730 return NULL;
1731 return xml;
1732
1733 skip_child:
1734 js_LeaveLocalRootScope(cx);
1735 return PN2X_SKIP_CHILD;
1736
1737 #undef PN2X_SKIP_CHILD
1738
1739 syntax:
1740 js_ReportCompileErrorNumber(cx, &jsc->tokenStream, pn, JSREPORT_ERROR,
1741 JSMSG_BAD_XML_MARKUP);
1742 fail:
1743 js_LeaveLocalRootScope(cx);
1744 return NULL;
1745 }
1746
1747 /*
1748 * XML helper, object-ops, and library functions. We start with the helpers,
1749 * in ECMA-357 order, but merging XML (9.1) and XMLList (9.2) helpers.
1750 */
1751 static JSBool
1752 GetXMLSetting(JSContext *cx, const char *name, jsval *vp)
1753 {
1754 jsval v;
1755
1756 if (!js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_XML), &v))
1757 return JS_FALSE;
1758 if (!VALUE_IS_FUNCTION(cx, v)) {
1759 *vp = JSVAL_VOID;
1760 return JS_TRUE;
1761 }
1762 return JS_GetProperty(cx, JSVAL_TO_OBJECT(v), name, vp);
1763 }
1764
1765 static JSBool
1766 FillSettingsCache(JSContext *cx)
1767 {
1768 int i;
1769 const char *name;
1770 jsval v;
1771
1772 /* Note: XML_PRETTY_INDENT is not a boolean setting. */
1773 for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
1774 name = xml_static_props[i].name;
1775 if (!GetXMLSetting(cx, name, &v))
1776 return JS_FALSE;
1777 if (js_ValueToBoolean(v))
1778 cx->xmlSettingFlags |= JS_BIT(i);
1779 else
1780 cx->xmlSettingFlags &= ~JS_BIT(i);
1781 }
1782
1783 cx->xmlSettingFlags |= XSF_CACHE_VALID;
1784 return JS_TRUE;
1785 }
1786
1787 static JSBool
1788 GetBooleanXMLSetting(JSContext *cx, const char *name, JSBool *bp)
1789 {
1790 int i;
1791
1792 if (!(cx->xmlSettingFlags & XSF_CACHE_VALID) && !FillSettingsCache(cx))
1793 return JS_FALSE;
1794
1795 for (i = 0; xml_static_props[i].name; i++) {
1796 if (!strcmp(xml_static_props[i].name, name)) {
1797 *bp = (cx->xmlSettingFlags & JS_BIT(i)) != 0;
1798 return JS_TRUE;
1799 }
1800 }
1801 *bp = JS_FALSE;
1802 return JS_TRUE;
1803 }
1804
1805 static JSBool
1806 GetUint32XMLSetting(JSContext *cx, const char *name, uint32 *uip)
1807 {
1808 jsval v;
1809
1810 return GetXMLSetting(cx, name, &v) && JS_ValueToECMAUint32(cx, v, uip);
1811 }
1812
1813 static JSBool
1814 GetXMLSettingFlags(JSContext *cx, uintN *flagsp)
1815 {
1816 JSBool flag;
1817
1818 /* Just get the first flag to validate the setting flags cache. */
1819 if (!GetBooleanXMLSetting(cx, js_ignoreComments_str, &flag))
1820 return JS_FALSE;
1821 *flagsp = cx->xmlSettingFlags;
1822 return JS_TRUE;
1823 }
1824
1825 static JSXML *
1826 ParseXMLSource(JSContext *cx, JSString *src)
1827 {
1828 jsval nsval;
1829 JSString *uri;
1830 size_t urilen, srclen, length, offset, dstlen;
1831 jschar *chars;
1832 const jschar *srcp, *endp;
1833 JSXML *xml;
1834 const char *filename;
1835 uintN lineno;
1836 JSStackFrame *fp;
1837 JSOp op;
1838 JSParseNode *pn;
1839 JSXMLArray nsarray;
1840 uintN flags;
1841
1842 static const char prefix[] = "<parent xmlns=\"";
1843 static const char middle[] = "\">";
1844 static const char suffix[] = "</parent>";
1845
1846 #define constrlen(constr) (sizeof(constr) - 1)
1847
1848 if (!js_GetDefaultXMLNamespace(cx, &nsval))
1849 return NULL;
1850 uri = GetURI(JSVAL_TO_OBJECT(nsval));
1851 uri = js_EscapeAttributeValue(cx, uri, JS_FALSE);
1852
1853 urilen = uri->length();
1854 srclen = src->length();
1855 length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
1856 constrlen(suffix);
1857
1858 chars = (jschar *) cx->malloc((length + 1) * sizeof(jschar));
1859 if (!chars)
1860 return NULL;
1861
1862 dstlen = length;
1863 js_InflateStringToBuffer(cx, prefix, constrlen(prefix), chars, &dstlen);
1864 offset = dstlen;
1865 js_strncpy(chars + offset, uri->chars(), urilen);
1866 offset += urilen;
1867 dstlen = length - offset + 1;
1868 js_InflateStringToBuffer(cx, middle, constrlen(middle), chars + offset,
1869 &dstlen);
1870 offset += dstlen;
1871 srcp = src->chars();
1872 js_strncpy(chars + offset, srcp, srclen);
1873 offset += srclen;
1874 dstlen = length - offset + 1;
1875 js_InflateStringToBuffer(cx, suffix, constrlen(suffix), chars + offset,
1876 &dstlen);
1877 chars [offset + dstlen] = 0;
1878
1879 xml = NULL;
1880 for (fp = js_GetTopStackFrame(cx); fp && !fp->regs; fp = fp->down)
1881 JS_ASSERT(!fp->script);
1882 filename = NULL;
1883 lineno = 1;
1884 if (fp) {
1885 op = (JSOp) *fp->regs->pc;
1886 if (op == JSOP_TOXML || op == JSOP_TOXMLLIST) {
1887 filename = fp->script->filename;
1888 lineno = js_FramePCToLineNumber(cx, fp);
1889 for (endp = srcp + srclen; srcp < endp; srcp++) {
1890 if (*srcp == '\n')
1891 --lineno;
1892 }
1893 }
1894 }
1895
1896 {
1897 JSCompiler jsc(cx);
1898 if (jsc.init(chars, length, NULL, filename, lineno)) {
1899 pn = jsc.parseXMLText(js_GetTopStackFrame(cx)->scopeChain, false);
1900 if (pn && XMLArrayInit(cx, &nsarray, 1)) {
1901 if (GetXMLSettingFlags(cx, &flags))
1902 xml = ParseNodeToXML(&jsc, pn, &nsarray, flags);
1903
1904 XMLArrayFinish(cx, &nsarray);
1905 }
1906 }
1907 }
1908
1909 cx->free(chars);
1910 return xml;
1911
1912 #undef constrlen
1913 }
1914
1915 /*
1916 * Errata in 10.3.1, 10.4.1, and 13.4.4.24 (at least).
1917 *
1918 * 10.3.1 Step 6(a) fails to NOTE that implementations that do not enforce
1919 * the constraint:
1920 *
1921 * for all x belonging to XML:
1922 * x.[[InScopeNamespaces]] >= x.[[Parent]].[[InScopeNamespaces]]
1923 *
1924 * must union x.[[InScopeNamespaces]] into x[0].[[InScopeNamespaces]] here
1925 * (in new sub-step 6(a), renumbering the others to (b) and (c)).
1926 *
1927 * Same goes for 10.4.1 Step 7(a).
1928 *
1929 * In order for XML.prototype.namespaceDeclarations() to work correctly, the
1930 * default namespace thereby unioned into x[0].[[InScopeNamespaces]] must be
1931 * flagged as not declared, so that 13.4.4.24 Step 8(a) can exclude all such
1932 * undeclared namespaces associated with x not belonging to ancestorNS.
1933 */
1934 static JSXML *
1935 OrphanXMLChild(JSContext *cx, JSXML *xml, uint32 i)
1936 {
1937 JSObject *ns;
1938
1939 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, 0, JSObject);
1940 xml = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
1941 if (!ns || !xml)
1942 return xml;
1943 if (xml->xml_class == JSXML_CLASS_ELEMENT) {
1944 if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
1945 return NULL;
1946 ns->fslots[JSSLOT_DECLARED] = JSVAL_VOID;
1947 }
1948 xml->parent = NULL;
1949 return xml;
1950 }
1951
1952 static JSObject *
1953 ToXML(JSContext *cx, jsval v)
1954 {
1955 JSObject *obj;
1956 JSXML *xml;
1957 JSClass *clasp;
1958 JSString *str;
1959 uint32 length;
1960
1961 if (JSVAL_IS_PRIMITIVE(v)) {
1962 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
1963 goto bad;
1964 } else {
1965 obj = JSVAL_TO_OBJECT(v);
1966 if (OBJECT_IS_XML(cx, obj)) {
1967 xml = (JSXML *) obj->getPrivate();
1968 if (xml->xml_class == JSXML_CLASS_LIST) {
1969 if (xml->xml_kids.length != 1)
1970 goto bad;
1971 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
1972 if (xml) {
1973 JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
1974 return js_GetXMLObject(cx, xml);
1975 }
1976 }
1977 return obj;
1978 }
1979
1980 clasp = OBJ_GET_CLASS(cx, obj);
1981 if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
1982 JS_ASSERT(0);
1983 }
1984
1985 if (clasp != &js_StringClass &&
1986 clasp != &js_NumberClass &&
1987 clasp != &js_BooleanClass) {
1988 goto bad;
1989 }
1990 }
1991
1992 str = js_ValueToString(cx, v);
1993 if (!str)
1994 return NULL;
1995 if (str->empty()) {
1996 length = 0;
1997 #ifdef __GNUC__ /* suppress bogus gcc warnings */
1998 xml = NULL;
1999 #endif
2000 } else {
2001 xml = ParseXMLSource(cx, str);
2002 if (!xml)
2003 return NULL;
2004 length = JSXML_LENGTH(xml);
2005 }
2006
2007 if (length == 0) {
2008 obj = js_NewXMLObject(cx, JSXML_CLASS_TEXT);
2009 if (!obj)
2010 return NULL;
2011 } else if (length == 1) {
2012 xml = OrphanXMLChild(cx, xml, 0);
2013 if (!xml)
2014 return NULL;
2015 obj = js_GetXMLObject(cx, xml);
2016 if (!obj)
2017 return NULL;
2018 } else {
2019 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SYNTAX_ERROR);
2020 return NULL;
2021 }
2022 return obj;
2023
2024 bad:
2025 js_ReportValueError(cx, JSMSG_BAD_XML_CONVERSION,
2026 JSDVG_IGNORE_STACK, v, NULL);
2027 return NULL;
2028 }
2029
2030 static JSBool
2031 Append(JSContext *cx, JSXML *list, JSXML *kid);
2032
2033 static JSObject *
2034 ToXMLList(JSContext *cx, jsval v)
2035 {
2036 JSObject *obj, *listobj;
2037 JSXML *xml, *list, *kid;
2038 JSClass *clasp;
2039 JSString *str;
2040 uint32 i, length;
2041
2042 if (JSVAL_IS_PRIMITIVE(v)) {
2043 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
2044 goto bad;
2045 } else {
2046 obj = JSVAL_TO_OBJECT(v);
2047 if (OBJECT_IS_XML(cx, obj)) {
2048 xml = (JSXML *) obj->getPrivate();
2049 if (xml->xml_class != JSXML_CLASS_LIST) {
2050 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2051 if (!listobj)
2052 return NULL;
2053 list = (JSXML *) listobj->getPrivate();
2054 if (!Append(cx, list, xml))
2055 return NULL;
2056 return listobj;
2057 }
2058 return obj;
2059 }
2060
2061 clasp = OBJ_GET_CLASS(cx, obj);
2062 if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
2063 JS_ASSERT(0);
2064 }
2065
2066 if (clasp != &js_StringClass &&
2067 clasp != &js_NumberClass &&
2068 clasp != &js_BooleanClass) {
2069 goto bad;
2070 }
2071 }
2072
2073 str = js_ValueToString(cx, v);
2074 if (!str)
2075 return NULL;
2076 if (str->empty()) {
2077 xml = NULL;
2078 length = 0;
2079 } else {
2080 if (!js_EnterLocalRootScope(cx))
2081 return NULL;
2082 xml = ParseXMLSource(cx, str);
2083 if (!xml) {
2084 js_LeaveLocalRootScope(cx);
2085 return NULL;
2086 }
2087 length = JSXML_LENGTH(xml);
2088 }
2089
2090 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2091 if (listobj) {
2092 list = (JSXML *) listobj->getPrivate();
2093 for (i = 0; i < length; i++) {
2094 kid = OrphanXMLChild(cx, xml, i);
2095 if (!kid || !Append(cx, list, kid)) {
2096 listobj = NULL;
2097 break;
2098 }
2099 }
2100 }
2101
2102 if (xml)
2103 js_LeaveLocalRootScopeWithResult(cx, (jsval) listobj);
2104 return listobj;
2105
2106 bad:
2107 js_ReportValueError(cx, JSMSG_BAD_XMLLIST_CONVERSION,
2108 JSDVG_IGNORE_STACK, v, NULL);
2109 return NULL;
2110 }
2111
2112 /*
2113 * ECMA-357 10.2.1 Steps 5-7 pulled out as common subroutines of XMLToXMLString
2114 * and their library-public js_* counterparts. The guts of MakeXMLCDataString,
2115 * MakeXMLCommentString, and MakeXMLPIString are further factored into a common
2116 * MakeXMLSpecialString subroutine.
2117 *
2118 * These functions mutate cb, leaving it empty.
2119 */
2120 static JSString *
2121 MakeXMLSpecialString(JSContext *cx, JSCharBuffer &cb,
2122 JSString *str, JSString *str2,
2123 const jschar *prefix, size_t prefixlength,
2124 const jschar *suffix, size_t suffixlength)
2125 {
2126 if (!cb.append(prefix, prefixlength) || !AppendString(cb, str))
2127 return NULL;
2128 if (str2 && !str2->empty()) {
2129 if (!cb.append(' ') || !AppendString(cb, str2))
2130 return NULL;
2131 }
2132 if (!cb.append(suffix, suffixlength))
2133 return NULL;
2134
2135 return js_NewStringFromCharBuffer(cx, cb);
2136 }
2137
2138 static JSString *
2139 MakeXMLCDATAString(JSContext *cx, JSCharBuffer &cb, JSString *str)
2140 {
2141 static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
2142 'C', 'D', 'A', 'T', 'A',
2143 '['};
2144 static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};
2145
2146 return MakeXMLSpecialString(cx, cb, str, NULL,
2147 cdata_prefix_ucNstr, 9,
2148 cdata_suffix_ucNstr, 3);
2149 }
2150
2151 static JSString *
2152 MakeXMLCommentString(JSContext *cx, JSCharBuffer &cb, JSString *str)
2153 {
2154 static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
2155 static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};
2156
2157 return MakeXMLSpecialString(cx, cb, str, NULL,
2158 comment_prefix_ucNstr, 4,
2159 comment_suffix_ucNstr, 3);
2160 }
2161
2162 static JSString *
2163 MakeXMLPIString(JSContext *cx, JSCharBuffer &cb, JSString *name,
2164 JSString *value)
2165 {
2166 static const jschar pi_prefix_ucNstr[] = {'<', '?'};
2167 static const jschar pi_suffix_ucNstr[] = {'?', '>'};
2168
2169 return MakeXMLSpecialString(cx, cb, name, value,
2170 pi_prefix_ucNstr, 2,
2171 pi_suffix_ucNstr, 2);
2172 }
2173
2174 /*
2175 * ECMA-357 10.2.1 17(d-g) pulled out into a common subroutine that appends
2176 * equals, a double quote, an attribute value, and a closing double quote.
2177 */
2178 static bool
2179 AppendAttributeValue(JSContext *cx, JSCharBuffer &cb, JSString *valstr)
2180 {
2181 if (!cb.append('='))
2182 return false;
2183 valstr = js_EscapeAttributeValue(cx, valstr, JS_TRUE);
2184 return valstr && AppendString(cb, valstr);
2185 }
2186
2187 /*
2188 * ECMA-357 10.2.1.1 EscapeElementValue helper method.
2189
2190 * These functions mutate cb, leaving it empty.
2191 */
2192 static JSString *
2193 EscapeElementValue(JSContext *cx, JSCharBuffer &cb, JSString *str)
2194 {
2195 size_t length;
2196 const jschar *start;
2197 str->getCharsAndLength(start, length);
2198
2199 for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
2200 jschar c = *cp;
2201 switch (*cp) {
2202 case '<':
2203 if (!js_AppendLiteral(cb, js_lt_entity_str))
2204 return NULL;
2205 break;
2206 case '>':
2207 if (!js_AppendLiteral(cb, js_gt_entity_str))
2208 return NULL;
2209 break;
2210 case '&':
2211 if (!js_AppendLiteral(cb, js_amp_entity_str))
2212 return NULL;
2213 break;
2214 default:
2215 if (!cb.append(c))
2216 return NULL;
2217 }
2218 }
2219 return js_NewStringFromCharBuffer(cx, cb);
2220 }
2221
2222 /*
2223 * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
2224 *
2225 * These functions mutate cb, leaving it empty.
2226 */
2227 static JSString *
2228 EscapeAttributeValue(JSContext *cx, JSCharBuffer &cb, JSString *str,
2229 JSBool quote)
2230 {
2231 size_t length;
2232 const jschar *start;
2233 str->getCharsAndLength(start, length);
2234
2235 if (quote && !cb.append('"'))
2236 return NULL;
2237
2238 for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
2239 jschar c = *cp;
2240 switch (c) {
2241 case '"':
2242 if (!js_AppendLiteral(cb, js_quot_entity_str))
2243 return NULL;
2244 break;
2245 case '<':
2246 if (!js_AppendLiteral(cb, js_lt_entity_str))
2247 return NULL;
2248 break;
2249 case '&':
2250 if (!js_AppendLiteral(cb, js_amp_entity_str))
2251 return NULL;
2252 break;
2253 case '\n':
2254 if (!js_AppendLiteral(cb, "&#xA;"))
2255 return NULL;
2256 break;
2257 case '\r':
2258 if (!js_AppendLiteral(cb, "&#xD;"))
2259 return NULL;
2260 break;
2261 case '\t':
2262 if (!js_AppendLiteral(cb, "&#x9;"))
2263 return NULL;
2264 break;
2265 default:
2266 if (!cb.append(c))
2267 return NULL;
2268 }
2269 }
2270
2271 if (quote && !cb.append('"'))
2272 return NULL;
2273
2274 return js_NewStringFromCharBuffer(cx, cb);
2275 }
2276
2277 /* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
2278 static JSObject *
2279 GetNamespace(JSContext *cx, JSObject *qn, const JSXMLArray *inScopeNSes)
2280 {
2281 JSString *uri, *prefix, *nsprefix;
2282 JSObject *match, *ns;
2283 uint32 i, n;
2284 jsval argv[2];
2285
2286 uri = GetURI(qn);
2287 prefix = GetPrefix(qn);
2288 JS_ASSERT(uri);
2289 if (!uri) {
2290 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2291 JSMSG_BAD_XML_NAMESPACE,
2292 prefix
2293 ? js_ValueToPrintableString(cx,
2294 STRING_TO_JSVAL(prefix))
2295 : js_undefined_str);
2296 return NULL;
2297 }
2298
2299 /* Look for a matching namespace in inScopeNSes, if provided. */
2300 match = NULL;
2301 if (inScopeNSes) {
2302 for (i = 0, n = inScopeNSes->length; i < n; i++) {
2303 ns = XMLARRAY_MEMBER(inScopeNSes, i, JSObject);
2304 if (!ns)
2305 continue;
2306
2307 /*
2308 * Erratum, very tricky, and not specified in ECMA-357 13.3.5.4:
2309 * If we preserve prefixes, we must match null prefix against
2310 * an empty prefix of ns, in order to avoid generating redundant
2311 * prefixed and default namespaces for cases such as:
2312 *
2313 * x = <t xmlns="http://foo.com"/>
2314 * print(x.toXMLString());
2315 *
2316 * Per 10.3.2.1, the namespace attribute in t has an empty string
2317 * prefix (*not* a null prefix), per 10.3.2.1 Step 6(h)(i)(1):
2318 *
2319 * 1. If the [local name] property of a is "xmlns"
2320 * a. Map ns.prefix to the empty string
2321 *
2322 * But t's name has a null prefix in this implementation, meaning
2323 * *undefined*, per 10.3.2.1 Step 6(c)'s NOTE (which refers to
2324 * the http://www.w3.org/TR/xml-infoset/ spec, item 2.2.3, without
2325 * saying how "no value" maps to an ECMA-357 value -- but it must
2326 * map to the *undefined* prefix value).
2327 *
2328 * Since "" != undefined (or null, in the current implementation)
2329 * the ECMA-357 spec will fail to match in [[GetNamespace]] called
2330 * on t with argument {} U {(prefix="", uri="http://foo.com")}.
2331 * This spec bug leads to ToXMLString results that duplicate the
2332 * declared namespace.
2333 */
2334 if (js_EqualStrings(GetURI(ns), uri)) {
2335 nsprefix = GetPrefix(ns);
2336 if (nsprefix == prefix ||
2337 ((nsprefix && prefix)
2338 ? js_EqualStrings(nsprefix, prefix)
2339 : (nsprefix ? nsprefix : prefix)->empty())) {
2340 match = ns;
2341 break;
2342 }
2343 }
2344 }
2345 }
2346
2347 /* If we didn't match, make a new namespace from qn. */
2348 if (!match) {
2349 argv[0] = prefix ? STRING_TO_JSVAL(prefix) : JSVAL_VOID;
2350 argv[1] = STRING_TO_JSVAL(uri);
2351 ns = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL,
2352 2, argv);
2353 if (!ns)
2354 return NULL;
2355 match = ns;
2356 }
2357 return match;
2358 }
2359
2360 static JSString *
2361 GeneratePrefix(JSContext *cx, JSString *uri, JSXMLArray *decls)
2362 {
2363 const jschar *cp, *start, *end;
2364 size_t length, newlength, offset;
2365 uint32 i, n, m, serial;
2366 jschar *bp, *dp;
2367 JSBool done;
2368 JSObject *ns;
2369 JSString *nsprefix, *prefix;
2370
2371 JS_ASSERT(!uri->empty());
2372
2373 /*
2374 * If there are no *declared* namespaces, skip all collision detection and
2375 * return a short prefix quickly; an example of such a situation:
2376 *
2377 * var x = <f/>;
2378 * var n = new Namespace("http://example.com/");
2379 * x.@n::att = "val";
2380 * x.toXMLString();
2381 *
2382 * This is necessary for various log10 uses below to be valid.
2383 */
2384 if (decls->length == 0)
2385 return JS_NewStringCopyZ(cx, "a");
2386
2387 /*
2388 * Try peeling off the last filename suffix or pathname component till
2389 * we have a valid XML name. This heuristic will prefer "xul" given
2390 * ".../there.is.only.xul", "xbl" given ".../xbl", and "xbl2" given any
2391 * likely URI of the form ".../xbl2/2005".
2392 */
2393 uri->getCharsAndEnd(start, end);
2394 cp = end;
2395 while (--cp > start) {
2396 if (*cp == '.' || *cp == '/' || *cp == ':') {
2397 ++cp;
2398 length = end - cp;
2399 if (IsXMLName(cp, length) && !STARTS_WITH_XML(cp, length))
2400 break;
2401 end = --cp;
2402 }
2403 }
2404 length = end - cp;
2405
2406 /*
2407 * If the namespace consisted only of non-XML names or names that begin
2408 * case-insensitively with "xml", arbitrarily create a prefix consisting
2409 * of 'a's of size length (allowing dp-calculating code to work with or
2410 * without this branch executing) plus the space for storing a hyphen and
2411 * the serial number (avoiding reallocation if a collision happens).
2412 */
2413 bp = (jschar *) cp;
2414 newlength = length;
2415 if (STARTS_WITH_XML(cp, length) || !IsXMLName(cp, length)) {
2416 newlength = length + 2 + (size_t) log10((double) decls->length);
2417 bp = (jschar *)
2418 cx->malloc((newlength + 1) * sizeof(jschar));
2419 if (!bp)
2420 return NULL;
2421
2422 bp[newlength] = 0;
2423 for (i = 0; i < newlength; i++)
2424 bp[i] = 'a';
2425 }
2426
2427 /*
2428 * Now search through decls looking for a collision. If we collide with
2429 * an existing prefix, start tacking on a hyphen and a serial number.
2430 */
2431 serial = 0;
2432 do {
2433 done = JS_TRUE;
2434 for (i = 0, n = decls->length; i < n; i++) {
2435 ns = XMLARRAY_MEMBER(decls, i, JSObject);
2436 if (ns && (nsprefix = GetPrefix(ns)) &&
2437 nsprefix->length() == newlength &&
2438 !memcmp(nsprefix->chars(), bp,
2439 newlength * sizeof(jschar))) {
2440 if (bp == cp) {
2441 newlength = length + 2 + (size_t) log10((double) n);
2442 bp = (jschar *)
2443 cx->malloc((newlength + 1) * sizeof(jschar));
2444 if (!bp)
2445 return NULL;
2446 js_strncpy(bp, cp, length);
2447 }
2448
2449 ++serial;
2450 JS_ASSERT(serial <= n);
2451 dp = bp + length + 2 + (size_t) log10((double) serial);
2452 *dp = 0;
2453 for (m = serial; m != 0; m /= 10)
2454 *--dp = (jschar)('0' + m % 10);
2455 *--dp = '-';
2456 JS_ASSERT(dp == bp + length);
2457
2458 done = JS_FALSE;
2459 break;
2460 }
2461 }
2462 } while (!done);
2463
2464 if (bp == cp) {
2465 offset = cp - start;
2466 prefix = js_NewDependentString(cx, uri, offset, length);
2467 } else {
2468 prefix = js_NewString(cx, bp, newlength);
2469 if (!prefix)
2470 cx->free(bp);
2471 }
2472 return prefix;
2473 }
2474
2475 static JSBool
2476 namespace_match(const void *a, const void *b)
2477 {
2478 const JSObject *nsa = (const JSObject *) a;
2479 const JSObject *nsb = (const JSObject *) b;
2480 JSString *prefixa, *prefixb = GetPrefix(nsb);
2481
2482 if (prefixb) {
2483 prefixa = GetPrefix(nsa);
2484 return prefixa && js_EqualStrings(prefixa, prefixb);
2485 }
2486 return js_EqualStrings(GetURI(nsa), GetURI(nsb));
2487 }
2488
2489 /* ECMA-357 10.2.1 and 10.2.2 */
2490 #define TO_SOURCE_FLAG 0x80000000
2491
2492 static JSString *
2493 XMLToXMLString(JSContext *cx, JSXML *xml, const JSXMLArray *ancestorNSes,
2494 uint32 indentLevel)
2495 {
2496 JSBool pretty, indentKids;
2497 JSCharBuffer cb(cx);
2498 JSString *str, *prefix, *nsuri;
2499 uint32 i, n, nextIndentLevel;
2500 JSXMLArray empty, decls, ancdecls;
2501 JSObject *ns, *ns2;
2502
2503 if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
2504 return NULL;
2505
2506 if (pretty) {
2507 if (!cb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
2508 return NULL;
2509 }
2510
2511 str = NULL;
2512
2513 switch (xml->xml_class) {
2514 case JSXML_CLASS_TEXT:
2515 /* Step 4. */
2516 if (pretty) {
2517 str = ChompXMLWhitespace(cx, xml->xml_value);
2518 if (!str)
2519 return NULL;
2520 } else {
2521 str = xml->xml_value;
2522 }
2523 return EscapeElementValue(cx, cb, str);
2524
2525 case JSXML_CLASS_ATTRIBUTE:
2526 /* Step 5. */
2527 return EscapeAttributeValue(cx, cb, xml->xml_value,
2528 (indentLevel & TO_SOURCE_FLAG) != 0);
2529
2530 case JSXML_CLASS_COMMENT:
2531 /* Step 6. */
2532 return MakeXMLCommentString(cx, cb, xml->xml_value);
2533
2534 case JSXML_CLASS_PROCESSING_INSTRUCTION:
2535 /* Step 7. */
2536 return MakeXMLPIString(cx, cb, GetLocalName(xml->name),
2537 xml->xml_value);
2538
2539 case JSXML_CLASS_LIST:
2540 /* ECMA-357 10.2.2. */
2541 {
2542 JSXMLArrayCursor cursor(&xml->xml_kids);
2543 i = 0;
2544 while (JSXML *kid = (JSXML *) cursor.getNext()) {
2545 if (pretty && i != 0) {
2546 if (!cb.append('\n'))
2547 return NULL;
2548 }
2549
2550 JSString *kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel);
2551 if (!kidstr || !AppendString(cb, kidstr))
2552 return NULL;
2553 ++i;
2554 }
2555 }
2556
2557 if (cb.empty())
2558 return cx->runtime->emptyString;
2559 return js_NewStringFromCharBuffer(cx, cb);
2560
2561 default:;
2562 }
2563
2564 /* After this point, control must flow through label out: to exit. */
2565 if (!js_EnterLocalRootScope(cx))
2566 return NULL;
2567
2568 /* ECMA-357 10.2.1 step 8 onward: handle ToXMLString on an XML element. */
2569 if (!ancestorNSes) {
2570 XMLArrayInit(cx, &empty, 0);
2571 ancestorNSes = &empty;
2572 }
2573 XMLArrayInit(cx, &decls, 0);
2574 ancdecls.capacity = 0;
2575
2576 /* Clone in-scope namespaces not in ancestorNSes into decls. */
2577 {
2578 JSXMLArrayCursor cursor(&xml->xml_namespaces);
2579 while ((ns = (JSObject *) cursor.getNext()) != NULL) {
2580 if (!IsDeclared(ns))
2581 continue;
2582 if (!XMLARRAY_HAS_MEMBER(ancestorNSes, ns, namespace_identity)) {
2583 /* NOTE: may want to exclude unused namespaces here. */
2584 ns2 = NewXMLNamespace(cx, GetPrefix(ns), GetURI(ns), JS_TRUE);
2585 if (!ns2 || !XMLARRAY_APPEND(cx, &decls, ns2))
2586 goto out;
2587 }
2588 }
2589 }
2590
2591 /*
2592 * Union ancestorNSes and decls into ancdecls. Note that ancdecls does
2593 * not own its member references. In the spec, ancdecls has no name, but
2594 * is always written out as (AncestorNamespaces U namespaceDeclarations).
2595 */
2596 if (!XMLArrayInit(cx, &ancdecls, ancestorNSes->length + decls.length))
2597 goto out;
2598 for (i = 0, n = ancestorNSes->length; i < n; i++) {
2599 ns2 = XMLARRAY_MEMBER(ancestorNSes, i, JSObject);
2600 if (!ns2)
2601 continue;
2602 JS_ASSERT(!XMLARRAY_HAS_MEMBER(&decls, ns2, namespace_identity));
2603 if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
2604 goto out;
2605 }
2606 for (i = 0, n = decls.length; i < n; i++) {
2607 ns2 = XMLARRAY_MEMBER(&decls, i, JSObject);
2608 if (!ns2)
2609 continue;
2610 JS_ASSERT(!XMLARRAY_HAS_MEMBER(&ancdecls, ns2, namespace_identity));
2611 if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
2612 goto out;
2613 }
2614
2615 /* Step 11, except we don't clone ns unless its prefix is undefined. */
2616 ns = GetNamespace(cx, xml->name, &ancdecls);
2617 if (!ns)
2618 goto out;
2619
2620 /* Step 12 (NULL means *undefined* here), plus the deferred ns cloning. */
2621 prefix = GetPrefix(ns);
2622 if (!prefix) {
2623 /*
2624 * Create a namespace prefix that isn't used by any member of decls.
2625 * Assign the new prefix to a copy of ns. Flag this namespace as if
2626 * it were declared, for assertion-testing's sake later below.
2627 *
2628 * Erratum: if prefix and xml->name are both null (*undefined* in
2629 * ECMA-357), we know that xml was named using the default namespace
2630 * (proof: see GetNamespace and the Namespace constructor called with
2631 * two arguments). So we ought not generate a new prefix here, when
2632 * we can declare ns as the default namespace for xml.
2633 *
2634 * This helps descendants inherit the namespace instead of redundantly
2635 * redeclaring it with generated prefixes in each descendant.
2636 */
2637 nsuri = GetURI(ns);
2638 if (!GetPrefix(xml->name)) {
2639 prefix = cx->runtime->emptyString;
2640 } else {
2641 prefix = GeneratePrefix(cx, nsuri, &ancdecls);
2642 if (!prefix)
2643 goto out;
2644 }
2645 ns = NewXMLNamespace(cx, prefix, nsuri, JS_TRUE);
2646 if (!ns)
2647 goto out;
2648
2649 /*
2650 * If the xml->name was unprefixed, we must remove any declared default
2651 * namespace from decls before appending ns. How can you get a default
2652 * namespace in decls that doesn't match the one from name? Apparently
2653 * by calling x.setNamespace(ns) where ns has no prefix. The other way
2654 * to fix this is to update x's in-scope namespaces when setNamespace
2655 * is called, but that's not specified by ECMA-357.
2656 *
2657 * Likely Erratum here, depending on whether the lack of update to x's
2658 * in-scope namespace in XML.prototype.setNamespace (13.4.4.36) is an
2659 * erratum or not. Note that changing setNamespace to update the list
2660 * of in-scope namespaces will change x.namespaceDeclarations().
2661 */
2662 if (prefix->empty()) {
2663 i = XMLArrayFindMember(&decls, ns, namespace_match);
2664 if (i != XML_NOT_FOUND)
2665 XMLArrayDelete(cx, &decls, i, JS_TRUE);
2666 }
2667
2668 /*
2669 * In the spec, ancdecls has no name, but is always written out as
2670 * (AncestorNamespaces U namespaceDeclarations). Since we compute
2671 * that union in ancdecls, any time we append a namespace strong
2672 * ref to decls, we must also append a weak ref to ancdecls. Order
2673 * matters here: code at label out: releases strong refs in decls.
2674 */
2675 if (!XMLARRAY_APPEND(cx, &ancdecls, ns) ||
2676 !XMLARRAY_APPEND(cx, &decls, ns)) {
2677 goto out;
2678 }
2679 }
2680
2681 /* Format the element or point-tag into cb. */
2682 if (!cb.append('<'))
2683 goto out;
2684
2685 if (prefix && !prefix->empty()) {
2686 if (!AppendString(cb, prefix) || !cb.append(':'))
2687 goto out;
2688 }
2689 if (!AppendString(cb, GetLocalName(xml->name)))
2690 goto out;
2691
2692 /*
2693 * Step 16 makes a union to avoid writing two loops in step 17, to share
2694 * common attribute value appending spec-code. We prefer two loops for
2695 * faster code and less data overhead.
2696 */
2697
2698 /* Step 17(b): append attributes. */
2699 {
2700 JSXMLArrayCursor cursor(&xml->xml_attrs);
2701 while (JSXML *attr = (JSXML *) cursor.getNext()) {
2702 if (!cb.append(' '))
2703 goto out;
2704 ns2 = GetNamespace(cx, attr->name, &ancdecls);
2705 if (!ns2)
2706 goto out;
2707
2708 /* 17(b)(ii): NULL means *undefined* here. */
2709 prefix = GetPrefix(ns2);
2710 if (!prefix) {
2711 prefix = GeneratePrefix(cx, GetURI(ns2), &ancdecls);
2712 if (!prefix)
2713 goto out;
2714
2715 /* Again, we avoid copying ns2 until we know it's prefix-less. */
2716 ns2 = NewXMLNamespace(cx, prefix, GetURI(ns2), JS_TRUE);
2717 if (!ns2)
2718 goto out;
2719
2720 /*
2721 * In the spec, ancdecls has no name, but is always written out as
2722 * (AncestorNamespaces U namespaceDeclarations). Since we compute
2723 * that union in ancdecls, any time we append a namespace strong
2724 * ref to decls, we must also append a weak ref to ancdecls. Order
2725 * matters here: code at label out: releases strong refs in decls.
2726 */
2727 if (!XMLARRAY_APPEND(cx, &ancdecls, ns2) ||
2728 !XMLARRAY_APPEND(cx, &decls, ns2)) {
2729 goto out;
2730 }
2731 }
2732
2733 /* 17(b)(iii). */
2734 if (!prefix->empty()) {
2735 if (!AppendString(cb, prefix) || !cb.append(':'))
2736 goto out;
2737 }
2738
2739 /* 17(b)(iv). */
2740 if (!AppendString(cb, GetLocalName(attr->name)))
2741 goto out;
2742
2743 /* 17(d-g). */
2744 if (!AppendAttributeValue(cx, cb, attr->xml_value))
2745 goto out;
2746 }
2747 }
2748
2749 /* Step 17(c): append XML namespace declarations. */
2750 {
2751 JSXMLArrayCursor cursor(&decls);
2752 while (JSObject *ns3 = (JSObject *) cursor.getNext()) {
2753 JS_ASSERT(IsDeclared(ns3));
2754
2755 if (!js_AppendLiteral(cb, " xmlns"))
2756 goto out;
2757
2758 /* 17(c)(ii): NULL means *undefined* here. */
2759 prefix = GetPrefix(ns3);
2760 if (!prefix) {
2761 prefix = GeneratePrefix(cx, GetURI(ns3), &ancdecls);
2762 if (!prefix)
2763 goto out;
2764 ns3->fslots[JSSLOT_PREFIX] = STRING_TO_JSVAL(prefix);
2765 }
2766
2767 /* 17(c)(iii). */
2768 if (!prefix->empty()) {
2769 if (!cb.append(':') || !AppendString(cb, prefix))
2770 goto out;
2771 }
2772
2773 /* 17(d-g). */
2774 if (!AppendAttributeValue(cx, cb, GetURI(ns3)))
2775 goto out;
2776 }
2777 }
2778
2779 /* Step 18: handle point tags. */
2780 n = xml->xml_kids.length;
2781 if (n == 0) {
2782 if (!js_AppendLiteral(cb, "/>"))
2783 goto out;
2784 } else {
2785 /* Steps 19 through 25: handle element content, and open the end-tag. */
2786 if (!cb.append('>'))
2787 goto out;
2788 {
2789 JSXML *kid;
2790 indentKids = n > 1 ||
2791 (n == 1 &&
2792 (kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
2793 kid->xml_class != JSXML_CLASS_TEXT);
2794 }
2795
2796 if (pretty && indentKids) {
2797 if (!GetUint32XMLSetting(cx, js_prettyIndent_str, &i))
2798 goto out;
2799 nextIndentLevel = indentLevel + i;
2800 } else {
2801 nextIndentLevel = indentLevel & TO_SOURCE_FLAG;
2802 }
2803
2804 {
2805 JSXMLArrayCursor cursor(&xml->xml_kids);
2806 while (JSXML *kid = (JSXML *) cursor.getNext()) {
2807 if (pretty && indentKids) {
2808 if (!cb.append('\n'))
2809 goto out;
2810 }
2811
2812 JSString *kidstr = XMLToXMLString(cx, kid, &ancdecls, nextIndentLevel);
2813 if (!kidstr)
2814 goto out;
2815
2816 if (!AppendString(cb, kidstr))
2817 goto out;
2818 }
2819 }
2820
2821 if (pretty && indentKids) {
2822 if (!cb.append('\n') ||
2823 !cb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
2824 goto out;
2825 }
2826 if (!js_AppendLiteral(cb, "</"))
2827 goto out;
2828
2829 /* Step 26. */
2830 prefix = GetPrefix(ns);
2831 if (prefix && !prefix->empty()) {
2832 if (!AppendString(cb, prefix) || !cb.append(':'))
2833 goto out;
2834 }
2835
2836 /* Step 27. */
2837 if (!AppendString(cb, GetLocalName(xml->name)) || !cb.append('>'))
2838 goto out;
2839 }
2840
2841 str = js_NewStringFromCharBuffer(cx, cb);
2842 out:
2843 js_LeaveLocalRootScopeWithResult(cx, STRING_TO_JSVAL(str));
2844 XMLArrayFinish(cx, &decls);
2845 if (ancdecls.capacity != 0)
2846 XMLArrayFinish(cx, &ancdecls);
2847 return str;
2848 }
2849
2850 /* ECMA-357 10.2 */
2851 static JSString *
2852 ToXMLString(JSContext *cx, jsval v, uint32 toSourceFlag)
2853 {
2854 JSObject *obj;
2855 JSString *str;
2856 JSXML *xml;
2857
2858 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
2859 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2860 JSMSG_BAD_XML_CONVERSION,
2861 JSVAL_IS_NULL(v) ? js_null_str : js_undefined_str);
2862 return NULL;
2863 }
2864
2865 if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
2866 return js_ValueToString(cx, v);
2867
2868 if (JSVAL_IS_STRING(v)) {
2869 JSCharBuffer cb(cx);
2870 return EscapeElementValue(cx, cb, JSVAL_TO_STRING(v));
2871 }
2872
2873 obj = JSVAL_TO_OBJECT(v);
2874 if (!OBJECT_IS_XML(cx, obj)) {
2875 if (!obj->defaultValue(cx, JSTYPE_STRING, &v))
2876 return NULL;
2877 str = js_ValueToString(cx, v);
2878 if (!str)
2879 return NULL;
2880 JSCharBuffer cb(cx);
2881 return EscapeElementValue(cx, cb, str);
2882 }
2883
2884 /* Handle non-element cases in this switch, returning from each case. */
2885 xml = (JSXML *) obj->getPrivate();
2886 return XMLToXMLString(cx, xml, NULL, toSourceFlag | 0);
2887 }
2888
2889 static JSObject *
2890 ToAttributeName(JSContext *cx, jsval v)
2891 {
2892 JSString *name, *uri, *prefix;
2893 JSObject *obj;
2894 JSClass *clasp;
2895 JSObject *qn;
2896
2897 if (JSVAL_IS_STRING(v)) {
2898 name = JSVAL_TO_STRING(v);
2899 uri = prefix = cx->runtime->emptyString;
2900 } else {
2901 if (JSVAL_IS_PRIMITIVE(v)) {
2902 js_ReportValueError(cx, JSMSG_BAD_XML_ATTR_NAME,
2903 JSDVG_IGNORE_STACK, v, NULL);
2904 return NULL;
2905 }
2906
2907 obj = JSVAL_TO_OBJECT(v);
2908 clasp = OBJ_GET_CLASS(cx, obj);
2909 if (clasp == &js_AttributeNameClass)
2910 return obj;
2911
2912 if (clasp == &js_QNameClass.base) {
2913 qn = obj;
2914 uri = GetURI(qn);
2915 prefix = GetPrefix(qn);
2916 name = GetLocalName(qn);
2917 } else {
2918 if (clasp == &js_AnyNameClass) {
2919 name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
2920 } else {
2921 name = js_ValueToString(cx, v);
2922 if (!name)
2923 return NULL;
2924 }
2925 uri = prefix = cx->runtime->emptyString;
2926 }
2927 }
2928
2929 qn = NewXMLQName(cx, uri, prefix, name, &js_AttributeNameClass);
2930 if (!qn)
2931 return NULL;
2932 return qn;
2933 }
2934
2935 static void
2936 ReportBadXMLName(JSContext *cx, jsval id)
2937 {
2938 js_ReportValueError(cx, JSMSG_BAD_XML_NAME, JSDVG_IGNORE_STACK, id, NULL);
2939 }
2940
2941 static JSBool
2942 IsFunctionQName(JSContext *cx, JSObject *qn, jsid *funidp)
2943 {
2944 JSAtom *atom;
2945 JSString *uri;
2946
2947 atom = cx->runtime->atomState.lazy.functionNamespaceURIAtom;
2948 uri = GetURI(qn);
2949 if (uri && atom &&
2950 (uri == ATOM_TO_STRING(atom) ||
2951 js_EqualStrings(uri, ATOM_TO_STRING(atom)))) {
2952 return JS_ValueToId(cx, STRING_TO_JSVAL(GetLocalName(qn)), funidp);
2953 }
2954 *funidp = 0;
2955 return JS_TRUE;
2956 }
2957
2958 JSBool
2959 js_IsFunctionQName(JSContext *cx, JSObject *obj, jsid *funidp)
2960 {
2961 if (OBJ_GET_CLASS(cx, obj) == &js_QNameClass.base)
2962 return IsFunctionQName(cx, obj, funidp);
2963 *funidp = 0;
2964 return JS_TRUE;
2965 }
2966
2967 static JSObject *
2968 ToXMLName(JSContext *cx, jsval v, jsid *funidp)
2969 {
2970 JSString *name;
2971 JSObject *obj;
2972 JSClass *clasp;
2973 uint32 index;
2974
2975 if (JSVAL_IS_STRING(v)) {
2976 name = JSVAL_TO_STRING(v);
2977 } else {
2978 if (JSVAL_IS_PRIMITIVE(v)) {
2979 ReportBadXMLName(cx, v);
2980 return NULL;
2981 }
2982
2983 obj = JSVAL_TO_OBJECT(v);
2984 clasp = OBJ_GET_CLASS(cx, obj);
2985 if (clasp == &js_AttributeNameClass || clasp == &js_QNameClass.base)
2986 goto out;
2987 if (clasp == &js_AnyNameClass) {
2988 name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
2989 goto construct;
2990 }
2991 name = js_ValueToString(cx, v);
2992 if (!name)
2993 return NULL;
2994 }
2995
2996 /*
2997 * ECMA-357 10.6.1 step 1 seems to be incorrect. The spec says:
2998 *
2999 * 1. If ToString(ToNumber(P)) == ToString(P), throw a TypeError exception
3000 *
3001 * First, _P_ should be _s_, to refer to the given string.
3002 *
3003 * Second, why does ToXMLName applied to the string type throw TypeError
3004 * only for numeric literals without any leading or trailing whitespace?
3005 *
3006 * If the idea is to reject uint32 property names, then the check needs to
3007 * be stricter, to exclude hexadecimal and floating point literals.
3008 */
3009 if (js_IdIsIndex(STRING_TO_JSVAL(name), &index))
3010 goto bad;
3011
3012 if (*name->chars() == '@') {
3013 name = js_NewDependentString(cx, name, 1, name->length() - 1);
3014 if (!name)
3015 return NULL;
3016 *funidp = 0;
3017 return ToAttributeName(cx, STRING_TO_JSVAL(name));
3018 }
3019
3020 construct:
3021 v = STRING_TO_JSVAL(name);
3022 obj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &v);
3023 if (!obj)
3024 return NULL;
3025
3026 out:
3027 if (!IsFunctionQName(cx, obj, funidp))
3028 return NULL;
3029 return obj;
3030
3031 bad:
3032 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3033 JSMSG_BAD_XML_NAME,
3034 js_ValueToPrintableString(cx, STRING_TO_JSVAL(name)));
3035 return NULL;
3036 }
3037
3038 /* ECMA-357 9.1.1.13 XML [[AddInScopeNamespace]]. */
3039 static JSBool
3040 AddInScopeNamespace(JSContext *cx, JSXML *xml, JSObject *ns)
3041 {
3042 JSString *prefix, *prefix2;
3043 JSObject *match, *ns2;
3044 uint32 i, n, m;
3045
3046 if (xml->xml_class != JSXML_CLASS_ELEMENT)
3047 return JS_TRUE;
3048
3049 /* NULL means *undefined* here -- see ECMA-357 9.1.1.13 step 2. */
3050 prefix = GetPrefix(ns);
3051 if (!prefix) {
3052 match = NULL;
3053 for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3054 ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3055 if (ns2 && js_EqualStrings(GetURI(ns2), GetURI(ns))) {
3056 match = ns2;
3057 break;
3058 }
3059 }
3060 if (!match && !XMLARRAY_ADD_MEMBER(cx, &xml->xml_namespaces, n, ns))
3061 return JS_FALSE;
3062 } else {
3063 if (prefix->empty() && GetURI(xml->name)->empty())
3064 return JS_TRUE;
3065 match = NULL;
3066 #ifdef __GNUC__ /* suppress bogus gcc warnings */
3067 m = XML_NOT_FOUND;
3068 #endif
3069 for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3070 ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3071 if (ns2 && (prefix2 = GetPrefix(ns2)) &&
3072 js_EqualStrings(prefix2, prefix)) {
3073 match = ns2;
3074 m = i;
3075 break;
3076 }
3077 }
3078 if (match && !js_EqualStrings(GetURI(match), GetURI(ns))) {
3079 ns2 = XMLARRAY_DELETE(cx, &xml->xml_namespaces, m, JS_TRUE,
3080 JSObject);
3081 JS_ASSERT(ns2 == match);
3082 match->fslots[JSSLOT_PREFIX] = JSVAL_VOID;
3083 if (!AddInScopeNamespace(cx, xml, match))
3084 return JS_FALSE;
3085 }
3086 if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
3087 return JS_FALSE;
3088 }
3089
3090 /* OPTION: enforce that descendants have superset namespaces. */
3091 return JS_TRUE;
3092 }
3093
3094 /* ECMA-357 9.2.1.6 XMLList [[Append]]. */
3095 static JSBool
3096 Append(JSContext *cx, JSXML *list, JSXML *xml)
3097 {
3098 uint32 i, j, k, n;
3099 JSXML *kid;
3100
3101 JS_ASSERT(list->xml_class == JSXML_CLASS_LIST);
3102 i = list->xml_kids.length;
3103 n = 1;
3104 if (xml->xml_class == JSXML_CLASS_LIST) {
3105 list->xml_target = xml->xml_target;
3106 list->xml_targetprop = xml->xml_targetprop;
3107 n = JSXML_LENGTH(xml);
3108 k = i + n;
3109 if (!XMLArraySetCapacity(cx, &list->xml_kids, k))
3110 return JS_FALSE;
3111 for (j = 0; j < n; j++) {
3112 kid = XMLARRAY_MEMBER(&xml->xml_kids, j, JSXML);
3113 if (kid)
3114 XMLARRAY_SET_MEMBER(&list->xml_kids, i + j, kid);
3115 }
3116 return JS_TRUE;
3117 }
3118
3119 list->xml_target = xml->parent;
3120 if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
3121 list->xml_targetprop = NULL;
3122 else
3123 list->xml_targetprop = xml->name;
3124 if (!XMLARRAY_ADD_MEMBER(cx, &list->xml_kids, i, xml))
3125 return JS_FALSE;
3126 return JS_TRUE;
3127 }
3128
3129 /* ECMA-357 9.1.1.7 XML [[DeepCopy]] and 9.2.1.7 XMLList [[DeepCopy]]. */
3130 static JSXML *
3131 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags);
3132
3133 static JSXML *
3134 DeepCopy(JSContext *cx, JSXML *xml, JSObject *obj, uintN flags)
3135 {
3136 JSXML *copy;
3137
3138 /* Our caller may not be protecting newborns with a local root scope. */
3139 if (!js_EnterLocalRootScope(cx))
3140 return NULL;
3141 copy = DeepCopyInLRS(cx, xml, flags);
3142 if (copy) {
3143 if (obj) {
3144 /* Caller provided the object for this copy, hook 'em up. */
3145 obj->setPrivate(copy);
3146 copy->object = obj;
3147 } else if (!js_GetXMLObject(cx, copy)) {
3148 copy = NULL;
3149 }
3150 }
3151 js_LeaveLocalRootScopeWithResult(cx, (jsval) copy);
3152 return copy;
3153 }
3154
3155 /*
3156 * (i) We must be in a local root scope (InLRS).
3157 * (ii) parent must have a rooted object.
3158 * (iii) from's owning object must be locked if not thread-local.
3159 */
3160 static JSBool
3161 DeepCopySetInLRS(JSContext *cx, JSXMLArray *from, JSXMLArray *to, JSXML *parent,
3162 uintN flags)
3163 {
3164 uint32 j, n;
3165 JSXML *kid2;
3166 JSString *str;
3167
3168 JS_ASSERT(cx->localRootStack);
3169
3170 n = from->length;
3171 if (!XMLArraySetCapacity(cx, to, n))
3172 return JS_FALSE;
3173
3174 JSXMLArrayCursor cursor(from);
3175 j = 0;
3176 while (JSXML *kid = (JSXML *) cursor.getNext()) {
3177 if ((flags & XSF_IGNORE_COMMENTS) &&
3178 kid->xml_class == JSXML_CLASS_COMMENT) {
3179 continue;
3180 }
3181 if ((flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS) &&
3182 kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
3183 continue;
3184 }
3185 if ((flags & XSF_IGNORE_WHITESPACE) &&
3186 (kid->xml_flags & XMLF_WHITESPACE_TEXT)) {
3187 continue;
3188 }
3189 kid2 = DeepCopyInLRS(cx, kid, flags);
3190 if (!kid2) {
3191 to->length = j;
3192 return JS_FALSE;
3193 }
3194
3195 if ((flags & XSF_IGNORE_WHITESPACE) &&
3196 n > 1 && kid2->xml_class == JSXML_CLASS_TEXT) {
3197 str = ChompXMLWhitespace(cx, kid2->xml_value);
3198 if (!str) {
3199 to->length = j;
3200 return JS_FALSE;
3201 }
3202 kid2->xml_value = str;
3203 }
3204
3205 XMLARRAY_SET_MEMBER(to, j, kid2);
3206 ++j;
3207 if (parent->xml_class != JSXML_CLASS_LIST)
3208 kid2->parent = parent;
3209 }
3210
3211 if (j < n)
3212 XMLArrayTrim(to);
3213 return JS_TRUE;
3214 }
3215
3216 static JSXML *
3217 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags)
3218 {
3219 JSXML *copy;
3220 JSObject *qn;
3221 JSBool ok;
3222 uint32 i, n;
3223 JSObject *ns, *ns2;
3224
3225 /* Our caller must be protecting newborn objects. */
3226 JS_ASSERT(cx->localRootStack);
3227
3228 JS_CHECK_RECURSION(cx, return NULL);
3229
3230 copy = js_NewXML(cx, JSXMLClass(xml->xml_class));
3231 if (!copy)
3232 return NULL;
3233 qn = xml->name;
3234 if (qn) {
3235 qn = NewXMLQName(cx, GetURI(qn), GetPrefix(qn), GetLocalName(qn));
3236 if (!qn) {
3237 ok = JS_FALSE;
3238 goto out;
3239 }
3240 }
3241 copy->name = qn;
3242 copy->xml_flags = xml->xml_flags;
3243
3244 if (JSXML_HAS_VALUE(xml)) {
3245 copy->xml_value = xml->xml_value;
3246 ok = JS_TRUE;
3247 } else {
3248 ok = DeepCopySetInLRS(cx, &xml->xml_kids, &copy->xml_kids, copy, flags);
3249 if (!ok)
3250 goto out;
3251
3252 if (xml->xml_class == JSXML_CLASS_LIST) {
3253 copy->xml_target = xml->xml_target;
3254 copy->xml_targetprop = xml->xml_targetprop;
3255 } else {
3256 n = xml->xml_namespaces.length;
3257 ok = XMLArraySetCapacity(cx, &copy->xml_namespaces, n);
3258 if (!ok)
3259 goto out;
3260 for (i = 0; i < n; i++) {
3261 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3262 if (!ns)
3263 continue;
3264 ns2 = NewXMLNamespace(cx, GetPrefix(ns), GetURI(ns),
3265 IsDeclared(ns));
3266 if (!ns2) {
3267 copy->xml_namespaces.length = i;
3268 ok = JS_FALSE;
3269 goto out;
3270 }
3271 XMLARRAY_SET_MEMBER(&copy->xml_namespaces, i, ns2);
3272 }
3273