blob: bcdad25a55e2bc4991f1565bd7956ae39157aa29 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2000 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
* <http://www.apache.org/>.
*/
package org.apache.xerces.dom;
import java.io.*;
import java.util.Vector;
import java.util.Enumeration;
import org.w3c.dom.*;
import org.apache.xerces.dom.events.MutationEventImpl;
import org.w3c.dom.events.*;
/**
* 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>
*
*/
public class AttributeMap extends NamedNodeMapImpl {
//
// 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()) {
throw
new DOMExceptionImpl(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"DOM001 Modification not allowed");
}
if(arg.getOwnerDocument() != ownerNode.ownerDocument()) {
throw new DOMExceptionImpl(DOMException.WRONG_DOCUMENT_ERR,
"DOM005 Wrong document");
}
NodeImpl argn = (NodeImpl)arg;
if (argn.isOwned()) {
throw new DOMExceptionImpl(DOMException.INUSE_ATTRIBUTE_ERR,
"DOM009 Attribute already in use");
}
// set owner
argn.ownerNode = ownerNode;
argn.isOwned(true);
int i = findNamePoint(arg.getNodeName(),0);
NodeImpl previous = null;
if (i >= 0) {
previous = (NodeImpl) 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);
}
if (NodeImpl.MUTATIONEVENTS &&
ownerNode.ownerDocument().mutationEvents) {
// MUTATION POST-EVENTS:
ownerNode.dispatchAggregateEvents(
(AttrImpl)arg,
previous==null ? null : previous.getNodeValue()
);
}
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()) {
throw
new DOMExceptionImpl(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"DOM001 Modification not allowed");
}
if(arg.getOwnerDocument() != ownerNode.ownerDocument()) {
throw new DOMExceptionImpl(DOMException.WRONG_DOCUMENT_ERR,
"DOM005 Wrong document");
}
NodeImpl argn = (NodeImpl)arg;
if (argn.isOwned()) {
throw new DOMExceptionImpl(DOMException.INUSE_ATTRIBUTE_ERR,
"DOM009 Attribute already in use");
}
// set owner
argn.ownerNode = ownerNode;
argn.isOwned(true);
int i = findNamePoint(argn.getNamespaceURI(), argn.getLocalName());
NodeImpl previous = null;
if (i >= 0) {
previous = (NodeImpl) 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 = (NodeImpl) 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);
// Only NamedNodeMaps containing attributes (those which are
// bound to an element) need report MutationEvents
if (NodeImpl.MUTATIONEVENTS
&& ownerNode.ownerDocument().mutationEvents)
{
// MUTATION POST-EVENTS:
ownerNode.dispatchAggregateEvents(
(AttrImpl)arg,
previous==null ? null : previous.getNodeValue()
);
}
return previous;
} // setNamedItem(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);
}
/**
* 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()) {
throw
new DOMExceptionImpl(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"DOM001 Modification not allowed");
}
int i = findNamePoint(name,0);
if (i < 0) {
if (raiseEx) {
throw new DOMExceptionImpl(DOMException.NOT_FOUND_ERR,
"DOM008 Not found");
} else {
return null;
}
}
LCount lc=null;
String oldvalue="";
AttrImpl enclosingAttribute=null;
if (NodeImpl.MUTATIONEVENTS
&& ownerNode.ownerDocument().mutationEvents)
{
// MUTATION PREPROCESSING AND PRE-EVENTS:
lc=LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED);
if(lc.captures+lc.bubbles+lc.defaults>0)
{
enclosingAttribute=(AttrImpl)(nodes.elementAt(i));
oldvalue=enclosingAttribute.getNodeValue();
}
} // End mutation preprocessing
NodeImpl n = (NodeImpl)nodes.elementAt(i);
// If there's a default, add it instead
if (hasDefaults()) {
NamedNodeMapImpl defaults =
((ElementImpl) ownerNode).getDefaultAttributes();
Node d;
if (defaults != null && (d = defaults.getNamedItem(name)) != null
&& findNamePoint(name, i+1) < 0) {
NodeImpl clone = (NodeImpl)d.cloneNode(true);
clone.ownerNode = ownerNode;
clone.isOwned(true);
clone.isSpecified(false);
nodes.setElementAt(clone, i);
} else {
nodes.removeElementAt(i);
}
} else {
nodes.removeElementAt(i);
}
// changed(true);
// remove owning element
n.ownerNode = ownerNode.ownerDocument();
n.isOwned(false);
// make sure it won't be mistaken with defaults in case it's reused
n.isSpecified(true);
// We can't use the standard dispatchAggregate, since it assumes
// that the Attr is still attached to an owner. This code is
// similar but dispatches to the previous owner, "element".
if(NodeImpl.MUTATIONEVENTS && ownerNode.ownerDocument().mutationEvents)
{
// If we have to send DOMAttrModified (determined earlier),
// do so.
if(lc.captures+lc.bubbles+lc.defaults>0) {
MutationEvent me= new MutationEventImpl();
//?????ownerDocument.createEvent("MutationEvents");
me.initMutationEvent(MutationEventImpl.DOM_ATTR_MODIFIED,
true, false,
null, n.getNodeValue(),
null, name);
ownerNode.dispatchEvent(me);
}
// We can hand off to process DOMSubtreeModified, though.
// Note that only the Element needs to be informed; the
// Attr's subtree has not been changed by this operation.
ownerNode.dispatchAggregateEvents(null,null);
}
return n;
} // removeNamedItem(String):Node
/**
* 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 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()) {
throw
new DOMExceptionImpl(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"DOM001 Modification not allowed");
}
int i = findNamePoint(namespaceURI, name);
if (i < 0) {
if (raiseEx) {
throw new DOMExceptionImpl(DOMException.NOT_FOUND_ERR,
"DOM008 Not found");
} else {
return null;
}
}
LCount lc=null;
String oldvalue="";
AttrImpl enclosingAttribute=null;
if (NodeImpl.MUTATIONEVENTS
&& ownerNode.ownerDocument().mutationEvents)
{
// MUTATION PREPROCESSING AND PRE-EVENTS:
lc=LCount.lookup(MutationEventImpl.DOM_ATTR_MODIFIED);
if(lc.captures+lc.bubbles+lc.defaults>0)
{
enclosingAttribute=(AttrImpl)(nodes.elementAt(i));
oldvalue=enclosingAttribute.getNodeValue();
}
} // End mutation preprocessing
NodeImpl n = (NodeImpl)nodes.elementAt(i);
// 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;
clone.isOwned(true);
clone.isSpecified(false);
nodes.setElementAt(clone, i);
} else {
nodes.removeElementAt(i);
}
} else {
nodes.removeElementAt(i);
}
} else {
nodes.removeElementAt(i);
}
// changed(true);
// Need to remove references to an Attr's owner before the
// MutationEvents fire.
n.ownerNode = ownerNode.ownerDocument();
n.isOwned(false);
// make sure it won't be mistaken with defaults in case it's reused
n.isSpecified(true);
// We can't use the standard dispatchAggregate, since it assumes
// that the Attr is still attached to an owner. This code is
// similar but dispatches to the previous owner, "element".
if(NodeImpl.MUTATIONEVENTS && ownerNode.ownerDocument().mutationEvents)
{
// If we have to send DOMAttrModified (determined earlier),
// do so.
if(lc.captures+lc.bubbles+lc.defaults>0) {
MutationEvent me= new MutationEventImpl();
//?????ownerDocument.createEvent("MutationEvents");
me.initMutationEvent(MutationEventImpl.DOM_ATTR_MODIFIED,
true, false,
null, n.getNodeValue(),
null, name);
ownerNode.dispatchEvent(me);
}
// We can hand off to process DOMSubtreeModified, though.
// Note that only the Element needs to be informed; the
// Attr's subtree has not been changed by this operation.
ownerNode.dispatchAggregateEvents(null,null);
}
return n;
} // removeNamedItem(String):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
//
// Protected methods
//
/**
* Subroutine: If this NamedNodeMap is backed by a "defaults" map (eg,
* if it's being used for Attributes of an XML file validated against
* a DTD), we need to deal with the risk that those defaults might
* have changed. Entries may have been added, changed, or removed, and
* if so we need to update our version of that information
* <P>
* Luckily, this currently applies _only_ to Attributes, which have a
* "specified" flag that allows us to distinguish which we set manually
* versus which were defaults... assuming that the defaults list is being
* maintained properly, of course.
* <P>
* Also luckily, The NameNodeMaps are maintained as
* sorted lists. This should keep the cost of convolving the two lists
* managable... not wonderful, but at least more like 2N than N**2..
* <P>
* Finally, to avoid doing the convolution except when there are actually
* changes to be absorbed, I've made the Map aware of whether or not
* its defaults Map has changed. This is not 110% reliable, but it should
* work under normal circumstances, especially since the DTD is usually
* relatively static information.
* <P>
* Note: This is NON-DOM implementation, though used to support behavior
* that the DOM requires.
*/
/** COMMENTED OUT!!!!!!! ********
Doing this dynamically is a killer, since editing the DTD isn't even
supported this is commented out at least for now. In the long run it seems
better to update the document on user's demand after the DTD has been
changed rather than doing this anyway.
protected void reconcileDefaults() {
if (DEBUG) System.out.println("reconcileDefaults:");
if (defaults != null && changed()) {
int n = 0;
int d = 0;
int nsize = (nodes != null) ? nodes.size() : 0;
int dsize = defaults.nodes.size();
AttrImpl nnode = (nsize == 0) ? null : (AttrImpl) nodes.elementAt(0);
AttrImpl dnode = (dsize == 0) ? null : (AttrImpl) defaults.nodes.elementAt(0);
while (n < nsize && d < dsize) {
nnode = (AttrImpl) nodes.elementAt(n);
dnode = (AttrImpl) defaults.nodes.elementAt(d);
if (DEBUG) {
System.out.println("\n\nnnode="+nnode.getNodeName());
System.out.println("dnode="+dnode.getNodeName());
}
int test = nnode.getNodeName().compareTo(dnode.getNodeName());
//REVIST: EACH CONDITION - HOW IT RESPONDS TO DUPLICATE KEYS!
// Same name and a default -- make sure same value
if (test == 0) {
if (!nnode.getSpecified()) {
if (DEBUG) System.out.println("reconcile (test==0, specified = false): clone default");
NodeImpl clone = (NodeImpl)dnode.cloneNode(true);
clone.ownerNode = ownerNode;
clone.isOwned(true);
nodes.setElementAt(clone, n);
// Advance over both, since names in sync
++n;
++d;
}
else { //REVIST: if same name, but specified, simply increment over it.
if (DEBUG)
System.out.println("reconcile (test==0, specified=true): just increment");
++n;
++d;
}
}
// Different name, new default in table; add it
else if (test > 0) {
if (DEBUG) System.out.println("reconcile (test>0): insert new default");
NodeImpl clone = (NodeImpl)dnode.cloneNode(true);
clone.ownerNode = ownerNode;
clone.isOwned(true);
nodes.insertElementAt(clone, n);
// Now in sync, so advance over both
++n;
++d;
}
// Different name, old default here; remove it.
else if (!nnode.getSpecified()) {
if (DEBUG) System.out.println("reconcile(!specified): remove old default:"
+nnode.getNodeName());
// NOTE: We don't need to null out the parent
// because this is a node that we're
// throwing away (not returning). -Ac
// REVISIT: [Q] Should we null it out anyway? -Ac
nodes.removeElementAt(n);
// n didn't advance but represents a different element
}
// Different name, specified; accept it
else {
if (DEBUG) System.out.println("reconcile: Different name else accept it.");
++n;
}
}
// If we ran out of local before default, pick up defaults
if (d < dsize) {
if (nodes == null) nodes = new Vector();
while (d < dsize) {
dnode = (AttrImpl)defaults.nodes.elementAt(d++);
NodeImpl clone = (NodeImpl)dnode.cloneNode(true);
clone.ownerNode = ownerNode;
clone.isOwned(true);
if (DEBUG) System.out.println("reconcile: adding"+clone);
nodes.addElement(clone);
}
}
changed(false);
}
} // reconcileDefaults()
**/
} // class AttributeMap