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 17 package org.apache.harmony.xml.dom; 18 19 import java.util.ArrayList; 20 import java.util.List; 21 import java.util.Objects; 22 import org.w3c.dom.DOMException; 23 import org.w3c.dom.DocumentFragment; 24 import org.w3c.dom.Node; 25 import 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 */ 42 public abstract class InnerNodeImpl extends LeafNodeImpl { 43 44 // Maintained by LeafNodeImpl and ElementImpl. 45 List<LeafNodeImpl> children = new ArrayList<LeafNodeImpl>(); 46 InnerNodeImpl(DocumentImpl document)47 protected InnerNodeImpl(DocumentImpl document) { 48 super(document); 49 } 50 appendChild(Node newChild)51 public Node appendChild(Node newChild) throws DOMException { 52 return insertChildAt(newChild, children.size()); 53 } 54 getChildNodes()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 getFirstChild()65 public Node getFirstChild() { 66 return (!children.isEmpty() ? children.get(0) : null); 67 } 68 getLastChild()69 public Node getLastChild() { 70 return (!children.isEmpty() ? children.get(children.size() - 1) : null); 71 } 72 getNextSibling()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 hasChildNodes()81 public boolean hasChildNodes() { 82 return children.size() != 0; 83 } 84 insertBefore(Node newChild, Node refChild)85 public Node insertBefore(Node newChild, Node refChild) throws DOMException { 86 LeafNodeImpl refChildImpl = (LeafNodeImpl) refChild; 87 88 if (refChildImpl == null) { 89 return appendChild(newChild); 90 } 91 92 if (refChildImpl.document != document) { 93 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null); 94 } 95 96 if (refChildImpl.parent != this) { 97 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null); 98 } 99 100 return insertChildAt(newChild, refChildImpl.index); 101 } 102 103 /** 104 * Inserts {@code newChild} at {@code index}. If it is already child of 105 * another node, it is removed from there. 106 */ insertChildAt(Node newChild, int index)107 Node insertChildAt(Node newChild, int index) throws DOMException { 108 if (newChild instanceof DocumentFragment) { 109 NodeList toAdd = newChild.getChildNodes(); 110 for (int i = 0; i < toAdd.getLength(); i++) { 111 insertChildAt(toAdd.item(i), index + i); 112 } 113 return newChild; 114 } 115 116 LeafNodeImpl toInsert = (LeafNodeImpl) newChild; 117 if (toInsert.document != null && document != null && toInsert.document != document) { 118 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null); 119 } 120 if (toInsert.isParentOf(this)) { 121 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null); 122 } 123 124 if (toInsert.parent != null) { 125 int oldIndex = toInsert.index; 126 toInsert.parent.children.remove(oldIndex); 127 toInsert.parent.refreshIndices(oldIndex); 128 } 129 130 children.add(index, toInsert); 131 toInsert.parent = this; 132 refreshIndices(index); 133 134 return newChild; 135 } 136 isParentOf(Node node)137 public boolean isParentOf(Node node) { 138 LeafNodeImpl nodeImpl = (LeafNodeImpl) node; 139 140 while (nodeImpl != null) { 141 if (nodeImpl == this) { 142 return true; 143 } 144 145 nodeImpl = nodeImpl.parent; 146 } 147 148 return false; 149 } 150 151 /** 152 * Normalize the text nodes within this subtree. Although named similarly, 153 * this method is unrelated to Document.normalize. 154 */ 155 @Override normalize()156 public final void normalize() { 157 Node next; 158 for (Node node = getFirstChild(); node != null; node = next) { 159 next = node.getNextSibling(); 160 node.normalize(); 161 162 if (node.getNodeType() == Node.TEXT_NODE) { 163 ((TextImpl) node).minimize(); 164 } 165 } 166 } 167 refreshIndices(int fromIndex)168 private void refreshIndices(int fromIndex) { 169 for (int i = fromIndex; i < children.size(); i++) { 170 children.get(i).index = i; 171 } 172 } 173 removeChild(Node oldChild)174 public Node removeChild(Node oldChild) throws DOMException { 175 LeafNodeImpl oldChildImpl = (LeafNodeImpl) oldChild; 176 177 if (oldChildImpl.document != document) { 178 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null); 179 } 180 if (oldChildImpl.parent != this) { 181 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null); 182 } 183 184 int index = oldChildImpl.index; 185 children.remove(index); 186 oldChildImpl.parent = null; 187 refreshIndices(index); 188 189 return oldChild; 190 } 191 192 /** 193 * Removes {@code oldChild} and adds {@code newChild} in its place. This 194 * is not atomic. 195 */ replaceChild(Node newChild, Node oldChild)196 public Node replaceChild(Node newChild, Node oldChild) throws DOMException { 197 int index = ((LeafNodeImpl) oldChild).index; 198 removeChild(oldChild); 199 insertChildAt(newChild, index); 200 return oldChild; 201 } 202 getTextContent()203 public String getTextContent() throws DOMException { 204 Node child = getFirstChild(); 205 if (child == null) { 206 return ""; 207 } 208 209 Node next = child.getNextSibling(); 210 if (next == null) { 211 return hasTextContent(child) ? child.getTextContent() : ""; 212 } 213 214 StringBuilder buf = new StringBuilder(); 215 getTextContent(buf); 216 return buf.toString(); 217 } 218 getTextContent(StringBuilder buf)219 void getTextContent(StringBuilder buf) throws DOMException { 220 Node child = getFirstChild(); 221 while (child != null) { 222 if (hasTextContent(child)) { 223 ((NodeImpl) child).getTextContent(buf); 224 } 225 child = child.getNextSibling(); 226 } 227 } 228 hasTextContent(Node child)229 final boolean hasTextContent(Node child) { 230 // TODO: skip text nodes with ignorable whitespace? 231 return child.getNodeType() != Node.COMMENT_NODE 232 && child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE; 233 } 234 getElementsByTagName(NodeListImpl out, String name)235 void getElementsByTagName(NodeListImpl out, String name) { 236 for (NodeImpl node : children) { 237 if (node.getNodeType() == Node.ELEMENT_NODE) { 238 ElementImpl element = (ElementImpl) node; 239 if (matchesNameOrWildcard(name, element.getNodeName())) { 240 out.add(element); 241 } 242 element.getElementsByTagName(out, name); 243 } 244 } 245 } 246 getElementsByTagNameNS(NodeListImpl out, String namespaceURI, String localName)247 void getElementsByTagNameNS(NodeListImpl out, String namespaceURI, String localName) { 248 for (NodeImpl node : children) { 249 if (node.getNodeType() == Node.ELEMENT_NODE) { 250 ElementImpl element = (ElementImpl) node; 251 if (matchesNameOrWildcard(namespaceURI, element.getNamespaceURI()) 252 && matchesNameOrWildcard(localName, element.getLocalName())) { 253 out.add(element); 254 } 255 element.getElementsByTagNameNS(out, namespaceURI, localName); 256 } 257 } 258 } 259 260 /** 261 * Returns true if {@code pattern} equals either "*" or {@code s}. Pattern 262 * may be {@code null}. 263 */ matchesNameOrWildcard(String pattern, String s)264 private static boolean matchesNameOrWildcard(String pattern, String s) { 265 return "*".equals(pattern) || Objects.equals(pattern, s); 266 } 267 } 268