/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xerces" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, International
* Business Machines, Inc., http://www.apache.org. For more
* information on the Apache Software Foundation, please see
*
* This class doesn't directly support mutation events, however, it notifies * the document when mutations are performed so that the document class do so. * * @version $Id: AttributeMap.java,v 1.28 2004/02/16 23:09:35 mrglavas 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 { 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 (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(arg.getNodeName(),0); AttrImpl previous = null; if (i >= 0) { previous = (AttrImpl) nodes.elementAt(i); nodes.setElementAt(arg,i); 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 Vector(5, 10); } nodes.insertElementAt(arg, i); } // 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 { 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 (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.elementAt(i); nodes.setElementAt(arg,i); 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.elementAt(i); nodes.insertElementAt(arg,i); } else { i = -1 - i; // Insert point (may be end of list) if (null == nodes) { nodes = new Vector(5, 10); } nodes.insertElementAt(arg, i); } } // 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) { for (int i = 0; i < nodes.size(); i++) { if (nodes.elementAt(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.elementAt(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.setElementAt(clone, index); if (attr.isIdAttribute()) { ownerDocument.putIdentifier(clone.getNodeValue(), (ElementImpl)ownerNode); } } else { nodes.removeElementAt(index); } } else { nodes.removeElementAt(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.
* 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 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) { 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(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.elementAt(i); CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); 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.setElementAt(clone, i); if (clone.isIdAttribute()) { ownerDocument.putIdentifier(clone.getNodeValue(), (ElementImpl)ownerNode); } } else { nodes.removeElementAt(i); } } else { nodes.removeElementAt(i); } } else { nodes.removeElementAt(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) { Vector srcnodes = srcmap.nodes; if (srcnodes != null) { int size = srcnodes.size(); if (size != 0) { if (nodes == null) { nodes = new Vector(size); } nodes.setSize(size); for (int i = 0; i < size; ++i) { NodeImpl n = (NodeImpl) srcnodes.elementAt(i); NodeImpl clone = (NodeImpl) n.cloneNode(true); clone.isSpecified(n.isSpecified()); nodes.setElementAt(clone, i); 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.elementAt(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.elementAt(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.elementAt(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.insertElementAt(clone, i); } } } } // reconcileDefaults() } // class AttributeMap