| /* |
| * reserved comment block |
| * DO NOT REMOVE OR ALTER! |
| */ |
| /* |
| * Copyright 2000-2002,2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.sun.org.apache.xerces.internal.dom; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.w3c.dom.DOMException; |
| import org.w3c.dom.Node; |
| |
| /** |
| * AttributeMap inherits from NamedNodeMapImpl and extends it to deal with the |
| * specifics of storing attributes. These are: |
| * <ul> |
| * <li>managing ownership of attribute nodes |
| * <li>managing default attributes |
| * <li>firing mutation events |
| * </ul> |
| * <p> |
| * This class doesn't directly support mutation events, however, it notifies |
| * the document when mutations are performed so that the document class do so. |
| * |
| * @xerces.internal |
| * |
| * @version $Id: AttributeMap.java,v 1.7 2010-11-01 04:39:37 joehw Exp $ |
| */ |
| public class AttributeMap extends NamedNodeMapImpl { |
| |
| /** Serialization version. */ |
| static final long serialVersionUID = 8872606282138665383L; |
| |
| // |
| // Constructors |
| // |
| |
| /** Constructs a named node map. */ |
| protected AttributeMap(ElementImpl ownerNode, NamedNodeMapImpl defaults) { |
| super(ownerNode); |
| if (defaults != null) { |
| // initialize map with the defaults |
| cloneContent(defaults); |
| if (nodes != null) { |
| hasDefaults(true); |
| } |
| } |
| } |
| |
| /** |
| * Adds an attribute using its nodeName attribute. |
| * @see org.w3c.dom.NamedNodeMap#setNamedItem |
| * @return If the new Node replaces an existing node the replaced Node is |
| * returned, otherwise null is returned. |
| * @param arg |
| * An Attr node to store in this map. |
| * @exception org.w3c.dom.DOMException The exception description. |
| */ |
| public Node setNamedItem(Node arg) |
| throws DOMException { |
| |
| boolean errCheck = ownerNode.ownerDocument().errorChecking; |
| if (errCheck) { |
| if (isReadOnly()) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); |
| throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); |
| } |
| if (arg.getOwnerDocument() != ownerNode.ownerDocument()) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); |
| throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); |
| } |
| if (arg.getNodeType() != Node.ATTRIBUTE_NODE) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null); |
| throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg); |
| } |
| } |
| AttrImpl argn = (AttrImpl)arg; |
| |
| if (argn.isOwned()){ |
| if (errCheck && argn.getOwnerElement() != ownerNode) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INUSE_ATTRIBUTE_ERR", null); |
| throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, msg); |
| } |
| // replacing an Attribute with itself does nothing |
| return arg; |
| } |
| |
| |
| // set owner |
| argn.ownerNode = ownerNode; |
| argn.isOwned(true); |
| |
| int i = findNamePoint(argn.getNodeName(),0); |
| AttrImpl previous = null; |
| if (i >= 0) { |
| previous = (AttrImpl) nodes.get(i); |
| nodes.set(i, arg); |
| previous.ownerNode = ownerNode.ownerDocument(); |
| previous.isOwned(false); |
| // make sure it won't be mistaken with defaults in case it's reused |
| previous.isSpecified(true); |
| } else { |
| i = -1 - i; // Insert point (may be end of list) |
| if (null == nodes) { |
| nodes = new ArrayList(5); |
| } |
| nodes.add(i, arg); |
| } |
| |
| // notify document |
| ownerNode.ownerDocument().setAttrNode(argn, previous); |
| |
| // If the new attribute is not normalized, |
| // the owning element is inherently not normalized. |
| if (!argn.isNormalized()) { |
| ownerNode.isNormalized(false); |
| } |
| return previous; |
| |
| } // setNamedItem(Node):Node |
| |
| /** |
| * Adds an attribute using its namespaceURI and localName. |
| * @see org.w3c.dom.NamedNodeMap#setNamedItem |
| * @return If the new Node replaces an existing node the replaced Node is |
| * returned, otherwise null is returned. |
| * @param arg A node to store in a named node map. |
| */ |
| public Node setNamedItemNS(Node arg) |
| throws DOMException { |
| |
| boolean errCheck = ownerNode.ownerDocument().errorChecking; |
| if (errCheck) { |
| if (isReadOnly()) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); |
| throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); |
| } |
| if(arg.getOwnerDocument() != ownerNode.ownerDocument()) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); |
| throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); |
| } |
| if (arg.getNodeType() != Node.ATTRIBUTE_NODE) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null); |
| throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg); |
| } |
| } |
| AttrImpl argn = (AttrImpl)arg; |
| |
| if (argn.isOwned()){ |
| if (errCheck && argn.getOwnerElement() != ownerNode) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INUSE_ATTRIBUTE_ERR", null); |
| throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, msg); |
| } |
| // replacing an Attribute with itself does nothing |
| return arg; |
| } |
| |
| // set owner |
| argn.ownerNode = ownerNode; |
| argn.isOwned(true); |
| |
| int i = findNamePoint(argn.getNamespaceURI(), argn.getLocalName()); |
| AttrImpl previous = null; |
| if (i >= 0) { |
| previous = (AttrImpl) nodes.get(i); |
| nodes.set(i, arg); |
| previous.ownerNode = ownerNode.ownerDocument(); |
| previous.isOwned(false); |
| // make sure it won't be mistaken with defaults in case it's reused |
| previous.isSpecified(true); |
| } else { |
| // If we can't find by namespaceURI, localName, then we find by |
| // nodeName so we know where to insert. |
| i = findNamePoint(arg.getNodeName(),0); |
| if (i >=0) { |
| previous = (AttrImpl) nodes.get(i); |
| nodes.add(i, arg); |
| } else { |
| i = -1 - i; // Insert point (may be end of list) |
| if (null == nodes) { |
| nodes = new ArrayList(5); |
| } |
| nodes.add(i, arg); |
| } |
| } |
| // changed(true); |
| |
| // notify document |
| ownerNode.ownerDocument().setAttrNode(argn, previous); |
| |
| // If the new attribute is not normalized, |
| // the owning element is inherently not normalized. |
| if (!argn.isNormalized()) { |
| ownerNode.isNormalized(false); |
| } |
| return previous; |
| |
| } // setNamedItemNS(Node):Node |
| |
| /** |
| * Removes an attribute specified by name. |
| * @param name |
| * The name of a node to remove. If the |
| * removed attribute is known to have a default value, an |
| * attribute immediately appears containing the default value |
| * as well as the corresponding namespace URI, local name, |
| * and prefix when applicable. |
| * @return The node removed from the map if a node with such a name exists. |
| * @throws NOT_FOUND_ERR: Raised if there is no node named |
| * name in the map. |
| */ |
| /***/ |
| public Node removeNamedItem(String name) |
| throws DOMException { |
| return internalRemoveNamedItem(name, true); |
| } |
| |
| /** |
| * Same as removeNamedItem except that it simply returns null if the |
| * specified name is not found. |
| */ |
| Node safeRemoveNamedItem(String name) { |
| return internalRemoveNamedItem(name, false); |
| } |
| |
| |
| /** |
| * NON-DOM: Remove the node object |
| * |
| * NOTE: Specifically removes THIS NODE -- not the node with this |
| * name, nor the node with these contents. If node does not belong to |
| * this named node map, we throw a DOMException. |
| * |
| * @param item The node to remove |
| * @param addDefault true -- magically add default attribute |
| * @return Removed node |
| * @exception DOMException |
| */ |
| protected Node removeItem(Node item, boolean addDefault) |
| throws DOMException { |
| |
| int index = -1; |
| if (nodes != null) { |
| final int size = nodes.size(); |
| for (int i = 0; i < size; ++i) { |
| if (nodes.get(i) == item) { |
| index = i; |
| break; |
| } |
| } |
| } |
| if (index < 0) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); |
| throw new DOMException(DOMException.NOT_FOUND_ERR, msg); |
| } |
| |
| return remove((AttrImpl)item, index, addDefault); |
| } |
| |
| /** |
| * Internal removeNamedItem method allowing to specify whether an exception |
| * must be thrown if the specified name is not found. |
| */ |
| final protected Node internalRemoveNamedItem(String name, boolean raiseEx){ |
| if (isReadOnly()) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); |
| throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); |
| } |
| int i = findNamePoint(name,0); |
| if (i < 0) { |
| if (raiseEx) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); |
| throw new DOMException(DOMException.NOT_FOUND_ERR, msg); |
| } else { |
| return null; |
| } |
| } |
| |
| return remove((AttrImpl)nodes.get(i), i, true); |
| |
| } // internalRemoveNamedItem(String,boolean):Node |
| |
| private final Node remove(AttrImpl attr, int index, |
| boolean addDefault) { |
| |
| CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); |
| String name = attr.getNodeName(); |
| if (attr.isIdAttribute()) { |
| ownerDocument.removeIdentifier(attr.getValue()); |
| } |
| |
| if (hasDefaults() && addDefault) { |
| // If there's a default, add it instead |
| NamedNodeMapImpl defaults = |
| ((ElementImpl) ownerNode).getDefaultAttributes(); |
| |
| Node d; |
| if (defaults != null && |
| (d = defaults.getNamedItem(name)) != null && |
| findNamePoint(name, index+1) < 0) { |
| NodeImpl clone = (NodeImpl)d.cloneNode(true); |
| if (d.getLocalName() !=null){ |
| // we must rely on the name to find a default attribute |
| // ("test:attr"), but while copying it from the DOCTYPE |
| // we should not loose namespace URI that was assigned |
| // to the attribute in the instance document. |
| ((AttrNSImpl)clone).namespaceURI = attr.getNamespaceURI(); |
| } |
| clone.ownerNode = ownerNode; |
| clone.isOwned(true); |
| clone.isSpecified(false); |
| |
| nodes.set(index, clone); |
| if (attr.isIdAttribute()) { |
| ownerDocument.putIdentifier(clone.getNodeValue(), |
| (ElementImpl)ownerNode); |
| } |
| } else { |
| nodes.remove(index); |
| } |
| } else { |
| nodes.remove(index); |
| } |
| |
| // changed(true); |
| |
| // remove reference to owner |
| attr.ownerNode = ownerDocument; |
| attr.isOwned(false); |
| |
| // make sure it won't be mistaken with defaults in case it's |
| // reused |
| attr.isSpecified(true); |
| attr.isIdAttribute(false); |
| |
| // notify document |
| ownerDocument.removedAttrNode(attr, ownerNode, name); |
| |
| return attr; |
| } |
| |
| /** |
| * Introduced in DOM Level 2. <p> |
| * Removes an attribute specified by local name and namespace URI. |
| * @param namespaceURI |
| * The namespace URI of the node to remove. |
| * When it is null or an empty string, this |
| * method behaves like removeNamedItem. |
| * @param name The local name of the node to remove. If the |
| * removed attribute is known to have a default |
| * value, an attribute immediately appears |
| * containing the default value. |
| * @return Node The node removed from the map if a node with such |
| * a local name and namespace URI exists. |
| * @throws NOT_FOUND_ERR: Raised if there is no node named |
| * name in the map. |
| */ |
| public Node removeNamedItemNS(String namespaceURI, String name) |
| throws DOMException { |
| return internalRemoveNamedItemNS(namespaceURI, name, true); |
| } |
| |
| /** |
| * Same as removeNamedItem except that it simply returns null if the |
| * specified local name and namespace URI is not found. |
| */ |
| Node safeRemoveNamedItemNS(String namespaceURI, String name) { |
| return internalRemoveNamedItemNS(namespaceURI, name, false); |
| } |
| |
| /** |
| * Internal removeNamedItemNS method allowing to specify whether an |
| * exception must be thrown if the specified local name and namespace URI |
| * is not found. |
| */ |
| final protected Node internalRemoveNamedItemNS(String namespaceURI, |
| String name, |
| boolean raiseEx) { |
| |
| CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); |
| if (ownerDocument.errorChecking && isReadOnly()) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); |
| throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); |
| } |
| int i = findNamePoint(namespaceURI, name); |
| if (i < 0) { |
| if (raiseEx) { |
| String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); |
| throw new DOMException(DOMException.NOT_FOUND_ERR, msg); |
| } else { |
| return null; |
| } |
| } |
| |
| AttrImpl n = (AttrImpl)nodes.get(i); |
| |
| if (n.isIdAttribute()) { |
| ownerDocument.removeIdentifier(n.getValue()); |
| } |
| // If there's a default, add it instead |
| String nodeName = n.getNodeName(); |
| if (hasDefaults()) { |
| NamedNodeMapImpl defaults = ((ElementImpl) ownerNode).getDefaultAttributes(); |
| Node d; |
| if (defaults != null |
| && (d = defaults.getNamedItem(nodeName)) != null) |
| { |
| int j = findNamePoint(nodeName,0); |
| if (j>=0 && findNamePoint(nodeName, j+1) < 0) { |
| NodeImpl clone = (NodeImpl)d.cloneNode(true); |
| clone.ownerNode = ownerNode; |
| if (d.getLocalName() != null) { |
| // we must rely on the name to find a default attribute |
| // ("test:attr"), but while copying it from the DOCTYPE |
| // we should not loose namespace URI that was assigned |
| // to the attribute in the instance document. |
| ((AttrNSImpl)clone).namespaceURI = namespaceURI; |
| } |
| clone.isOwned(true); |
| clone.isSpecified(false); |
| nodes.set(i, clone); |
| if (clone.isIdAttribute()) { |
| ownerDocument.putIdentifier(clone.getNodeValue(), |
| (ElementImpl)ownerNode); |
| } |
| } else { |
| nodes.remove(i); |
| } |
| } else { |
| nodes.remove(i); |
| } |
| } else { |
| nodes.remove(i); |
| } |
| |
| // changed(true); |
| |
| // remove reference to owner |
| n.ownerNode = ownerDocument; |
| n.isOwned(false); |
| // make sure it won't be mistaken with defaults in case it's |
| // reused |
| n.isSpecified(true); |
| // update id table if needed |
| n.isIdAttribute(false); |
| |
| // notify document |
| ownerDocument.removedAttrNode(n, ownerNode, name); |
| |
| return n; |
| |
| } // internalRemoveNamedItemNS(String,String,boolean):Node |
| |
| // |
| // Public methods |
| // |
| |
| /** |
| * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones |
| * all the nodes contained in the map. |
| */ |
| |
| public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) { |
| AttributeMap newmap = |
| new AttributeMap((ElementImpl) ownerNode, null); |
| newmap.hasDefaults(hasDefaults()); |
| newmap.cloneContent(this); |
| return newmap; |
| } // cloneMap():AttributeMap |
| |
| /** |
| * Override parent's method to set the ownerNode correctly |
| */ |
| protected void cloneContent(NamedNodeMapImpl srcmap) { |
| List srcnodes = srcmap.nodes; |
| if (srcnodes != null) { |
| int size = srcnodes.size(); |
| if (size != 0) { |
| if (nodes == null) { |
| nodes = new ArrayList(size); |
| } |
| else { |
| nodes.clear(); |
| } |
| for (int i = 0; i < size; ++i) { |
| NodeImpl n = (NodeImpl) srcnodes.get(i); |
| NodeImpl clone = (NodeImpl) n.cloneNode(true); |
| clone.isSpecified(n.isSpecified()); |
| nodes.add(clone); |
| clone.ownerNode = ownerNode; |
| clone.isOwned(true); |
| } |
| } |
| } |
| } // cloneContent():AttributeMap |
| |
| |
| /** |
| * Move specified attributes from the given map to this one |
| */ |
| void moveSpecifiedAttributes(AttributeMap srcmap) { |
| int nsize = (srcmap.nodes != null) ? srcmap.nodes.size() : 0; |
| for (int i = nsize - 1; i >= 0; i--) { |
| AttrImpl attr = (AttrImpl) srcmap.nodes.get(i); |
| if (attr.isSpecified()) { |
| srcmap.remove(attr, i, false); |
| if (attr.getLocalName() != null) { |
| setNamedItem(attr); |
| } |
| else { |
| setNamedItemNS(attr); |
| } |
| } |
| } |
| } // moveSpecifiedAttributes(AttributeMap):void |
| |
| |
| /** |
| * Get this AttributeMap in sync with the given "defaults" map. |
| * @param defaults The default attributes map to sync with. |
| */ |
| protected void reconcileDefaults(NamedNodeMapImpl defaults) { |
| |
| // remove any existing default |
| int nsize = (nodes != null) ? nodes.size() : 0; |
| for (int i = nsize - 1; i >= 0; --i) { |
| AttrImpl attr = (AttrImpl) nodes.get(i); |
| if (!attr.isSpecified()) { |
| remove(attr, i, false); |
| } |
| } |
| // add the new defaults |
| if (defaults == null) { |
| return; |
| } |
| if (nodes == null || nodes.size() == 0) { |
| cloneContent(defaults); |
| } |
| else { |
| int dsize = defaults.nodes.size(); |
| for (int n = 0; n < dsize; ++n) { |
| AttrImpl d = (AttrImpl) defaults.nodes.get(n); |
| int i = findNamePoint(d.getNodeName(), 0); |
| if (i < 0) { |
| i = -1 - i; |
| NodeImpl clone = (NodeImpl) d.cloneNode(true); |
| clone.ownerNode = ownerNode; |
| clone.isOwned(true); |
| clone.isSpecified(false); |
| nodes.add(i, clone); |
| } |
| } |
| } |
| |
| } // reconcileDefaults() |
| |
| protected final int addItem (Node arg) { |
| |
| final AttrImpl argn = (AttrImpl) arg; |
| |
| // set owner |
| argn.ownerNode = ownerNode; |
| argn.isOwned(true); |
| |
| int i = findNamePoint(argn.getNamespaceURI(), argn.getLocalName()); |
| if (i >= 0) { |
| nodes.set(i, arg); |
| } |
| else { |
| // If we can't find by namespaceURI, localName, then we find by |
| // nodeName so we know where to insert. |
| i = findNamePoint(argn.getNodeName(),0); |
| if (i >= 0) { |
| nodes.add(i, arg); |
| } |
| else { |
| i = -1 - i; // Insert point (may be end of list) |
| if (null == nodes) { |
| nodes = new ArrayList(5); |
| } |
| nodes.add(i, arg); |
| } |
| } |
| |
| // notify document |
| ownerNode.ownerDocument().setAttrNode(argn, null); |
| return i; |
| } |
| |
| } // class AttributeMap |