/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*
* $Id: ElementImpl.java 571948 2007-09-02 10:51:37Z vgritsenko $
*/
package org.apache.xindice.xml.dom;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.data.Key;
import org.apache.xindice.util.ByteArrayInput;
import org.apache.xindice.xml.NodeSource;
import org.apache.xindice.xml.SymbolTable;
import org.apache.xindice.xml.XMLCompressedInput;
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.TypeInfo;
import org.w3c.dom.UserDataHandler;
import java.io.IOException;
import java.util.HashSet;
/**
* ElementImpl
*
* @version $Revision: 571948 $, $Date: 2007-09-02 06:51:37 -0400 (Sun, 02 Sep 2007) $
*/
public final class ElementImpl extends ContainerNodeImpl
implements Element {
private static final Log log = LogFactory.getLog(ElementImpl.class);
// private static final String SRC_NS = XMLNS_PREFIX + ":src";
// private static final String SRC_COL = "src:" + NodeSource.SOURCE_COL;
// private static final String SRC_KEY = "src:" + NodeSource.SOURCE_KEY;
private NamedNodeMapImpl attributes = new NamedNodeMapImpl(this);
private short symbolID = -1;
public ElementImpl() {
}
public ElementImpl(NodeImpl parent, byte[] data, int pos, int len) {
super(parent, data, pos, len);
DocumentImpl doc = (DocumentImpl) getOwnerDocument();
try {
loadAttributes(doc.getSymbols());
} catch (Exception e) {
if (log.isWarnEnabled()) {
log.warn("ignored exception", e);
}
}
}
public ElementImpl(NodeImpl parent, boolean dirty) {
super(parent, dirty);
}
public ElementImpl(NodeImpl parent, String nodeName) {
super(parent, true);
this.nodeName = nodeName;
}
protected boolean isNodeTypeValid(short type) {
return type == Node.ELEMENT_NODE
|| type == Node.COMMENT_NODE
|| type == Node.TEXT_NODE
|| type == Node.CDATA_SECTION_NODE
|| type == Node.ENTITY_REFERENCE_NODE;
}
protected void checkLoaded() {
if (loaded) {
return;
}
loaded = true;
try {
if (data != null) {
DocumentImpl doc = (DocumentImpl) getOwnerDocument();
SymbolTable st = doc.getSymbols();
ByteArrayInput bis = new ByteArrayInput(data, pos, len);
XMLCompressedInput in = new XMLCompressedInput(bis, st);
in.readSignature(); // Skip The Signature
in.readContentSize(); // Skip The Content Size
symbolID = in.readShort();
SymbolTable.SymbolInfo si = st.getSymbolInfo(symbolID);
nodeName = si.getQName();
nsURI = si.getNamespaceURI();
loadChildren(st);
}
} catch (IOException e) {
if (log.isWarnEnabled()) {
log.warn("ignored exception", e);
}
}
}
public short getSymbolID() {
return symbolID;
}
/**
* Add "src" and "col" attributes in {@link NodeSource#SOURCE_NS} namespace.
*/
public void expandSource() {
NodeSource src = getSource();
if (src != null) {
final String prefix = sourcePrefix("src", NodeSource.SOURCE_NS);
setAttribute(XMLNS_PREFIX + ":" + prefix, NodeSource.SOURCE_NS);
setAttribute(prefix + ":" + NodeSource.SOURCE_COL, src.getCollection().getCanonicalName());
Key k = src.getKey();
if (k != null) {
setAttribute(prefix + ":" + NodeSource.SOURCE_KEY, k.toString());
}
}
}
/**
* Choose unique prefix for a namespace.
* Reuse existing prefix if namespace is already defined.
*/
private String sourcePrefix(final String candidatePrefix, final String nsuri) {
Element element = this;
HashSet prefixes = new HashSet();
while (element != null) {
NamedNodeMap nm = element.getAttributes();
for (int i = 0; i < nm.getLength(); i++) {
final Attr a = (Attr) nm.item(i);
final String name = a.getNodeName();
if (name.startsWith("xmlns:")) {
final String prefix = name.substring(6);
if (nsuri.equals(a.getValue())) {
return prefix;
}
prefixes.add(prefix);
}
}
element = element.getParentNode().getNodeType() == ELEMENT_NODE ? (Element) element.getParentNode() : null;
}
String result = candidatePrefix;
while (prefixes.contains(result)) {
result = candidatePrefix + System.currentTimeMillis();
}
return result;
}
protected void loadAttributes(SymbolTable st) throws IOException {
ByteArrayInput bis = new ByteArrayInput(data, pos, len);
XMLCompressedInput in = new XMLCompressedInput(bis, st);
in.readSignature();
in.readContentSize();
in.readShort(); // Some "elemSymbol" - symbol ID?
int attrCount = in.readAttributeCount();
for (int i = 0; i < attrCount; i++) {
short symbol = in.readShort();
short strLen = in.readShort();
byte[] b = new byte[strLen];
in.read(b);
SymbolTable.SymbolInfo si = st.getSymbolInfo(symbol);
String name = si.getQName();
String nsURI = si.getNamespaceURI();
AttrImpl attr = new AttrImpl(this, name, nsURI, symbol, new String(b, "UTF-8"));
attributes.setNamedItem(attr);
}
}
public short getNodeType() {
return Node.ELEMENT_NODE;
}
/**
* A <code>NamedNodeMap</code> containing the attributes of this node (if it
* is an <code>Element</code>) or <code>null</code> otherwise.
*/
public NamedNodeMap getAttributes() {
return attributes;
}
public boolean hasAttributes() {
return attributes.size() > 0;
}
public boolean hasAttribute(String name) {
return attributes.getNamedItem(name) != null;
}
/**
* Removes an attribute by name. If the removed attribute has a
* default value it is immediately replaced. If the named
* attribute does not exist, this method has no effect.
*
* @param name The name of the attribute to remove.
* @exception DOMException
* NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
**/
public void removeAttribute(String name) throws DOMException {
checkReadOnly();
AttrImpl attr = (AttrImpl) attributes.removeNamedItem(name);
if (attr != null) {
attr.setParentNode((NodeImpl) this.getOwnerDocument());
}
setDirty();
}
/**
* The name of the element. For example, in: <elementExample
* id="demo"> ... </elementExample> , <code>tagName</code> has
* the value <code>"elementExample"</code>. Note that this is
* case-preserving in XML, as are all of the operations of the DOM. The
* HTML DOM returns the <code>tagName</code> of an HTML element in the
* canonical uppercase form, regardless of the case in the source HTML
* document.
*/
public String getTagName() {
return getNodeName();
}
/**
* Retrieves an attribute value by name.
* @param name The name of the attribute to retrieve.
* @return The <code>Attr</code> value as a string, or the empty string if
* that attribute does not have a specified or default value.
*/
public String getAttribute(String name) {
Attr attr = (Attr) attributes.getNamedItem(name);
return attr != null ? attr.getValue() : "";
}
/**
* Adds a new attribute. If an attribute with that name is already present
* in the element, it is replaced by the new one.
* @param newAttr The <code>Attr</code> node to add to the attribute list.
* @return If the <code>newAttr</code> attribute replaces an existing
* attribute with the same name, the previously existing
* <code>Attr</code> node is returned, otherwise <code>null</code> is
* returned.
* @exception DOMException
* WRONG_DOCUMENT_ERR: Raised if <code>newAttr</code> was created from a
* different document than the one that created the element.
* <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br>INUSE_ATTRIBUTE_ERR: Raised if <code>newAttr</code> is already an
* attribute of another <code>Element</code> object. The DOM user must
* explicitly clone <code>Attr</code> nodes to re-use them in other
* elements.
*/
public Attr setAttributeNode(Attr newAttr) throws DOMException {
checkReadOnly();
Attr oldAttr = (Attr) attributes.getNamedItem(newAttr.getName());
DocumentImpl doc = (DocumentImpl) getOwnerDocument();
if (newAttr.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
throw EX_INUSE_ATTRIBUTE;
}
if (doc != newAttr.getOwnerDocument()) {
throw EX_WRONG_DOCUMENT;
}
((AttrImpl) newAttr).setParentNode(this);
attributes.setNamedItem(newAttr);
setDirty();
return oldAttr;
}
/**
* Adds a new attribute. If an attribute with that name is already present
* in the element, its value is changed to be that of the value parameter.
* This value is a simple string, it is not parsed as it is being set. So
* any markup (such as syntax to be recognized as an entity reference) is
* treated as literal text, and needs to be appropriately escaped by the
* implementation when it is written out. In order to assign an attribute
* value that contains entity references, the user must create an
* <code>Attr</code> node plus any <code>Text</code> and
* <code>EntityReference</code> nodes, build the appropriate subtree, and
* use <code>setAttributeNode</code> to assign it as the value of an
* attribute.
* @param name The name of the attribute to create or alter.
* @param value Value to set in string form.
* @exception DOMException
* INVALID_CHARACTER_ERR: Raised if the specified name contains an
* invalid character.
* <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
*/
public void setAttribute(String name, String value) throws DOMException {
Attr attr = getOwnerDocument().createAttribute(name);
attr.setValue(value);
setAttributeNode(attr);
}
/**
* Retrieves an <code>Attr</code> node by name.
* @param name The name of the attribute to retrieve.
* @return The <code>Attr</code> node with the specified attribute name or
* <code>null</code> if there is no such attribute.
*/
public Attr getAttributeNode(String name) {
return (Attr) attributes.getNamedItem(name);
}
/**
* Removes the specified attribute.
* @param oldAttr The <code>Attr</code> node to remove from the attribute
* list. If the removed <code>Attr</code> has a default value it is
* immediately replaced.
* @return The <code>Attr</code> node that was removed.
* @exception DOMException
* NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br>NOT_FOUND_ERR: Raised if <code>oldAttr</code> is not an attribute
* of the element.
**/
public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
checkReadOnly();
if (oldAttr.getParentNode() != this) {
throw EX_NOT_FOUND;
}
attributes.remove(oldAttr.getName());
((AttrImpl) oldAttr).setParentNode((NodeImpl) this.getOwnerDocument());
setDirty();
return oldAttr;
}
/**
* Retrieves an attribute value by local name and namespace URI.
* HTML-only DOM implementations do not need to implement this method.
* @param namespaceURI The namespace URI of the attribute to retrieve.
* @param localName The local name of the attribute to retrieve.
* @return The <code>Attr</code> value as a string, or the empty
* string if that attribute does not have a specified or default
* value.
* @since DOM Level 2
*/
public String getAttributeNS(String namespaceURI, String localName) {
Attr attr = (Attr) attributes.getNamedItemNS(namespaceURI, localName);
return attr != null ? attr.getValue() : "";
}
/**
* Adds a new attribute. If an attribute with the same local name and
* namespace URI is already present on the element, its prefix is changed
* to be the prefix part of the <code>qualifiedName</code> , and its
* value is changed to be the <code>value</code> parameter. This value is
* a simple string; it is not parsed as it is being set. So any markup
* (such as syntax to be recognized as an entity reference) is treated as
* literal text, and needs to be appropriately escaped by the
* implementation when it is written out. In order to assign an attribute
* value that contains entity references, the user must create an
* <code>Attr</code> node plus any <code>Text</code> and
* <code>EntityReference</code> nodes, build the appropriate subtree, and
* use <code>setAttributeNodeNS</code> or <code>setAttributeNode</code> to
* assign it as the value of an attribute.
* <br> HTML-only DOM implementations do not need to implement this method.
* @param namespaceURI The namespace URI of the attribute to create or
* alter.
* @param qualifiedName The qualified name of the attribute to create or
* alter.
* @param value The value to set in string form.
* @exception DOMException
* INVALID_CHARACTER_ERR: Raised if the specified qualified name
* contains an illegal character.
* <br> NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br> NAMESPACE_ERR: Raised if the <code>qualifiedName</code> is
* malformed, if the <code>qualifiedName</code> has a prefix and the
* <code>namespaceURI</code> is <code>null</code> or an empty string,
* if the <code>qualifiedName</code> has a prefix that is "xml" and the
* <code>namespaceURI</code> is different from
* "http://www.w3.org/XML/1998/namespace", if the
* <code>qualifiedName</code> has a prefix that is "xmlns" and the
* <code>namespaceURI</code> is different from
* "http://www.w3.org/2000/xmlns/", or if the <code>qualifiedName</code>
* is "xmlns" and the <code>namespaceURI</code> is different from
* "http://www.w3.org/2000/xmlns/".
* @since DOM Level 2
*/
public void setAttributeNS(String namespaceURI, String qualifiedName, String value) {
Attr attr = getOwnerDocument().createAttributeNS(namespaceURI, qualifiedName);
attr.setValue(value);
setAttributeNodeNS(attr);
}
/**
* Removes an attribute by local name and namespace URI. If the
* removed attribute has a default value it is immediately
* replaced. The replacing attribute has the same namespace URI
* and local name, as well as the original prefix.
*
* <br> HTML-only DOM implementations do not need to implement this
* method.
*
* @param namespaceURI The namespace URI of the attribute to remove.
* @param localName The local name of the attribute to remove.
* @exception DOMException
* NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* @since DOM Level 2
**/
public void removeAttributeNS(String namespaceURI, String localName) {
checkReadOnly();
AttrImpl attr = (AttrImpl) attributes.removeNamedItemNS(namespaceURI, localName);
if (attr != null) {
attr.setParentNode((NodeImpl) this.getOwnerDocument());
}
setDirty();
}
/**
* Retrieves an <code>Attr</code> node by local name and namespace URI.
* HTML-only DOM implementations do not need to implement this method.
* @param namespaceURI The namespace URI of the attribute to retrieve.
* @param localName The local name of the attribute to retrieve.
* @return The <code>Attr</code> node with the specified attribute local
* name and namespace URI or <code>null</code> if there is no such
* attribute.
* @since DOM Level 2
*/
public Attr getAttributeNodeNS(String namespaceURI, String localName) {
return (Attr) attributes.getNamedItemNS(namespaceURI, localName);
}
/**
* Adds a new attribute. If an attribute with that local name and that
* namespace URI is already present in the element, it is replaced by the
* new one.
* <br> HTML-only DOM implementations do not need to implement this method.
* @param newAttr The <code>Attr</code> node to add to the attribute list.
* @return If the <code>newAttr</code> attribute replaces an existing
* attribute with the same local name and namespace URI , the
* replaced <code>Attr</code> node is returned, otherwise
* <code>null</code> is returned.
* @exception DOMException
* WRONG_DOCUMENT_ERR: Raised if <code>newAttr</code> was created from
* a different document than the one that created the element.
* <br> NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
* <br> INUSE_ATTRIBUTE_ERR: Raised if <code>newAttr</code> is already
* an attribute of another <code>Element</code> object. The DOM user
* must explicitly clone <code>Attr</code> nodes to re-use them in
* other elements.
* @since DOM Level 2
*/
public Attr setAttributeNodeNS(Attr newAttr) {
return setAttributeNode(newAttr);
}
/**
* @since DOM Level 2
*/
public boolean hasAttributeNS(String namespaceURI, String localName) {
return attributes.getNamedItemNS(namespaceURI, localName) != null;
}
//
// DOM Level 3 Implementation
//
/**
* @since DOM Level 3
*/
public TypeInfo getSchemaTypeInfo() {
return null;
}
/**
* @since DOM Level 3
*/
public void setIdAttribute(String name, boolean isId) throws DOMException {
}
/**
* @since DOM Level 3
*/
public void setIdAttributeNS(String namespaceURI, String localName, boolean isId) throws DOMException {
}
/**
* @since DOM Level 3
*/
public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
}
/**
* @since DOM Level 3
*/
public boolean isEqualNode(Node other) {
if (!super.isEqualNode(other)) {
return false;
}
// Attributes of the nodes have to be the same, but can have different order
if (!hasAttributes() && !other.hasAttributes()) {
return true;
}
if (!hasAttributes() || !other.hasAttributes()) {
return false;
}
NamedNodeMap attrMap = getAttributes();
NamedNodeMap otherAttrMap = other.getAttributes();
if (attrMap.getLength() != otherAttrMap.getLength()) {
return false;
}
for (int i = 0; i < attrMap.getLength(); i++) {
String name = attrMap.item(i).getNodeName();
Node attr = otherAttrMap.getNamedItem(name);
if (attr == null || !attrMap.item(i).getNodeValue().equals(attr.getNodeValue())) {
return false;
}
}
return true;
}
Node renameNode(String namespaceURI, String qualifiedName, String prefix) throws DOMException {
checkLoaded();
checkReadOnly();
nodeName = qualifiedName;
if (prefix != null) {
setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:" + prefix, namespaceURI);
nsURI = namespaceURI;
} else {
removeAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:" + nsURI);
nsURI = null;
}
invokeHandlers(UserDataHandler.NODE_RENAMED, this, null);
return this;
}
}