001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
002: *
003: * ***** BEGIN LICENSE BLOCK *****
004: * Version: MPL 1.1/GPL 2.0
005: *
006: * The contents of this file are subject to the Mozilla Public License Version
007: * 1.1 (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: * http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the
014: * License.
015: *
016: * The Original Code is Rhino code, released
017: * May 6, 1999.
018: *
019: * The Initial Developer of the Original Code is
020: * Netscape Communications Corporation.
021: * Portions created by the Initial Developer are Copyright (C) 1997-2000
022: * the Initial Developer. All Rights Reserved.
023: *
024: * Contributor(s):
025: * Igor Bukanov
026: * David P. Caldwell <inonit@inonit.com>
027: *
028: * Alternatively, the contents of this file may be used under the terms of
029: * the GNU General Public License Version 2 or later (the "GPL"), in which
030: * case the provisions of the GPL are applicable instead of those above. If
031: * you wish to allow use of your version of this file only under the terms of
032: * the GPL and not to allow others to use your version of this file under the
033: * MPL, indicate your decision by deleting the provisions above and replacing
034: * them with the notice and other provisions required by the GPL. If you do
035: * not delete the provisions above, a recipient may use your version of this
036: * file under either the MPL or the GPL.
037: *
038: * ***** END LICENSE BLOCK ***** */
039:
040: package org.mozilla.javascript.xmlimpl;
041:
042: import java.io.Serializable;
043:
044: import org.mozilla.javascript.*;
045: import org.mozilla.javascript.xml.*;
046:
047: public final class XMLLibImpl extends XMLLib implements Serializable {
048: // TODO Document that this only works with JDK 1.5 or backport its
049: // features to earlier versions
050: private static final long serialVersionUID = 1L;
051:
052: //
053: // EXPERIMENTAL Java interface
054: //
055:
056: /**
057: This experimental interface is undocumented.
058: */
059: public static org.w3c.dom.Node toDomNode(Object xmlObject) {
060: // Could return DocumentFragment for XMLList
061: // Probably a single node for XMLList with one element
062: if (xmlObject instanceof XML) {
063: return ((XML) xmlObject).toDomNode();
064: } else {
065: throw new IllegalArgumentException(
066: "xmlObject is not an XML object in JavaScript.");
067: }
068: }
069:
070: public static void init(Context cx, Scriptable scope, boolean sealed) {
071: XMLLibImpl lib = new XMLLibImpl(scope);
072: XMLLib bound = lib.bindToScope(scope);
073: if (bound == lib) {
074: lib.exportToScope(sealed);
075: }
076: }
077:
078: private Scriptable globalScope;
079:
080: private XML xmlPrototype;
081: private XMLList xmlListPrototype;
082: private Namespace namespacePrototype;
083: private QName qnamePrototype;
084:
085: private XmlProcessor options = new XmlProcessor();
086:
087: private XMLLibImpl(Scriptable globalScope) {
088: this .globalScope = globalScope;
089: }
090:
091: /** @deprecated */
092: QName qnamePrototype() {
093: return qnamePrototype;
094: }
095:
096: /** @deprecated */
097: Scriptable globalScope() {
098: return globalScope;
099: }
100:
101: XmlProcessor getProcessor() {
102: return options;
103: }
104:
105: private void exportToScope(boolean sealed) {
106: xmlPrototype = newXML(XmlNode.createText(options, ""));
107: xmlListPrototype = newXMLList();
108: namespacePrototype = Namespace.create(this .globalScope, null,
109: XmlNode.Namespace.GLOBAL);
110: qnamePrototype = QName.create(this , this .globalScope, null,
111: XmlNode.QName.create(XmlNode.Namespace.create(""), ""));
112:
113: xmlPrototype.exportAsJSClass(sealed);
114: xmlListPrototype.exportAsJSClass(sealed);
115: namespacePrototype.exportAsJSClass(sealed);
116: qnamePrototype.exportAsJSClass(sealed);
117: }
118:
119: /** @deprecated */
120: XMLName toAttributeName(Context cx, Object nameValue) {
121: if (nameValue instanceof XMLName) {
122: // TODO Will this always be an XMLName of type attribute name?
123: return (XMLName) nameValue;
124: } else if (nameValue instanceof QName) {
125: return XMLName.create(((QName) nameValue).getDelegate(),
126: true, false);
127: } else if (nameValue instanceof Boolean
128: || nameValue instanceof Number
129: || nameValue == Undefined.instance || nameValue == null) {
130: throw badXMLName(nameValue);
131: } else {
132: // TODO Not 100% sure that putting these in global namespace is the right thing to do
133: String localName = null;
134: if (nameValue instanceof String) {
135: localName = (String) nameValue;
136: } else {
137: localName = ScriptRuntime.toString(nameValue);
138: }
139: if (localName != null && localName.equals("*"))
140: localName = null;
141: return XMLName.create(XmlNode.QName.create(
142: XmlNode.Namespace.create(""), localName), true,
143: false);
144: }
145: }
146:
147: private static RuntimeException badXMLName(Object value) {
148: String msg;
149: if (value instanceof Number) {
150: msg = "Can not construct XML name from number: ";
151: } else if (value instanceof Boolean) {
152: msg = "Can not construct XML name from boolean: ";
153: } else if (value == Undefined.instance || value == null) {
154: msg = "Can not construct XML name from ";
155: } else {
156: throw new IllegalArgumentException(value.toString());
157: }
158: return ScriptRuntime.typeError(msg
159: + ScriptRuntime.toString(value));
160: }
161:
162: XMLName toXMLNameFromString(Context cx, String name) {
163: return XMLName.create(getDefaultNamespaceURI(cx), name);
164: }
165:
166: /** @deprecated */
167: XMLName toXMLName(Context cx, Object nameValue) {
168: XMLName result;
169:
170: if (nameValue instanceof XMLName) {
171: result = (XMLName) nameValue;
172: } else if (nameValue instanceof QName) {
173: QName qname = (QName) nameValue;
174: result = XMLName.formProperty(qname.uri(), qname
175: .localName());
176: } else if (nameValue instanceof String) {
177: result = toXMLNameFromString(cx, (String) nameValue);
178: } else if (nameValue instanceof Boolean
179: || nameValue instanceof Number
180: || nameValue == Undefined.instance || nameValue == null) {
181: throw badXMLName(nameValue);
182: } else {
183: String name = ScriptRuntime.toString(nameValue);
184: result = toXMLNameFromString(cx, name);
185: }
186:
187: return result;
188: }
189:
190: /**
191: * If value represents Uint32 index, make it available through
192: * ScriptRuntime.lastUint32Result(cx) and return null.
193: * Otherwise return the same value as toXMLName(cx, value).
194: */
195: XMLName toXMLNameOrIndex(Context cx, Object value) {
196: XMLName result;
197:
198: if (value instanceof XMLName) {
199: result = (XMLName) value;
200: } else if (value instanceof String) {
201: String str = (String) value;
202: long test = ScriptRuntime.testUint32String(str);
203: if (test >= 0) {
204: ScriptRuntime.storeUint32Result(cx, test);
205: result = null;
206: } else {
207: result = toXMLNameFromString(cx, str);
208: }
209: } else if (value instanceof Number) {
210: double d = ((Number) value).doubleValue();
211: long l = (long) d;
212: if (l == d && 0 <= l && l <= 0xFFFFFFFFL) {
213: ScriptRuntime.storeUint32Result(cx, l);
214: result = null;
215: } else {
216: throw badXMLName(value);
217: }
218: } else if (value instanceof QName) {
219: QName qname = (QName) value;
220: String uri = qname.uri();
221: boolean number = false;
222: result = null;
223: if (uri != null && uri.length() == 0) {
224: // Only in this case qname.toString() can resemble uint32
225: long test = ScriptRuntime.testUint32String(uri);
226: if (test >= 0) {
227: ScriptRuntime.storeUint32Result(cx, test);
228: number = true;
229: }
230: }
231: if (!number) {
232: result = XMLName.formProperty(uri, qname.localName());
233: }
234: } else if (value instanceof Boolean
235: || value == Undefined.instance || value == null) {
236: throw badXMLName(value);
237: } else {
238: String str = ScriptRuntime.toString(value);
239: long test = ScriptRuntime.testUint32String(str);
240: if (test >= 0) {
241: ScriptRuntime.storeUint32Result(cx, test);
242: result = null;
243: } else {
244: result = toXMLNameFromString(cx, str);
245: }
246: }
247:
248: return result;
249: }
250:
251: Object addXMLObjects(Context cx, XMLObject obj1, XMLObject obj2) {
252: XMLList listToAdd = newXMLList();
253:
254: if (obj1 instanceof XMLList) {
255: XMLList list1 = (XMLList) obj1;
256: if (list1.length() == 1) {
257: listToAdd.addToList(list1.item(0));
258: } else {
259: // Might be xmlFragment + xmlFragment + xmlFragment + ...;
260: // then the result will be an XMLList which we want to be an
261: // rValue and allow it to be assigned to an lvalue.
262: listToAdd = newXMLListFrom(obj1);
263: }
264: } else {
265: listToAdd.addToList(obj1);
266: }
267:
268: if (obj2 instanceof XMLList) {
269: XMLList list2 = (XMLList) obj2;
270: for (int i = 0; i < list2.length(); i++) {
271: listToAdd.addToList(list2.item(i));
272: }
273: } else if (obj2 instanceof XML) {
274: listToAdd.addToList(obj2);
275: }
276:
277: return listToAdd;
278: }
279:
280: private Ref xmlPrimaryReference(Context cx, XMLName xmlName,
281: Scriptable scope) {
282: XMLObjectImpl xmlObj;
283: XMLObjectImpl firstXml = null;
284: for (;;) {
285: // XML object can only present on scope chain as a wrapper
286: // of XMLWithScope
287: if (scope instanceof XMLWithScope) {
288: xmlObj = (XMLObjectImpl) scope.getPrototype();
289: if (xmlObj.hasXMLProperty(xmlName)) {
290: break;
291: }
292: if (firstXml == null) {
293: firstXml = xmlObj;
294: }
295: }
296: scope = scope.getParentScope();
297: if (scope == null) {
298: xmlObj = firstXml;
299: break;
300: }
301: }
302:
303: // xmlObj == null corresponds to undefined as the target of
304: // the reference
305: if (xmlObj != null) {
306: xmlName.initXMLObject(xmlObj);
307: }
308: return xmlName;
309: }
310:
311: Namespace castToNamespace(Context cx, Object namespaceObj) {
312: return this .namespacePrototype.castToNamespace(namespaceObj);
313: }
314:
315: private String getDefaultNamespaceURI(Context cx) {
316: return getDefaultNamespace(cx).uri();
317: }
318:
319: Namespace newNamespace(String uri) {
320: return this .namespacePrototype.newNamespace(uri);
321: }
322:
323: Namespace getDefaultNamespace(Context cx) {
324: if (cx == null) {
325: cx = Context.getCurrentContext();
326: if (cx == null) {
327: return namespacePrototype;
328: }
329: }
330:
331: Object ns = ScriptRuntime.searchDefaultNamespace(cx);
332: if (ns == null) {
333: return namespacePrototype;
334: } else {
335: if (ns instanceof Namespace) {
336: return (Namespace) ns;
337: } else {
338: // TODO Clarify or remove the following comment
339: // Should not happen but for now it could
340: // due to bad searchDefaultNamespace implementation.
341: return namespacePrototype;
342: }
343: }
344: }
345:
346: Namespace[] createNamespaces(XmlNode.Namespace[] declarations) {
347: Namespace[] rv = new Namespace[declarations.length];
348: for (int i = 0; i < declarations.length; i++) {
349: rv[i] = this .namespacePrototype.newNamespace(
350: declarations[i].getPrefix(), declarations[i]
351: .getUri());
352: }
353: return rv;
354: }
355:
356: // See ECMA357 13.3.2
357: QName constructQName(Context cx, Object namespace, Object name) {
358: return this .qnamePrototype.constructQName(this , cx, namespace,
359: name);
360: }
361:
362: QName newQName(String uri, String localName, String prefix) {
363: return this .qnamePrototype.newQName(this , uri, localName,
364: prefix);
365: }
366:
367: QName constructQName(Context cx, Object nameValue) {
368: // return constructQName(cx, Undefined.instance, nameValue);
369: return this .qnamePrototype.constructQName(this , cx, nameValue);
370: }
371:
372: QName castToQName(Context cx, Object qnameValue) {
373: return this .qnamePrototype.castToQName(this , cx, qnameValue);
374: }
375:
376: QName newQName(XmlNode.QName qname) {
377: return QName.create(this , this .globalScope,
378: this .qnamePrototype, qname);
379: }
380:
381: XML newXML(XmlNode node) {
382: return new XML(this , this .globalScope, this .xmlPrototype, node);
383: }
384:
385: /**
386: @deprecated I believe this can be replaced by ecmaToXml below.
387: */
388: final XML newXMLFromJs(Object inputObject) {
389: String frag;
390:
391: if (inputObject == null || inputObject == Undefined.instance) {
392: frag = "";
393: } else if (inputObject instanceof XMLObjectImpl) {
394: // todo: faster way for XMLObjects?
395: frag = ((XMLObjectImpl) inputObject).toXMLString();
396: } else {
397: frag = ScriptRuntime.toString(inputObject);
398: }
399:
400: if (frag.trim().startsWith("<>")) {
401: throw ScriptRuntime
402: .typeError("Invalid use of XML object anonymous tags <></>.");
403: }
404:
405: if (frag.indexOf("<") == -1) {
406: // Solo text node
407: return newXML(XmlNode.createText(options, frag));
408: }
409: return parse(frag);
410: }
411:
412: private XML parse(String frag) {
413: try {
414: return newXML(XmlNode
415: .createElement(options,
416: getDefaultNamespaceURI(Context
417: .getCurrentContext()), frag));
418: } catch (org.xml.sax.SAXException e) {
419: throw ScriptRuntime.typeError("Cannot parse XML: "
420: + e.getMessage());
421: }
422: }
423:
424: final XML ecmaToXml(Object object) {
425: // See ECMA357 10.3
426: if (object == null || object == Undefined.instance)
427: throw ScriptRuntime.typeError("Cannot convert " + object
428: + " to XML");
429: if (object instanceof XML)
430: return (XML) object;
431: if (object instanceof XMLList) {
432: XMLList list = (XMLList) object;
433: if (list.getXML() != null) {
434: return list.getXML();
435: } else {
436: throw ScriptRuntime
437: .typeError("Cannot convert list of >1 element to XML");
438: }
439: }
440: // TODO Technically we should fail on anything except a String, Number or Boolean
441: // See ECMA357 10.3
442: // Extension: if object is a DOM node, use that to construct the XML
443: // object.
444: if (object instanceof Wrapper) {
445: object = ((Wrapper) object).unwrap();
446: }
447: if (object instanceof org.w3c.dom.Node) {
448: org.w3c.dom.Node node = (org.w3c.dom.Node) object;
449: return newXML(XmlNode.createElementFromNode(node));
450: }
451: // Instead we just blindly cast to a String and let them convert anything.
452: String s = ScriptRuntime.toString(object);
453: // TODO Could this get any uglier?
454: if (s.length() > 0 && s.charAt(0) == '<') {
455: return parse(s);
456: } else {
457: return newXML(XmlNode.createText(options, s));
458: }
459: }
460:
461: final XML newTextElementXML(XmlNode reference, XmlNode.QName qname,
462: String value) {
463: return newXML(XmlNode.newElementWithText(options, reference,
464: qname, value));
465: }
466:
467: XMLList newXMLList() {
468: return new XMLList(this , this .globalScope,
469: this .xmlListPrototype);
470: }
471:
472: final XMLList newXMLListFrom(Object inputObject) {
473: XMLList rv = newXMLList();
474:
475: if (inputObject == null || inputObject instanceof Undefined) {
476: return rv;
477: } else if (inputObject instanceof XML) {
478: XML xml = (XML) inputObject;
479: rv.getNodeList().add(xml);
480: return rv;
481: } else if (inputObject instanceof XMLList) {
482: XMLList xmll = (XMLList) inputObject;
483: rv.getNodeList().add(xmll.getNodeList());
484: return rv;
485: } else {
486: String frag = ScriptRuntime.toString(inputObject).trim();
487:
488: if (!frag.startsWith("<>")) {
489: frag = "<>" + frag + "</>";
490: }
491:
492: frag = "<fragment>" + frag.substring(2);
493: if (!frag.endsWith("</>")) {
494: throw ScriptRuntime
495: .typeError("XML with anonymous tag missing end anonymous tag");
496: }
497:
498: frag = frag.substring(0, frag.length() - 3) + "</fragment>";
499:
500: XML orgXML = newXMLFromJs(frag);
501:
502: // Now orphan the children and add them to our XMLList.
503: XMLList children = orgXML.children();
504:
505: for (int i = 0; i < children.getNodeList().length(); i++) {
506: // Copy here is so that they'll be orphaned (parent() will be undefined)
507: rv.getNodeList().add(((XML) children.item(i).copy()));
508: }
509: return rv;
510: }
511: }
512:
513: XmlNode.QName toNodeQName(Context cx, Object namespaceValue,
514: Object nameValue) {
515: // This is duplication of constructQName(cx, namespaceValue, nameValue)
516: // but for XMLName
517:
518: String localName;
519:
520: if (nameValue instanceof QName) {
521: QName qname = (QName) nameValue;
522: localName = qname.localName();
523: } else {
524: localName = ScriptRuntime.toString(nameValue);
525: }
526:
527: XmlNode.Namespace ns;
528: if (namespaceValue == Undefined.instance) {
529: if ("*".equals(localName)) {
530: ns = null;
531: } else {
532: ns = getDefaultNamespace(cx).getDelegate();
533: }
534: } else if (namespaceValue == null) {
535: ns = null;
536: } else if (namespaceValue instanceof Namespace) {
537: ns = ((Namespace) namespaceValue).getDelegate();
538: } else {
539: ns = this .namespacePrototype.constructNamespace(
540: namespaceValue).getDelegate();
541: }
542:
543: if (localName != null && localName.equals("*"))
544: localName = null;
545: return XmlNode.QName.create(ns, localName);
546: }
547:
548: XmlNode.QName toNodeQName(Context cx, String name, boolean attribute) {
549: XmlNode.Namespace defaultNamespace = getDefaultNamespace(cx)
550: .getDelegate();
551: if (name != null && name.equals("*")) {
552: return XmlNode.QName.create(null, null);
553: } else {
554: if (attribute) {
555: return XmlNode.QName.create(XmlNode.Namespace.GLOBAL,
556: name);
557: } else {
558: return XmlNode.QName.create(defaultNamespace, name);
559: }
560: }
561: }
562:
563: /**
564: @deprecated Too general; this should be split into overloaded methods.
565: Is that possible?
566: */
567: XmlNode.QName toNodeQName(Context cx, Object nameValue,
568: boolean attribute) {
569: if (nameValue instanceof XMLName) {
570: return ((XMLName) nameValue).toQname();
571: } else if (nameValue instanceof QName) {
572: QName qname = (QName) nameValue;
573: return qname.getDelegate();
574: } else if (nameValue instanceof Boolean
575: || nameValue instanceof Number
576: || nameValue == Undefined.instance || nameValue == null) {
577: throw badXMLName(nameValue);
578: } else {
579: String local = null;
580: if (nameValue instanceof String) {
581: local = (String) nameValue;
582: } else {
583: local = ScriptRuntime.toString(nameValue);
584: }
585: return toNodeQName(cx, local, attribute);
586: }
587: }
588:
589: //
590: // Override methods from XMLLib
591: //
592:
593: public boolean isXMLName(Context _cx, Object nameObj) {
594: return XMLName.accept(nameObj);
595: }
596:
597: public Object toDefaultXmlNamespace(Context cx, Object uriValue) {
598: return this .namespacePrototype.constructNamespace(uriValue);
599: }
600:
601: public String escapeTextValue(Object o) {
602: return options.escapeTextValue(o);
603: }
604:
605: public String escapeAttributeValue(Object o) {
606: return options.escapeAttributeValue(o);
607: }
608:
609: public Ref nameRef(Context cx, Object name, Scriptable scope,
610: int memberTypeFlags) {
611: if ((memberTypeFlags & Node.ATTRIBUTE_FLAG) == 0) {
612: // should only be called foir cases like @name or @[expr]
613: throw Kit.codeBug();
614: }
615: XMLName xmlName = toAttributeName(cx, name);
616: return xmlPrimaryReference(cx, xmlName, scope);
617: }
618:
619: public Ref nameRef(Context cx, Object namespace, Object name,
620: Scriptable scope, int memberTypeFlags) {
621: XMLName xmlName = XMLName.create(toNodeQName(cx, namespace,
622: name), false, false);
623:
624: // No idea what is coming in from the parser in this case; is it detecting the "@"?
625: if ((memberTypeFlags & Node.ATTRIBUTE_FLAG) != 0) {
626: if (!xmlName.isAttributeName()) {
627: xmlName.setAttributeName();
628: }
629: }
630:
631: return xmlPrimaryReference(cx, xmlName, scope);
632: }
633: }
|