blob: 9cee352573f1266e41b465fea5f1008d0c4155d5 [file] [log] [blame]
/*
* 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.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import java.util.ArrayList;
import java.util.List;
/**
* Provides a straightforward implementation of the corresponding W3C DOM
* interface. The class is used internally only, thus only notable members that
* are not in the original interface are documented (the W3C docs are quite
* extensive).
*
* <p>Some of the fields may have package visibility, so other classes belonging
* to the DOM implementation can easily access them while maintaining the DOM
* tree structure.
*
* <p>This class represents a Node that has a parent Node as well as
* (potentially) a number of children.
*
* <p>Some code was adapted from Apache Xerces.
*/
public abstract class InnerNodeImpl extends LeafNodeImpl {
// Maintained by LeafNodeImpl and ElementImpl.
List<LeafNodeImpl> children = new ArrayList<LeafNodeImpl>();
public InnerNodeImpl(DocumentImpl document) {
super(document);
}
public Node appendChild(Node newChild) throws DOMException {
return insertChildAt(newChild, children.size());
}
public NodeList getChildNodes() {
NodeListImpl list = new NodeListImpl();
for (NodeImpl node : children) {
list.add(node);
}
return list;
}
public Node getFirstChild() {
return (!children.isEmpty() ? children.get(0) : null);
}
public Node getLastChild() {
return (!children.isEmpty() ? children.get(children.size() - 1) : null);
}
public Node getNextSibling() {
if (parent == null || index + 1 >= parent.children.size()) {
return null;
}
return parent.children.get(index + 1);
}
public boolean hasChildNodes() {
return children.size() != 0;
}
public Node insertBefore(Node newChild, Node refChild) throws DOMException {
LeafNodeImpl refChildImpl = (LeafNodeImpl) refChild;
if (refChildImpl.document != document) {
throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
}
if (refChildImpl.parent != this) {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
return insertChildAt(newChild, refChildImpl.index);
}
/**
* Inserts a new child node into this node at a given position. If the new
* node is already child of another node, it is first removed from there.
* This method is the generalization of the appendChild() and insertBefore()
* methods.
*
* @param newChild The new child node to add.
* @param index The index at which to insert the new child node.
*
* @return The node added.
*
* @throws DOMException If the attempted operation violates the XML/DOM
* well-formedness rules.
*/
public Node insertChildAt(Node newChild, int index) throws DOMException {
LeafNodeImpl newChildImpl = (LeafNodeImpl) newChild;
if (document != null && newChildImpl.document != null && newChildImpl.document != document) {
throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
}
if (newChildImpl.isParentOf(this)) {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
if (newChildImpl.parent != null) {
int oldIndex = newChildImpl.index;
newChildImpl.parent.children.remove(oldIndex);
newChildImpl.parent.refreshIndices(oldIndex);
}
children.add(index, newChildImpl);
newChildImpl.parent = this;
refreshIndices(index);
return newChild;
}
public boolean isParentOf(Node node) {
LeafNodeImpl nodeImpl = (LeafNodeImpl) node;
while (nodeImpl != null) {
if (nodeImpl == this) {
return true;
}
nodeImpl = nodeImpl.parent;
}
return false;
}
/**
* Normalize the text nodes within this subtree. Although named similarly,
* this method is unrelated to Document.normalize.
*/
@Override
public final void normalize() {
Text next = null; // null if next doesn't exist or is not a TEXT_NODE
for (int i = children.size() - 1; i >= 0; i--) {
Node node = children.get(i);
node.normalize();
if (node.getNodeType() != Node.TEXT_NODE) {
next = null;
continue;
}
Text text = (Text) node;
if (text.getLength() == 0) {
removeChild(text);
continue;
}
if (next != null) {
text.appendData(next.getData());
removeChild(next);
}
next = text;
}
}
private void refreshIndices(int fromIndex) {
for (int i = fromIndex; i < children.size(); i++) {
children.get(i).index = i;
}
}
public Node removeChild(Node oldChild) throws DOMException {
LeafNodeImpl oldChildImpl = (LeafNodeImpl) oldChild;
if (oldChildImpl.document != document) {
throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
}
if (oldChildImpl.parent != this) {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
int index = oldChildImpl.index;
children.remove(index);
oldChildImpl.parent = null;
refreshIndices(index);
return oldChild;
}
public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
LeafNodeImpl oldChildImpl = (LeafNodeImpl) oldChild;
LeafNodeImpl newChildImpl = (LeafNodeImpl) newChild;
if (oldChildImpl.document != document
|| newChildImpl.document != document) {
throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
}
if (oldChildImpl.parent != this || newChildImpl.isParentOf(this)) {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
}
int index = oldChildImpl.index;
children.set(index, newChildImpl);
oldChildImpl.parent = null;
newChildImpl.parent = this;
refreshIndices(index);
return oldChildImpl;
}
public String getTextContent() throws DOMException {
Node child = getFirstChild();
if (child == null) {
return "";
}
Node next = child.getNextSibling();
if (next == null) {
return hasTextContent(child) ? child.getTextContent() : "";
}
StringBuilder buf = new StringBuilder();
getTextContent(buf);
return buf.toString();
}
void getTextContent(StringBuilder buf) throws DOMException {
Node child = getFirstChild();
while (child != null) {
if (hasTextContent(child)) {
((NodeImpl) child).getTextContent(buf);
}
child = child.getNextSibling();
}
}
final boolean hasTextContent(Node child) {
// TODO: skip text nodes with ignorable whitespace?
return child.getNodeType() != Node.COMMENT_NODE
&& child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE;
}
}