/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Igor Bukanov * Milen Nankov * David P. Caldwell <inonit@inonit.com> * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package org.mozilla.javascript.xmlimpl; import org.mozilla.javascript.*; class XMLName extends Ref { static final long serialVersionUID = 3832176310755686977L; private static boolean isNCNameStartChar(int c) { if ((c & ~0x7F) == 0) { // Optimize for ASCII and use A..Z < _ < a..z if (c >= 'a') { return c <= 'z'; } else if (c >= 'A') { if (c <= 'Z') { return true; } return c == '_'; } } else if ((c & ~0x1FFF) == 0) { return (0xC0 <= c && c <= 0xD6) || (0xD8 <= c && c <= 0xF6) || (0xF8 <= c && c <= 0x2FF) || (0x370 <= c && c <= 0x37D) || 0x37F <= c; } return (0x200C <= c && c <= 0x200D) || (0x2070 <= c && c <= 0x218F) || (0x2C00 <= c && c <= 0x2FEF) || (0x3001 <= c && c <= 0xD7FF) || (0xF900 <= c && c <= 0xFDCF) || (0xFDF0 <= c && c <= 0xFFFD) || (0x10000 <= c && c <= 0xEFFFF); } private static boolean isNCNameChar(int c) { if ((c & ~0x7F) == 0) { // Optimize for ASCII and use - < . < 0..9 < A..Z < _ < a..z if (c >= 'a') { return c <= 'z'; } else if (c >= 'A') { if (c <= 'Z') { return true; } return c == '_'; } else if (c >= '0') { return c <= '9'; } else { return c == '-' || c == '.'; } } else if ((c & ~0x1FFF) == 0) { return isNCNameStartChar(c) || c == 0xB7 || (0x300 <= c && c <= 0x36F); } return isNCNameStartChar(c) || (0x203F <= c && c <= 0x2040); } // This means "accept" in the parsing sense // See ECMA357 13.1.2.1 static boolean accept(Object nameObj) { String name; try { name = ScriptRuntime.toString(nameObj); } catch (EcmaError ee) { if ("TypeError".equals(ee.getName())) { return false; } throw ee; } // See http://w3.org/TR/xml-names11/#NT-NCName int length = name.length(); if (length != 0) { if (isNCNameStartChar(name.charAt(0))) { for (int i = 1; i != length; ++i) { if (!isNCNameChar(name.charAt(i))) { return false; } } return true; } } return false; } private XmlNode.QName qname; private boolean isAttributeName; private boolean isDescendants; private XMLObjectImpl xmlObject; private XMLName() { } static XMLName formStar() { XMLName rv = new XMLName(); rv.qname = XmlNode.QName.create(null, null); return rv; } /** @deprecated */ static XMLName formProperty(XmlNode.Namespace namespace, String localName) { if (localName != null && localName.equals("*")) localName = null; XMLName rv = new XMLName(); rv.qname = XmlNode.QName.create(namespace, localName); return rv; } /** TODO: marked deprecated by original author */ static XMLName formProperty(String uri, String localName) { return formProperty(XmlNode.Namespace.create(uri), localName); } /** TODO: marked deprecated by original implementor */ static XMLName create(String defaultNamespaceUri, String name) { if (name == null) throw new IllegalArgumentException(); int l = name.length(); if (l != 0) { char firstChar = name.charAt(0); if (firstChar == '*') { if (l == 1) { return XMLName.formStar(); } } else if (firstChar == '@') { XMLName xmlName = XMLName.formProperty("", name.substring(1)); xmlName.setAttributeName(); return xmlName; } } return XMLName.formProperty(defaultNamespaceUri, name); } static XMLName create(XmlNode.QName qname, boolean attribute, boolean descendants) { XMLName rv = new XMLName(); rv.qname = qname; rv.isAttributeName = attribute; rv.isDescendants = descendants; return rv; } /** @deprecated */ static XMLName create(XmlNode.QName qname) { return create(qname, false, false); } void initXMLObject(XMLObjectImpl xmlObject) { if (xmlObject == null) throw new IllegalArgumentException(); if (this.xmlObject != null) throw new IllegalStateException(); this.xmlObject = xmlObject; } String uri() { if (qname.getNamespace() == null) return null; return qname.getNamespace().getUri(); } String localName() { if (qname.getLocalName() == null) return "*"; return qname.getLocalName(); } private void addDescendantChildren(XMLList list, XML target) { XMLName xmlName = this; if (target.isElement()) { XML[] children = target.getChildren(); for (int i=0; i<children.length; i++) { if (xmlName.matches( children[i] )) { list.addToList( children[i] ); } addDescendantChildren(list, children[i]); } } } void addMatchingAttributes(XMLList list, XML target) { XMLName name = this; if (target.isElement()) { XML[] attributes = target.getAttributes(); for (int i=0; i<attributes.length; i++) { if (name.matches( attributes[i]) ) { list.addToList( attributes[i] ); } } } } private void addDescendantAttributes(XMLList list, XML target) { if (target.isElement()) { addMatchingAttributes(list, target); XML[] children = target.getChildren(); for (int i=0; i<children.length; i++) { addDescendantAttributes(list, children[i]); } } } XMLList matchDescendantAttributes(XMLList rv, XML target) { rv.setTargets(target, null); addDescendantAttributes(rv, target); return rv; } XMLList matchDescendantChildren(XMLList rv, XML target) { rv.setTargets(target, null); addDescendantChildren(rv, target); return rv; } void addDescendants(XMLList rv, XML target) { XMLName xmlName = this; if (xmlName.isAttributeName()) { matchDescendantAttributes(rv, target); } else { matchDescendantChildren(rv, target); } } private void addAttributes(XMLList rv, XML target) { addMatchingAttributes(rv, target); } void addMatches(XMLList rv, XML target) { if (isDescendants()) { addDescendants(rv, target); } else if (isAttributeName()) { addAttributes(rv, target); } else { XML[] children = target.getChildren(); if (children != null) { for (int i=0; i<children.length; i++) { if (this.matches(children[i])) { rv.addToList( children[i] ); } } } rv.setTargets(target, this.toQname()); } } XMLList getMyValueOn(XML target) { XMLList rv = target.newXMLList(); addMatches(rv, target); return rv; } void setMyValueOn(XML target, Object value) { // Special-case checks for undefined and null if (value == null) { value = "null"; } else if (value instanceof Undefined) { value = "undefined"; } XMLName xmlName = this; // Get the named property if (xmlName.isAttributeName()) { target.setAttribute(xmlName, value); } else if (xmlName.uri() == null && xmlName.localName().equals("*")) { target.setChildren(value); } else { // Convert text into XML if needed. XMLObjectImpl xmlValue = null; if (value instanceof XMLObjectImpl) { xmlValue = (XMLObjectImpl)value; // Check for attribute type and convert to textNode if (xmlValue instanceof XML) { if (((XML)xmlValue).isAttribute()) { xmlValue = target.makeXmlFromString(xmlName, xmlValue.toString()); } } if (xmlValue instanceof XMLList) { for (int i = 0; i < xmlValue.length(); i++) { XML xml = ((XMLList) xmlValue).item(i); if (xml.isAttribute()) { ((XMLList)xmlValue).replace(i, target.makeXmlFromString(xmlName, xml.toString())); } } } } else { xmlValue = target.makeXmlFromString(xmlName, ScriptRuntime.toString(value)); } XMLList matches = target.getPropertyList(xmlName); if (matches.length() == 0) { target.appendChild(xmlValue); } else { // Remove all other matches for (int i = 1; i < matches.length(); i++) { target.removeChild(matches.item(i).childIndex()); } // Replace first match with new value. XML firstMatch = matches.item(0); target.replace(firstMatch.childIndex(), xmlValue); } } } @Override public boolean has(Context cx) { if (xmlObject == null) { return false; } return xmlObject.hasXMLProperty(this); } @Override public Object get(Context cx) { if (xmlObject == null) { throw ScriptRuntime.undefReadError(Undefined.instance, toString()); } return xmlObject.getXMLProperty(this); } @Override public Object set(Context cx, Object value) { if (xmlObject == null) { throw ScriptRuntime.undefWriteError(Undefined.instance, toString(), value); } // Assignment to descendants causes parse error on bad reference // and this should not be called if (isDescendants) throw Kit.codeBug(); xmlObject.putXMLProperty(this, value); return value; } @Override public boolean delete(Context cx) { if (xmlObject == null) { return true; } xmlObject.deleteXMLProperty(this); return !xmlObject.hasXMLProperty(this); } @Override public String toString() { //return qname.localName(); StringBuffer buff = new StringBuffer(); if (isDescendants) buff.append(".."); if (isAttributeName) buff.append('@'); if (uri() == null) { buff.append('*'); if(localName().equals("*")) { return buff.toString(); } } else { buff.append('"').append(uri()).append('"'); } buff.append(':').append(localName()); return buff.toString(); } final XmlNode.QName toQname() { return this.qname; } final boolean matchesLocalName(String localName) { return localName().equals("*") || localName().equals(localName); } final boolean matchesElement(XmlNode.QName qname) { if (this.uri() == null || this.uri().equals(qname.getNamespace().getUri())) { if (this.localName().equals("*") || this.localName().equals(qname.getLocalName())) { return true; } } return false; } final boolean matches(XML node) { XmlNode.QName qname = node.getNodeQname(); String nodeUri = null; if (qname.getNamespace() != null) { nodeUri = qname.getNamespace().getUri(); } if (isAttributeName) { if (node.isAttribute()) { if (this.uri() == null || this.uri().equals(nodeUri)) { if (this.localName().equals("*") || this.localName().equals(qname.getLocalName())) { return true; } } return false; } else { // TODO Could throw exception maybe, should not call this method on attribute name with arbitrary node type // unless we traverse all attributes and children habitually return false; } } else { if ( this.uri() == null || ((node.isElement()) && this.uri().equals(nodeUri)) ) { if (localName().equals("*")) return true; if (node.isElement()) { if (localName().equals(qname.getLocalName())) return true; } } return false; } } /* TODO: marked deprecated by original author */ boolean isAttributeName() { return isAttributeName; } // TODO Fix whether this is an attribute XMLName at construction? // Marked deprecated by original author void setAttributeName() { // if (isAttributeName) throw new IllegalStateException(); isAttributeName = true; } /* TODO: was marked deprecated by original author */ boolean isDescendants() { return isDescendants; } // TODO Fix whether this is an descendant XMLName at construction? /** @deprecated */ void setIsDescendants() { // if (isDescendants) throw new IllegalStateException(); isDescendants = true; } }