org.mozilla.javascript.xmlimpl.XMLLibImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhino-runtime Show documentation
Show all versions of rhino-runtime Show documentation
Rhino JavaScript runtime jar, excludes tools & JSR-223 Script Engine wrapper.
The newest version!
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript.xmlimpl;
import java.io.Serializable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Kit;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Ref;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.Wrapper;
import org.mozilla.javascript.xml.XMLLib;
import org.mozilla.javascript.xml.XMLObject;
import org.xml.sax.SAXException;
public final class XMLLibImpl extends XMLLib implements Serializable {
//
// EXPERIMENTAL Java interface
//
private static final long serialVersionUID = 1L;
/** This experimental interface is undocumented. */
public static org.w3c.dom.Node toDomNode(Object xmlObject) {
// Could return DocumentFragment for XMLList
// Probably a single node for XMLList with one element
if (xmlObject instanceof XML) {
return ((XML) xmlObject).toDomNode();
} else {
throw new IllegalArgumentException("xmlObject is not an XML object in JavaScript.");
}
}
public static void init(Context cx, Scriptable scope, boolean sealed) {
XMLLibImpl lib = new XMLLibImpl(scope);
XMLLib bound = lib.bindToScope(scope);
if (bound == lib) {
lib.exportToScope(sealed);
}
}
@Override
public void setIgnoreComments(boolean b) {
options.setIgnoreComments(b);
}
@Override
public void setIgnoreWhitespace(boolean b) {
options.setIgnoreWhitespace(b);
}
@Override
public void setIgnoreProcessingInstructions(boolean b) {
options.setIgnoreProcessingInstructions(b);
}
@Override
public void setPrettyPrinting(boolean b) {
options.setPrettyPrinting(b);
}
@Override
public void setPrettyIndent(int i) {
options.setPrettyIndent(i);
}
@Override
public boolean isIgnoreComments() {
return options.isIgnoreComments();
}
@Override
public boolean isIgnoreProcessingInstructions() {
return options.isIgnoreProcessingInstructions();
}
@Override
public boolean isIgnoreWhitespace() {
return options.isIgnoreWhitespace();
}
@Override
public boolean isPrettyPrinting() {
return options.isPrettyPrinting();
}
@Override
public int getPrettyIndent() {
return options.getPrettyIndent();
}
private Scriptable globalScope;
private XML xmlPrototype;
private XMLList xmlListPrototype;
private Namespace namespacePrototype;
private QName qnamePrototype;
private XmlProcessor options = new XmlProcessor();
private XMLLibImpl(Scriptable globalScope) {
this.globalScope = globalScope;
}
/** @deprecated */
@Deprecated
QName qnamePrototype() {
return qnamePrototype;
}
/** @deprecated */
@Deprecated
Scriptable globalScope() {
return globalScope;
}
XmlProcessor getProcessor() {
return options;
}
private void exportToScope(boolean sealed) {
xmlPrototype = newXML(XmlNode.createText(options, ""));
xmlListPrototype = newXMLList();
namespacePrototype = Namespace.create(this.globalScope, null, XmlNode.Namespace.GLOBAL);
qnamePrototype =
QName.create(
this,
this.globalScope,
null,
XmlNode.QName.create(XmlNode.Namespace.create(""), ""));
xmlPrototype.exportAsJSClass(sealed);
xmlListPrototype.exportAsJSClass(sealed);
namespacePrototype.exportAsJSClass(sealed);
qnamePrototype.exportAsJSClass(sealed);
}
/** @deprecated */
@Deprecated
XMLName toAttributeName(Context cx, Object nameValue) {
if (nameValue instanceof XMLName) {
// TODO Will this always be an XMLName of type attribute name?
return (XMLName) nameValue;
} else if (nameValue instanceof QName) {
return XMLName.create(((QName) nameValue).getDelegate(), true, false);
} else if (nameValue instanceof Boolean
|| nameValue instanceof Number
|| nameValue == Undefined.instance
|| nameValue == null) {
throw badXMLName(nameValue);
} else {
// TODO Not 100% sure that putting these in global namespace is the right thing to
// do
String localName = null;
if (nameValue instanceof String) {
localName = (String) nameValue;
} else {
localName = ScriptRuntime.toString(nameValue);
}
if (localName != null && localName.equals("*")) localName = null;
return XMLName.create(
XmlNode.QName.create(XmlNode.Namespace.create(""), localName), true, false);
}
}
private static RuntimeException badXMLName(Object value) {
String msg;
if (value instanceof Number) {
msg = "Can not construct XML name from number: ";
} else if (value instanceof Boolean) {
msg = "Can not construct XML name from boolean: ";
} else if (value == Undefined.instance || value == null) {
msg = "Can not construct XML name from ";
} else {
throw new IllegalArgumentException(value.toString());
}
return ScriptRuntime.typeError(msg + ScriptRuntime.toString(value));
}
XMLName toXMLNameFromString(Context cx, String name) {
return XMLName.create(getDefaultNamespaceURI(cx), name);
}
/* TODO: Marked deprecated by original author */
XMLName toXMLName(Context cx, Object nameValue) {
XMLName result;
if (nameValue instanceof XMLName) {
result = (XMLName) nameValue;
} else if (nameValue instanceof QName) {
QName qname = (QName) nameValue;
result = XMLName.formProperty(qname.uri(), qname.localName());
} else if (nameValue instanceof String) {
result = toXMLNameFromString(cx, (String) nameValue);
} else if (nameValue instanceof Boolean
|| nameValue instanceof Number
|| nameValue == Undefined.instance
|| nameValue == null) {
throw badXMLName(nameValue);
} else {
String name = ScriptRuntime.toString(nameValue);
result = toXMLNameFromString(cx, name);
}
return result;
}
/**
* If value represents Uint32 index, make it available through
* ScriptRuntime.lastUint32Result(cx) and return null. Otherwise return the same value as
* toXMLName(cx, value).
*/
XMLName toXMLNameOrIndex(Context cx, Object value) {
XMLName result;
if (value instanceof XMLName) {
result = (XMLName) value;
} else if (value instanceof String) {
String str = (String) value;
long test = ScriptRuntime.testUint32String(str);
if (test >= 0) {
ScriptRuntime.storeUint32Result(cx, test);
result = null;
} else {
result = toXMLNameFromString(cx, str);
}
} else if (value instanceof Number) {
double d = ((Number) value).doubleValue();
long l = (long) d;
if (l == d && 0 <= l && l <= 0xFFFFFFFFL) {
ScriptRuntime.storeUint32Result(cx, l);
result = null;
} else {
throw badXMLName(value);
}
} else if (value instanceof QName) {
QName qname = (QName) value;
String uri = qname.uri();
boolean number = false;
result = null;
if (uri != null && uri.length() == 0) {
// Only in this case qname.toString() can resemble uint32
long test = ScriptRuntime.testUint32String(uri);
if (test >= 0) {
ScriptRuntime.storeUint32Result(cx, test);
number = true;
}
}
if (!number) {
result = XMLName.formProperty(uri, qname.localName());
}
} else if (value instanceof Boolean || value == Undefined.instance || value == null) {
throw badXMLName(value);
} else {
String str = ScriptRuntime.toString(value);
long test = ScriptRuntime.testUint32String(str);
if (test >= 0) {
ScriptRuntime.storeUint32Result(cx, test);
result = null;
} else {
result = toXMLNameFromString(cx, str);
}
}
return result;
}
Object addXMLObjects(Context cx, XMLObject obj1, XMLObject obj2) {
XMLList listToAdd = newXMLList();
if (obj1 instanceof XMLList) {
XMLList list1 = (XMLList) obj1;
if (list1.length() == 1) {
listToAdd.addToList(list1.item(0));
} else {
// Might be xmlFragment + xmlFragment + xmlFragment + ...;
// then the result will be an XMLList which we want to be an
// rValue and allow it to be assigned to an lvalue.
listToAdd = newXMLListFrom(obj1);
}
} else {
listToAdd.addToList(obj1);
}
if (obj2 instanceof XMLList) {
XMLList list2 = (XMLList) obj2;
for (int i = 0; i < list2.length(); i++) {
listToAdd.addToList(list2.item(i));
}
} else if (obj2 instanceof XML) {
listToAdd.addToList(obj2);
}
return listToAdd;
}
private Ref xmlPrimaryReference(Context cx, XMLName xmlName, Scriptable scope) {
XMLObjectImpl xmlObj;
XMLObjectImpl firstXml = null;
for (; ; ) {
// XML object can only present on scope chain as a wrapper
// of XMLWithScope
if (scope instanceof XMLWithScope) {
xmlObj = (XMLObjectImpl) scope.getPrototype();
if (xmlObj.hasXMLProperty(xmlName)) {
break;
}
if (firstXml == null) {
firstXml = xmlObj;
}
}
scope = scope.getParentScope();
if (scope == null) {
xmlObj = firstXml;
break;
}
}
// xmlObj == null corresponds to undefined as the target of
// the reference
if (xmlObj != null) {
xmlName.initXMLObject(xmlObj);
}
return xmlName;
}
Namespace castToNamespace(Context cx, Object namespaceObj) {
return this.namespacePrototype.castToNamespace(namespaceObj);
}
private String getDefaultNamespaceURI(Context cx) {
return getDefaultNamespace(cx).uri();
}
Namespace newNamespace(String uri) {
return this.namespacePrototype.newNamespace(uri);
}
Namespace getDefaultNamespace(Context cx) {
if (cx == null) {
cx = Context.getCurrentContext();
if (cx == null) {
return namespacePrototype;
}
}
Object ns = ScriptRuntime.searchDefaultNamespace(cx);
if (ns == null) {
return namespacePrototype;
} else {
if (ns instanceof Namespace) {
return (Namespace) ns;
} else {
// TODO Clarify or remove the following comment
// Should not happen but for now it could
// due to bad searchDefaultNamespace implementation.
return namespacePrototype;
}
}
}
Namespace[] createNamespaces(XmlNode.Namespace[] declarations) {
Namespace[] rv = new Namespace[declarations.length];
for (int i = 0; i < declarations.length; i++) {
rv[i] =
this.namespacePrototype.newNamespace(
declarations[i].getPrefix(), declarations[i].getUri());
}
return rv;
}
// See ECMA357 13.3.2
QName constructQName(Context cx, Object namespace, Object name) {
return this.qnamePrototype.constructQName(this, cx, namespace, name);
}
QName newQName(String uri, String localName, String prefix) {
return this.qnamePrototype.newQName(this, uri, localName, prefix);
}
QName constructQName(Context cx, Object nameValue) {
// return constructQName(cx, Undefined.instance, nameValue);
return this.qnamePrototype.constructQName(this, cx, nameValue);
}
QName castToQName(Context cx, Object qnameValue) {
return this.qnamePrototype.castToQName(this, cx, qnameValue);
}
QName newQName(XmlNode.QName qname) {
return QName.create(this, this.globalScope, this.qnamePrototype, qname);
}
XML newXML(XmlNode node) {
return new XML(this, this.globalScope, this.xmlPrototype, node);
}
/* TODO: Can this can be replaced by ecmaToXml below?
*/
final XML newXMLFromJs(Object inputObject) {
String frag;
if (inputObject == null || inputObject == Undefined.instance) {
frag = "";
} else if (inputObject instanceof XMLObjectImpl) {
// todo: faster way for XMLObjects?
frag = ((XMLObjectImpl) inputObject).toXMLString();
} else {
frag = ScriptRuntime.toString(inputObject);
}
if (frag.trim().startsWith("<>")) {
throw ScriptRuntime.typeError("Invalid use of XML object anonymous tags <>>.");
}
if (frag.indexOf("<") == -1) {
// Solo text node
return newXML(XmlNode.createText(options, frag));
}
return parse(frag);
}
private XML parse(String frag) {
try {
return newXML(
XmlNode.createElement(
options, getDefaultNamespaceURI(Context.getCurrentContext()), frag));
} catch (SAXException e) {
throw ScriptRuntime.typeError("Cannot parse XML: " + e.getMessage());
}
}
final XML ecmaToXml(Object object) {
// See ECMA357 10.3
if (object == null || object == Undefined.instance) {
throw ScriptRuntime.typeError("Cannot convert " + object + " to XML");
}
if (object instanceof XML) return (XML) object;
if (object instanceof XMLList) {
XMLList list = (XMLList) object;
if (list.getXML() != null) {
return list.getXML();
} else {
throw ScriptRuntime.typeError("Cannot convert list of >1 element to XML");
}
}
// TODO Technically we should fail on anything except a String, Number or Boolean
// See ECMA357 10.3
// Extension: if object is a DOM node, use that to construct the XML
// object.
if (object instanceof Wrapper) {
object = ((Wrapper) object).unwrap();
}
if (object instanceof org.w3c.dom.Node) {
org.w3c.dom.Node node = (org.w3c.dom.Node) object;
return newXML(XmlNode.createElementFromNode(node));
}
// Instead we just blindly cast to a String and let them convert anything.
String s = ScriptRuntime.toString(object);
// TODO Could this get any uglier?
if (s.length() > 0 && s.charAt(0) == '<') {
return parse(s);
} else {
return newXML(XmlNode.createText(options, s));
}
}
final XML newTextElementXML(XmlNode reference, XmlNode.QName qname, String value) {
return newXML(XmlNode.newElementWithText(options, reference, qname, value));
}
XMLList newXMLList() {
return new XMLList(this, this.globalScope, this.xmlListPrototype);
}
final XMLList newXMLListFrom(Object inputObject) {
XMLList rv = newXMLList();
if (inputObject == null || inputObject instanceof Undefined) {
return rv;
} else if (inputObject instanceof XML) {
XML xml = (XML) inputObject;
rv.getNodeList().add(xml);
return rv;
} else if (inputObject instanceof XMLList) {
XMLList xmll = (XMLList) inputObject;
rv.getNodeList().add(xmll.getNodeList());
return rv;
} else {
String frag = ScriptRuntime.toString(inputObject).trim();
if (!frag.startsWith("<>")) {
frag = "<>" + frag + ">";
}
frag = "" + frag.substring(2);
if (!frag.endsWith(">")) {
throw ScriptRuntime.typeError("XML with anonymous tag missing end anonymous tag");
}
frag = frag.substring(0, frag.length() - 3) + " ";
XML orgXML = newXMLFromJs(frag);
// Now orphan the children and add them to our XMLList.
XMLList children = orgXML.children();
for (int i = 0; i < children.getNodeList().length(); i++) {
// Copy here is so that they'll be orphaned (parent() will be undefined)
rv.getNodeList().add(((XML) children.item(i).copy()));
}
return rv;
}
}
XmlNode.QName toNodeQName(Context cx, Object namespaceValue, Object nameValue) {
// This is duplication of constructQName(cx, namespaceValue, nameValue)
// but for XMLName
String localName;
if (nameValue instanceof QName) {
QName qname = (QName) nameValue;
localName = qname.localName();
} else {
localName = ScriptRuntime.toString(nameValue);
}
XmlNode.Namespace ns;
if (namespaceValue == Undefined.instance) {
if ("*".equals(localName)) {
ns = null;
} else {
ns = getDefaultNamespace(cx).getDelegate();
}
} else if (namespaceValue == null) {
ns = null;
} else if (namespaceValue instanceof Namespace) {
ns = ((Namespace) namespaceValue).getDelegate();
} else {
ns = this.namespacePrototype.constructNamespace(namespaceValue).getDelegate();
}
if (localName != null && localName.equals("*")) localName = null;
return XmlNode.QName.create(ns, localName);
}
XmlNode.QName toNodeQName(Context cx, String name, boolean attribute) {
XmlNode.Namespace defaultNamespace = getDefaultNamespace(cx).getDelegate();
if (name != null && name.equals("*")) {
return XmlNode.QName.create(null, null);
} else {
if (attribute) {
return XmlNode.QName.create(XmlNode.Namespace.GLOBAL, name);
} else {
return XmlNode.QName.create(defaultNamespace, name);
}
}
}
/*
TODO: Too general; this should be split into overloaded methods.
Is that possible?
*/
XmlNode.QName toNodeQName(Context cx, Object nameValue, boolean attribute) {
if (nameValue instanceof XMLName) {
return ((XMLName) nameValue).toQname();
} else if (nameValue instanceof QName) {
QName qname = (QName) nameValue;
return qname.getDelegate();
} else if (nameValue instanceof Boolean
|| nameValue instanceof Number
|| nameValue == Undefined.instance
|| nameValue == null) {
throw badXMLName(nameValue);
} else {
String local = null;
if (nameValue instanceof String) {
local = (String) nameValue;
} else {
local = ScriptRuntime.toString(nameValue);
}
return toNodeQName(cx, local, attribute);
}
}
//
// Override methods from XMLLib
//
@Override
public boolean isXMLName(Context _cx, Object nameObj) {
return XMLName.accept(nameObj);
}
@Override
public Object toDefaultXmlNamespace(Context cx, Object uriValue) {
return this.namespacePrototype.constructNamespace(uriValue);
}
@Override
public String escapeTextValue(Object o) {
return options.escapeTextValue(o);
}
@Override
public String escapeAttributeValue(Object o) {
return options.escapeAttributeValue(o);
}
@Override
public Ref nameRef(Context cx, Object name, Scriptable scope, int memberTypeFlags) {
if ((memberTypeFlags & Node.ATTRIBUTE_FLAG) == 0) {
// should only be called for cases like @name or @[expr]
throw Kit.codeBug();
}
XMLName xmlName = toAttributeName(cx, name);
return xmlPrimaryReference(cx, xmlName, scope);
}
@Override
public Ref nameRef(
Context cx, Object namespace, Object name, Scriptable scope, int memberTypeFlags) {
XMLName xmlName = XMLName.create(toNodeQName(cx, namespace, name), false, false);
// No idea what is coming in from the parser in this case; is it detecting the "@"?
if ((memberTypeFlags & Node.ATTRIBUTE_FLAG) != 0) {
if (!xmlName.isAttributeName()) {
xmlName.setAttributeName();
}
}
return xmlPrimaryReference(cx, xmlName, scope);
}
}