/* * Copyright (C) 2007 The Android Open Source Project * * 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 org.apache.harmony.xml.dom; import org.apache.xml.serializer.utils.SystemIDResolver; import org.apache.xml.utils.URI; import org.w3c.dom.Attr; import org.w3c.dom.CharacterData; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.TypeInfo; import org.w3c.dom.UserDataHandler; import javax.xml.transform.TransformerException; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * A straightforward implementation of the corresponding W3C DOM node. * *
Some fields have package visibility so other classes can access them while * maintaining the DOM structure. * *
This class represents a Node that has neither a parent nor children. * Subclasses may have either. * *
Some code was adapted from Apache Xerces.
*/
public abstract class NodeImpl implements Node {
private static final NodeList EMPTY_LIST = new NodeListImpl();
static final TypeInfo NULL_TYPE_INFO = new TypeInfo() {
public String getTypeName() {
return null;
}
public String getTypeNamespace() {
return null;
}
public boolean isDerivedFrom(
String typeNamespaceArg, String typeNameArg, int derivationMethod) {
return false;
}
};
DocumentImpl document;
NodeImpl(DocumentImpl document) {
this.document = document;
}
public Node appendChild(Node newChild) throws DOMException {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
public final Node cloneNode(boolean deep) {
return document.cloneOrImportNode(UserDataHandler.NODE_CLONED, this, deep);
}
public NamedNodeMap getAttributes() {
return null;
}
public NodeList getChildNodes() {
return EMPTY_LIST;
}
public Node getFirstChild() {
return null;
}
public Node getLastChild() {
return null;
}
public String getLocalName() {
return null;
}
public String getNamespaceURI() {
return null;
}
public Node getNextSibling() {
return null;
}
public String getNodeName() {
return null;
}
public abstract short getNodeType();
public String getNodeValue() throws DOMException {
return null;
}
public final Document getOwnerDocument() {
return document == this ? null : document;
}
public Node getParentNode() {
return null;
}
public String getPrefix() {
return null;
}
public Node getPreviousSibling() {
return null;
}
public boolean hasAttributes() {
return false;
}
public boolean hasChildNodes() {
return false;
}
public Node insertBefore(Node newChild, Node refChild) throws DOMException {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
public boolean isSupported(String feature, String version) {
return DOMImplementationImpl.getInstance().hasFeature(feature, version);
}
public void normalize() {
}
public Node removeChild(Node oldChild) throws DOMException {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
public final void setNodeValue(String nodeValue) throws DOMException {
switch (getNodeType()) {
case CDATA_SECTION_NODE:
case COMMENT_NODE:
case TEXT_NODE:
((CharacterData) this).setData(nodeValue);
return;
case PROCESSING_INSTRUCTION_NODE:
((ProcessingInstruction) this).setData(nodeValue);
return;
case ATTRIBUTE_NODE:
((Attr) this).setValue(nodeValue);
return;
case ELEMENT_NODE:
case ENTITY_REFERENCE_NODE:
case ENTITY_NODE:
case DOCUMENT_NODE:
case DOCUMENT_TYPE_NODE:
case DOCUMENT_FRAGMENT_NODE:
case NOTATION_NODE:
return; // do nothing!
default:
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"Unsupported node type " + getNodeType());
}
}
public void setPrefix(String prefix) throws DOMException {
}
/**
* Validates the element or attribute namespace prefix on this node.
*
* @param namespaceAware whether this node is namespace aware
* @param namespaceURI this node's namespace URI
*/
static String validatePrefix(String prefix, boolean namespaceAware, String namespaceURI) {
if (!namespaceAware) {
throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
}
if (prefix != null) {
if (namespaceURI == null
|| !DocumentImpl.isXMLIdentifier(prefix)
|| "xml".equals(prefix) && !"http://www.w3.org/XML/1998/namespace".equals(namespaceURI)
|| "xmlns".equals(prefix) && !"http://www.w3.org/2000/xmlns/".equals(namespaceURI)) {
throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
}
}
return prefix;
}
/**
* Sets the element or attribute node to be namespace-aware and assign it
* the specified name and namespace URI.
*
* @param node an AttrImpl or ElementImpl node.
* @param namespaceURI this node's namespace URI. May be null.
* @param qualifiedName a possibly-prefixed name like "img" or "html:img".
*/
static void setNameNS(NodeImpl node, String namespaceURI, String qualifiedName) {
if (qualifiedName == null) {
throw new DOMException(DOMException.NAMESPACE_ERR, qualifiedName);
}
String prefix = null;
int p = qualifiedName.lastIndexOf(":");
if (p != -1) {
prefix = validatePrefix(qualifiedName.substring(0, p), true, namespaceURI);
qualifiedName = qualifiedName.substring(p + 1);
}
if (!DocumentImpl.isXMLIdentifier(qualifiedName)) {
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, qualifiedName);
}
switch (node.getNodeType()) {
case ATTRIBUTE_NODE:
if ("xmlns".equals(qualifiedName)
&& !"http://www.w3.org/2000/xmlns/".equals(namespaceURI)) {
throw new DOMException(DOMException.NAMESPACE_ERR, qualifiedName);
}
AttrImpl attr = (AttrImpl) node;
attr.namespaceAware = true;
attr.namespaceURI = namespaceURI;
attr.prefix = prefix;
attr.localName = qualifiedName;
break;
case ELEMENT_NODE:
ElementImpl element = (ElementImpl) node;
element.namespaceAware = true;
element.namespaceURI = namespaceURI;
element.prefix = prefix;
element.localName = qualifiedName;
break;
default:
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"Cannot rename nodes of type " + node.getNodeType());
}
}
/**
* Checks whether a required string matches an actual string. This utility
* method is used for comparing namespaces and such. It takes into account
* null arguments and the "*" special case.
*
* @param required The required string.
* @param actual The actual string.
* @return True if and only if the actual string matches the required one.
*/
private static boolean matchesName(String required, String actual, boolean wildcard) {
if (wildcard && "*".equals(required)) {
return true;
}
if (required == null) {
return (actual == null);
}
return required.equals(actual);
}
/**
* Checks whether this node's name matches a required name. It takes into
* account null arguments and the "*" special case.
*
* @param name The required name.
* @return True if and only if the actual name matches the required one.
*/
public boolean matchesName(String name, boolean wildcard) {
return matchesName(name, getNodeName(), wildcard);
}
/**
* Checks whether this node's namespace and local name match a required
* pair of namespace and local name. It takes into account null arguments
* and the "*" special case.
*
* @param namespaceURI The required namespace.
* @param localName The required local name.
* @return True if and only if the actual namespace and local name match
* the required pair of namespace and local name.
*/
public boolean matchesNameNS(String namespaceURI, String localName, boolean wildcard) {
return matchesName(namespaceURI, getNamespaceURI(), wildcard) && matchesName(localName, getLocalName(), wildcard);
}
public final String getBaseURI() {
switch (getNodeType()) {
case DOCUMENT_NODE:
return sanitizeUri(((Document) this).getDocumentURI());
case ELEMENT_NODE:
Element element = (Element) this;
String uri = element.getAttributeNS(
"http://www.w3.org/XML/1998/namespace", "base"); // or "xml:base"
// if this node has no base URI, return the parent's.
if (uri == null || uri.length() == 0) {
return getParentBaseUri();
}
// if this node's URI is absolute, return that
if (SystemIDResolver.isAbsoluteURI(uri)) {
return uri;
}
// this node has a relative URI. Try to resolve it against the
// parent, but if that doesn't work just give up and return null.
String parentUri = getParentBaseUri();
if (parentUri == null) {
return null;
}
try {
return SystemIDResolver.getAbsoluteURI(uri, parentUri);
} catch (TransformerException e) {
return null; // the spec requires that we swallow exceptions
}
case PROCESSING_INSTRUCTION_NODE:
return getParentBaseUri();
case NOTATION_NODE:
case ENTITY_NODE:
// When we support these node types, the parser should
// initialize a base URI field on these nodes.
return null;
case ENTITY_REFERENCE_NODE:
// TODO: get this value from the parser, falling back to the
// referenced entity's baseURI if that doesn't exist
return null;
case DOCUMENT_TYPE_NODE:
case DOCUMENT_FRAGMENT_NODE:
case ATTRIBUTE_NODE:
case TEXT_NODE:
case CDATA_SECTION_NODE:
case COMMENT_NODE:
return null;
default:
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"Unsupported node type " + getNodeType());
}
}
private String getParentBaseUri() {
Node parentNode = getParentNode();
return parentNode != null ? parentNode.getBaseURI() : null;
}
/**
* Returns the sanitized input if it is a URI, or {@code null} otherwise.
*/
private String sanitizeUri(String uri) {
if (uri == null || uri.length() == 0) {
return null;
}
try {
return new URI(uri).toString();
} catch (URI.MalformedURIException e) {
return null;
}
}
public short compareDocumentPosition(Node other)
throws DOMException {
throw new UnsupportedOperationException(); // TODO
}
public String getTextContent() throws DOMException {
return getNodeValue();
}
void getTextContent(StringBuilder buf) throws DOMException {
String content = getNodeValue();
if (content != null) {
buf.append(content);
}
}
public final void setTextContent(String textContent) throws DOMException {
switch (getNodeType()) {
case DOCUMENT_TYPE_NODE:
case DOCUMENT_NODE:
return; // do nothing!
case ELEMENT_NODE:
case ENTITY_NODE:
case ENTITY_REFERENCE_NODE:
case DOCUMENT_FRAGMENT_NODE:
// remove all existing children
Node child;
while ((child = getFirstChild()) != null) {
removeChild(child);
}
// create a text node to hold the given content
if (textContent != null && textContent.length() != 0) {
appendChild(document.createTextNode(textContent));
}
return;
case ATTRIBUTE_NODE:
case TEXT_NODE:
case CDATA_SECTION_NODE:
case PROCESSING_INSTRUCTION_NODE:
case COMMENT_NODE:
case NOTATION_NODE:
setNodeValue(textContent);
return;
default:
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"Unsupported node type " + getNodeType());
}
}
public boolean isSameNode(Node other) {
return this == other;
}
/**
* Returns the element whose namespace definitions apply to this node. Use
* this element when mapping prefixes to URIs and vice versa.
*/
private NodeImpl getNamespacingElement() {
switch (this.getNodeType()) {
case ELEMENT_NODE:
return this;
case DOCUMENT_NODE:
return (NodeImpl) ((Document) this).getDocumentElement();
case ENTITY_NODE:
case NOTATION_NODE:
case DOCUMENT_FRAGMENT_NODE:
case DOCUMENT_TYPE_NODE:
return null;
case ATTRIBUTE_NODE:
return (NodeImpl) ((Attr) this).getOwnerElement();
case TEXT_NODE:
case CDATA_SECTION_NODE:
case ENTITY_REFERENCE_NODE:
case PROCESSING_INSTRUCTION_NODE:
case COMMENT_NODE:
return getContainingElement();
default:
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
"Unsupported node type " + getNodeType());
}
}
/**
* Returns the nearest ancestor element that contains this node.
*/
private NodeImpl getContainingElement() {
for (Node p = getParentNode(); p != null; p = p.getParentNode()) {
if (p.getNodeType() == ELEMENT_NODE) {
return (NodeImpl) p;
}
}
return null;
}
public final String lookupPrefix(String namespaceURI) {
if (namespaceURI == null) {
return null;
}
// the XML specs define some prefixes (like "xml" and "xmlns") but this
// API is explicitly defined to ignore those.
NodeImpl target = getNamespacingElement();
for (NodeImpl node = target; node != null; node = node.getContainingElement()) {
// check this element's namespace first
if (namespaceURI.equals(node.getNamespaceURI())
&& target.isPrefixMappedToUri(node.getPrefix(), namespaceURI)) {
return node.getPrefix();
}
// search this element for an attribute of this form:
// xmlns:foo="http://namespaceURI"
if (!node.hasAttributes()) {
continue;
}
NamedNodeMap attributes = node.getAttributes();
for (int i = 0, length = attributes.getLength(); i < length; i++) {
Node attr = attributes.item(i);
if (!"http://www.w3.org/2000/xmlns/".equals(attr.getNamespaceURI())
|| !"xmlns".equals(attr.getPrefix())
|| !namespaceURI.equals(attr.getNodeValue())) {
continue;
}
if (target.isPrefixMappedToUri(attr.getLocalName(), namespaceURI)) {
return attr.getLocalName();
}
}
}
return null;
}
/**
* Returns true if the given prefix is mapped to the given URI on this
* element. Since child elements can redefine prefixes, this check is
* necessary: {@code
*