blob: 4e981df91e0fd7967ee6878a4e718491f4d50cae [file] [log] [blame]
/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.dom;
import org.apache.batik.dom.util.DOMUtilities;
import org.apache.batik.dom.util.HashTable;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.DocumentEvent;
import org.w3c.dom.events.MutationEvent;
/**
* This class implements the {@link org.w3c.dom.Element} interface.
*
* @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
* @version $Id$
*/
public abstract class AbstractElement
extends AbstractParentChildNode
implements Element {
/**
* The attributes of this element.
*/
protected NamedNodeMap attributes;
/**
* Creates a new AbstractElement object.
*/
protected AbstractElement() {
}
/**
* Creates a new AbstractElement object.
* @param name The element name for validation purposes.
* @param owner The owner document.
* @exception DOMException
* INVALID_CHARACTER_ERR: if name contains invalid characters,
*/
protected AbstractElement(String name, AbstractDocument owner) {
ownerDocument = owner;
if (!DOMUtilities.isValidName(name)) {
throw createDOMException(DOMException.INVALID_CHARACTER_ERR,
"xml.name",
new Object[] { name });
}
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Node#getNodeType()}.
* @return {@link org.w3c.dom.Node#ELEMENT_NODE}
*/
public short getNodeType() {
return ELEMENT_NODE;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Node#hasAttributes()}.
* @return true.
*/
public boolean hasAttributes() {
return true;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Node#getAttributes()}.
* @return {@link #attributes}.
*/
public NamedNodeMap getAttributes() {
return (attributes == null)
? attributes = createAttributes()
: attributes;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Element#getTagName()}.
* @return {@link #getNodeName()}.
*/
public String getTagName() {
return getNodeName();
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Element#hasAttribute(String)}.
*/
public boolean hasAttribute(String name) {
return attributes != null && attributes.getNamedItem(name) != null;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Element#getAttribute(String)}.
*/
public String getAttribute(String name) {
if (attributes == null) {
return "";
}
Attr attr = (Attr)attributes.getNamedItem(name);
return (attr == null) ? "" : attr.getValue();
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#setAttribute(String,String)}.
*/
public void setAttribute(String name, String value) throws DOMException {
if (attributes == null) {
attributes = createAttributes();
}
Attr attr = getOwnerDocument().createAttribute(name);
attr.setValue(value);
attributes.setNamedItem(attr);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#removeAttribute(String)}.
*/
public void removeAttribute(String name) throws DOMException {
if (attributes == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { name });
}
attributes.removeNamedItem(name);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#getAttributeNode(String)}.
*/
public Attr getAttributeNode(String name) {
if (attributes == null) {
return null;
}
return (Attr)attributes.getNamedItem(name);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#setAttributeNode(Attr)}.
*/
public Attr setAttributeNode(Attr newAttr) throws DOMException {
if (newAttr == null) {
return null;
}
if (attributes == null) {
attributes = createAttributes();
}
return (Attr)attributes.setNamedItem(newAttr);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#removeAttributeNode(Attr)}.
*/
public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
if (oldAttr == null) {
return null;
}
if (attributes == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { oldAttr.getName() });
}
return (Attr)attributes.removeNamedItem(oldAttr.getNodeName());
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#getElementsByTagName(String)}.
*/
public NodeList getElementsByTagName(String name) {
Node n = getFirstChild();
if (n == null || name == null) {
return EMPTY_NODE_LIST;
}
Nodes result = new Nodes();
getElementsByTagName(this, name, result);
return result;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Node#normalize()}.
*/
public void normalize() {
super.normalize();
if (attributes != null) {
NamedNodeMap map = getAttributes();
for (int i = map.getLength() - 1; i >= 0; i--) {
map.item(i).normalize();
}
}
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#hasAttributeNS(String,String)}.
*/
public boolean hasAttributeNS(String namespaceURI, String localName) {
return attributes != null &&
attributes.getNamedItemNS(namespaceURI, localName) != null;
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#getAttributeNS(String,String)}.
*/
public String getAttributeNS(String namespaceURI, String localName) {
if (attributes == null) {
return "";
}
Attr attr = (Attr)attributes.getNamedItemNS(namespaceURI, localName);
return (attr == null) ? "" : attr.getValue();
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#setAttributeNS(String,String,String)}.
*/
public void setAttributeNS(String namespaceURI,
String qualifiedName,
String value) throws DOMException {
if (attributes == null) {
attributes = createAttributes();
}
Attr attr = getOwnerDocument().createAttributeNS(namespaceURI,
qualifiedName);
attr.setValue(value);
attributes.setNamedItemNS(attr);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#removeAttributeNS(String,String)}.
*/
public void removeAttributeNS(String namespaceURI,
String localName) throws DOMException {
if (attributes == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { localName });
}
attributes.removeNamedItemNS(namespaceURI, localName);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#getAttributeNodeNS(String,String)}.
*/
public Attr getAttributeNodeNS(String namespaceURI,
String localName) {
if (attributes == null) {
return null;
}
return (Attr)attributes.getNamedItemNS(namespaceURI, localName);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#setAttributeNodeNS(Attr)}.
*/
public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
if (newAttr == null) {
return null;
}
if (attributes == null) {
attributes = createAttributes();
}
return (Attr)attributes.setNamedItemNS(newAttr);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#getElementsByTagNameNS(String,String)}.
*/
public NodeList getElementsByTagNameNS(String namespaceURI,
String localName) {
Node n = getFirstChild();
if (n == null || localName == null) {
return EMPTY_NODE_LIST;
}
Nodes result = new Nodes();
getElementsByTagNameNS(this, namespaceURI, localName, result);
return result;
}
/**
* Creates the attribute list.
*/
protected NamedNodeMap createAttributes() {
return new NamedNodeHashMap();
}
/**
* An auxiliary method of getElementsByTagName.
*/
protected static void getElementsByTagName(Node node, String name,
Nodes list) {
if (node.getNodeType() == ELEMENT_NODE) {
if (name.equals("*") || name.equals(node.getNodeName())) {
list.append(node);
}
}
for (Node n = node.getFirstChild();
n != null;
n = n.getNextSibling()) {
getElementsByTagName(n, name, list);
}
}
/**
* An auxiliary method for getElementsByTagNameNS.
*/
protected static void getElementsByTagNameNS(Node node,
String ns,
String name,
Nodes list) {
if (node.getNodeType() == ELEMENT_NODE) {
if (stringMatches(ns, node.getNamespaceURI()) &&
(name.equals("*") || name.equals(node.getLocalName()))) {
list.append(node);
}
}
for (Node n = node.getFirstChild();
n != null;
n = n.getNextSibling()) {
getElementsByTagNameNS(n, ns, name, list);
}
}
/**
* String matching for getElementsByTagNameNS function.
*/
private static boolean stringMatches(String s1, String s2) {
if (s1 == null && s2 == null) {
return true;
}
if (s1 == null || s2 == null) {
return false;
}
if (s1.equals("*")) {
return true;
}
return s1.equals(s2);
}
/**
* Exports this node to the given document.
* @param n The clone node.
* @param d The destination document.
*/
protected Node export(Node n, AbstractDocument d) {
super.export(n, d);
AbstractElement ae = (AbstractElement)n;
if (attributes != null) {
NamedNodeMap map = attributes;
for (int i = map.getLength() - 1; i >= 0; i--) {
AbstractAttr aa = (AbstractAttr)map.item(i);
if (aa.getSpecified()) {
Attr attr = (Attr)aa.deepExport(aa.cloneNode(false), d);
if (aa instanceof AbstractAttrNS) {
ae.setAttributeNodeNS(attr);
} else {
ae.setAttributeNode(attr);
}
}
}
}
return n;
}
/**
* Deeply exports this node to the given document.
* @param n The clone node.
* @param d The destination document.
*/
protected Node deepExport(Node n, AbstractDocument d) {
super.deepExport(n, d);
AbstractElement ae = (AbstractElement)n;
if (attributes != null) {
NamedNodeMap map = attributes;
for (int i = map.getLength() - 1; i >= 0; i--) {
AbstractAttr aa = (AbstractAttr)map.item(i);
if (aa.getSpecified()) {
Attr attr = (Attr)aa.deepExport(aa.cloneNode(false), d);
if (aa instanceof AbstractAttrNS) {
ae.setAttributeNodeNS(attr);
} else {
ae.setAttributeNode(attr);
}
}
}
}
return n;
}
/**
* Copy the fields of the current node into the given node.
* @param n a node of the type of this.
*/
protected Node copyInto(Node n) {
super.copyInto(n);
AbstractElement ae = (AbstractElement)n;
if (attributes != null) {
NamedNodeMap map = attributes;
for (int i = map.getLength() - 1; i >= 0; i--) {
AbstractAttr aa = (AbstractAttr)map.item(i).cloneNode(false);
if (aa instanceof AbstractAttrNS) {
ae.setAttributeNodeNS(aa);
} else {
ae.setAttributeNode(aa);
}
}
}
return n;
}
/**
* Deeply copy the fields of the current node into the given node.
* @param n a node of the type of this.
*/
protected Node deepCopyInto(Node n) {
super.deepCopyInto(n);
AbstractElement ae = (AbstractElement)n;
if (attributes != null) {
NamedNodeMap map = attributes;
for (int i = map.getLength() - 1; i >= 0; i--) {
AbstractAttr aa = (AbstractAttr)map.item(i).cloneNode(true);
if (aa instanceof AbstractAttrNS) {
ae.setAttributeNodeNS(aa);
} else {
ae.setAttributeNode(aa);
}
}
}
return n;
}
/**
* Checks the validity of a node to be inserted.
* @param n The node to be inserted.
*/
protected void checkChildType(Node n) {
switch (n.getNodeType()) {
case ELEMENT_NODE:
case PROCESSING_INSTRUCTION_NODE:
case COMMENT_NODE:
case TEXT_NODE:
case CDATA_SECTION_NODE:
case ENTITY_REFERENCE_NODE:
case DOCUMENT_FRAGMENT_NODE:
break;
default:
throw createDOMException
(DOMException.HIERARCHY_REQUEST_ERR,
"child.type",
new Object[] { new Integer(getNodeType()),
getNodeName(),
new Integer(n.getNodeType()),
n.getNodeName() });
}
}
/**
* Fires a DOMAttrModified event.
* @param name The attribute name.
* @param oldv The old value of the attribute.
* @param newv The new value of the attribute.
*/
protected void fireDOMAttrModifiedEvent(String name, String oldv,
String newv) {
AbstractDocument doc = getCurrentDocument();
if (doc.getEventsEnabled() && !oldv.equals(newv)) {
DocumentEvent de = (DocumentEvent)doc;
MutationEvent ev = (MutationEvent)de.createEvent("MutationEvents");
ev.initMutationEvent("DOMAttrModified",
true, // canBubbleArg
false, // cancelableArg
null, // relatedNodeArg
oldv, // prevValueArg
newv, // newValueArg
name); // attrNameArg
dispatchEvent(ev);
}
}
/**
* An implementation of the {@link org.w3c.dom.NamedNodeMap}.
*/
public class NamedNodeHashMap implements NamedNodeMap {
/**
* The place where the nodes in the anonymous namespace are stored.
*/
protected HashTable table = new HashTable();
/**
* The place where the the nodes that use namespaces are stored.
*/
protected HashTable tableNS;
/**
* The current namespace URI.
*/
public String namespaceURI;
/**
* Creates a new NamedNodeHashMap object.
*/
public NamedNodeHashMap() {
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#getNamedItem(String)}.
*/
public Node getNamedItem(String name) {
if (name == null) {
return null;
}
return (Node)table.get(name);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#setNamedItem(Node)}.
*/
public Node setNamedItem(Node arg) throws DOMException {
if (arg == null) {
return null;
}
checkNode(arg);
return setNamedItem(arg.getNodeName(), arg);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#removeNamedItem(String)}.
*/
public Node removeNamedItem(String name) throws DOMException {
if (isReadonly()) {
throw createDOMException
(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"readonly.node.map",
new Object[] {});
}
if (name == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { "" });
}
AbstractAttr n = (AbstractAttr)table.remove(name);
if (n == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { name });
}
n.setOwnerElement(null);
// Mutation event
fireDOMAttrModifiedEvent(name, n.getNodeValue(), "");
return n;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.NamedNodeMap#item(int)}.
*/
public Node item(int index) {
int i = table.size();
if (index < i) {
return (Node)table.item(index);
}
index -= i;
if (tableNS != null) {
for (int k = 0; k < tableNS.size(); k++) {
NamedNodeHashMap hm = (NamedNodeHashMap)tableNS.item(k);
i = hm.getLength();
if (index < i) {
return (Node)hm.item(index);
}
index -= i;
}
}
return null;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.NamedNodeMap#getLength()}.
*/
public int getLength() {
int result = table.size();
if (tableNS != null) {
for (int i = 0; i < tableNS.size(); i++) {
NamedNodeHashMap hm = (NamedNodeHashMap)tableNS.item(i);
result += hm.getLength();
}
}
return result;
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#getNamedItemNS(String,String)}.
*/
public Node getNamedItemNS(String namespaceURI, String localName) {
if (namespaceURI == null) {
return getNamedItem(localName);
}
if (tableNS == null) {
return null;
}
NamedNodeHashMap attr;
attr = (NamedNodeHashMap)tableNS.get(namespaceURI);
if (attr == null) {
return null;
}
return attr.getNamedItem(localName);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#setNamedItemNS(Node)}.
*/
public Node setNamedItemNS(Node arg) throws DOMException {
if (arg == null) {
return null;
}
String nsURI = arg.getNamespaceURI();
if (nsURI == null) {
return setNamedItem(arg);
}
checkNode(arg);
if (tableNS == null) {
tableNS = new HashTable();
}
NamedNodeHashMap attr = (NamedNodeHashMap)tableNS.get(nsURI);
if (attr == null) {
tableNS.put(nsURI, attr = new NamedNodeHashMap());
attr.namespaceURI = nsURI;
}
return attr.setNamedItem(arg.getLocalName(), arg);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#removeNamedItemNS(String,String)}.
*/
public Node removeNamedItemNS(String namespaceURI, String localName)
throws DOMException {
if (namespaceURI == null) {
return removeNamedItem(localName);
}
if (isReadonly()) {
throw createDOMException
(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"readonly.node.map",
new Object[] {});
}
if (localName == null || tableNS == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { localName });
}
NamedNodeHashMap attr;
attr = (NamedNodeHashMap)tableNS.get(namespaceURI);
if (attr == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { localName });
}
return attr.removeNamedItem(localName);
}
/**
* Adds a node to the map.
*/
public Node setNamedItem(String name, Node arg) throws DOMException {
((AbstractAttr)arg).setOwnerElement(AbstractElement.this);
AbstractAttr result = (AbstractAttr)table.put(name, arg);
if (result != null) {
result.setOwnerElement(null);
fireDOMAttrModifiedEvent(name,
result.getNodeValue(),
arg.getNodeValue());
} else {
fireDOMAttrModifiedEvent(name,
"",
arg.getNodeValue());
}
return result;
}
/**
* Checks the validity of a node to add.
*/
protected void checkNode(Node arg) {
if (isReadonly()) {
throw createDOMException
(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"readonly.node.map",
new Object[] {});
}
if (getOwnerDocument() != arg.getOwnerDocument()) {
throw createDOMException(DOMException.WRONG_DOCUMENT_ERR,
"node.from.wrong.document",
new Object[] { new Integer
(arg.getNodeType()),
arg.getNodeName() });
}
if (arg.getNodeType() == ATTRIBUTE_NODE &&
((Attr)arg).getOwnerElement() != null) {
throw createDOMException(DOMException.WRONG_DOCUMENT_ERR,
"inuse.attribute",
new Object[] { arg.getNodeName() });
}
}
}
}