InnerNodeImpl.java revision 09c4640423dbe85c606c5b46312cd5c0e5c94eeb
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package org.apache.harmony.xml.dom; 18 19import java.util.ArrayList; 20import java.util.List; 21import libcore.base.Objects; 22import org.w3c.dom.DOMException; 23import org.w3c.dom.DocumentFragment; 24import org.w3c.dom.Node; 25import org.w3c.dom.NodeList; 26 27/** 28 * Provides a straightforward implementation of the corresponding W3C DOM 29 * interface. The class is used internally only, thus only notable members that 30 * are not in the original interface are documented (the W3C docs are quite 31 * extensive). 32 * 33 * <p>Some of the fields may have package visibility, so other classes belonging 34 * to the DOM implementation can easily access them while maintaining the DOM 35 * tree structure. 36 * 37 * <p>This class represents a Node that has a parent Node as well as 38 * (potentially) a number of children. 39 * 40 * <p>Some code was adapted from Apache Xerces. 41 */ 42public abstract class InnerNodeImpl extends LeafNodeImpl { 43 44 // Maintained by LeafNodeImpl and ElementImpl. 45 List<LeafNodeImpl> children = new ArrayList<LeafNodeImpl>(); 46 47 protected InnerNodeImpl(DocumentImpl document) { 48 super(document); 49 } 50 51 public Node appendChild(Node newChild) throws DOMException { 52 return insertChildAt(newChild, children.size()); 53 } 54 55 public NodeList getChildNodes() { 56 NodeListImpl list = new NodeListImpl(); 57 58 for (NodeImpl node : children) { 59 list.add(node); 60 } 61 62 return list; 63 } 64 65 public Node getFirstChild() { 66 return (!children.isEmpty() ? children.get(0) : null); 67 } 68 69 public Node getLastChild() { 70 return (!children.isEmpty() ? children.get(children.size() - 1) : null); 71 } 72 73 public Node getNextSibling() { 74 if (parent == null || index + 1 >= parent.children.size()) { 75 return null; 76 } 77 78 return parent.children.get(index + 1); 79 } 80 81 public boolean hasChildNodes() { 82 return children.size() != 0; 83 } 84 85 public Node insertBefore(Node newChild, Node refChild) throws DOMException { 86 LeafNodeImpl refChildImpl = (LeafNodeImpl) refChild; 87 88 if (refChildImpl.document != document) { 89 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null); 90 } 91 92 if (refChildImpl.parent != this) { 93 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null); 94 } 95 96 return insertChildAt(newChild, refChildImpl.index); 97 } 98 99 /** 100 * Inserts {@code newChild} at {@code index}. If it is already child of 101 * another node, it is removed from there. 102 */ 103 Node insertChildAt(Node newChild, int index) throws DOMException { 104 if (newChild instanceof DocumentFragment) { 105 NodeList toAdd = newChild.getChildNodes(); 106 for (int i = 0; i < toAdd.getLength(); i++) { 107 insertChildAt(toAdd.item(i), index + i); 108 } 109 return newChild; 110 } 111 112 LeafNodeImpl toInsert = (LeafNodeImpl) newChild; 113 if (toInsert.document != null && document != null && toInsert.document != document) { 114 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null); 115 } 116 if (toInsert.isParentOf(this)) { 117 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null); 118 } 119 120 if (toInsert.parent != null) { 121 int oldIndex = toInsert.index; 122 toInsert.parent.children.remove(oldIndex); 123 toInsert.parent.refreshIndices(oldIndex); 124 } 125 126 children.add(index, toInsert); 127 toInsert.parent = this; 128 refreshIndices(index); 129 130 return newChild; 131 } 132 133 public boolean isParentOf(Node node) { 134 LeafNodeImpl nodeImpl = (LeafNodeImpl) node; 135 136 while (nodeImpl != null) { 137 if (nodeImpl == this) { 138 return true; 139 } 140 141 nodeImpl = nodeImpl.parent; 142 } 143 144 return false; 145 } 146 147 /** 148 * Normalize the text nodes within this subtree. Although named similarly, 149 * this method is unrelated to Document.normalize. 150 */ 151 @Override 152 public final void normalize() { 153 Node next; 154 for (Node node = getFirstChild(); node != null; node = next) { 155 next = node.getNextSibling(); 156 node.normalize(); 157 158 if (node.getNodeType() == Node.TEXT_NODE) { 159 ((TextImpl) node).minimize(); 160 } 161 } 162 } 163 164 private void refreshIndices(int fromIndex) { 165 for (int i = fromIndex; i < children.size(); i++) { 166 children.get(i).index = i; 167 } 168 } 169 170 public Node removeChild(Node oldChild) throws DOMException { 171 LeafNodeImpl oldChildImpl = (LeafNodeImpl) oldChild; 172 173 if (oldChildImpl.document != document) { 174 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null); 175 } 176 if (oldChildImpl.parent != this) { 177 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null); 178 } 179 180 int index = oldChildImpl.index; 181 children.remove(index); 182 oldChildImpl.parent = null; 183 refreshIndices(index); 184 185 return oldChild; 186 } 187 188 /** 189 * Removes {@code oldChild} and adds {@code newChild} in its place. This 190 * is not atomic. 191 */ 192 public Node replaceChild(Node newChild, Node oldChild) throws DOMException { 193 int index = ((LeafNodeImpl) oldChild).index; 194 removeChild(oldChild); 195 insertChildAt(newChild, index); 196 return oldChild; 197 } 198 199 public String getTextContent() throws DOMException { 200 Node child = getFirstChild(); 201 if (child == null) { 202 return ""; 203 } 204 205 Node next = child.getNextSibling(); 206 if (next == null) { 207 return hasTextContent(child) ? child.getTextContent() : ""; 208 } 209 210 StringBuilder buf = new StringBuilder(); 211 getTextContent(buf); 212 return buf.toString(); 213 } 214 215 void getTextContent(StringBuilder buf) throws DOMException { 216 Node child = getFirstChild(); 217 while (child != null) { 218 if (hasTextContent(child)) { 219 ((NodeImpl) child).getTextContent(buf); 220 } 221 child = child.getNextSibling(); 222 } 223 } 224 225 final boolean hasTextContent(Node child) { 226 // TODO: skip text nodes with ignorable whitespace? 227 return child.getNodeType() != Node.COMMENT_NODE 228 && child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE; 229 } 230 231 void getElementsByTagName(NodeListImpl out, String name) { 232 for (NodeImpl node : children) { 233 if (node.getNodeType() == Node.ELEMENT_NODE) { 234 ElementImpl element = (ElementImpl) node; 235 if (matchesNameOrWildcard(name, element.getNodeName())) { 236 out.add(element); 237 } 238 element.getElementsByTagName(out, name); 239 } 240 } 241 } 242 243 void getElementsByTagNameNS(NodeListImpl out, String namespaceURI, String localName) { 244 for (NodeImpl node : children) { 245 if (node.getNodeType() == Node.ELEMENT_NODE) { 246 ElementImpl element = (ElementImpl) node; 247 if (matchesNameOrWildcard(namespaceURI, element.getNamespaceURI()) 248 && matchesNameOrWildcard(localName, element.getLocalName())) { 249 out.add(element); 250 } 251 element.getElementsByTagNameNS(out, namespaceURI, localName); 252 } 253 } 254 } 255 256 /** 257 * Returns true if {@code pattern} equals either "*" or {@code s}. Pattern 258 * may be {@code null}. 259 */ 260 private static boolean matchesNameOrWildcard(String pattern, String s) { 261 return "*".equals(pattern) || Objects.equal(pattern, s); 262 } 263} 264