DocumentImpl.java revision f33eae7e84eb6d3b0f4e86b59605bb3de73009f3
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 org.w3c.dom.Attr; 20import org.w3c.dom.CharacterData; 21import org.w3c.dom.Comment; 22import org.w3c.dom.DOMConfiguration; 23import org.w3c.dom.DOMException; 24import org.w3c.dom.DOMImplementation; 25import org.w3c.dom.Document; 26import org.w3c.dom.DocumentType; 27import org.w3c.dom.Element; 28import org.w3c.dom.NamedNodeMap; 29import org.w3c.dom.Node; 30import org.w3c.dom.NodeList; 31import org.w3c.dom.ProcessingInstruction; 32import org.w3c.dom.Text; 33import org.w3c.dom.UserDataHandler; 34 35import java.util.Collections; 36import java.util.HashMap; 37import java.util.Map; 38import java.util.WeakHashMap; 39 40/** 41 * Provides a straightforward implementation of the corresponding W3C DOM 42 * interface. The class is used internally only, thus only notable members that 43 * are not in the original interface are documented (the W3C docs are quite 44 * extensive). Hope that's ok. 45 * <p> 46 * Some of the fields may have package visibility, so other classes belonging to 47 * the DOM implementation can easily access them while maintaining the DOM tree 48 * structure. 49 */ 50public final class DocumentImpl extends InnerNodeImpl implements Document { 51 52 private DOMImplementation domImplementation; 53 private DOMConfigurationImpl domConfiguration; 54 55 /* 56 * The default values of these fields are specified by the Document 57 * interface. 58 */ 59 private String documentUri; 60 private String inputEncoding; 61 private String xmlEncoding; 62 private String xmlVersion = "1.0"; 63 private boolean xmlStandalone = false; 64 private boolean strictErrorChecking = true; 65 66 /** 67 * A lazily initialized map of user data values for this document's own 68 * nodes. The map is weak because the document may live longer than its 69 * nodes. 70 * 71 * <p>Attaching user data directly to the corresponding node would cost a 72 * field per node. Under the assumption that user data is rarely needed, we 73 * attach user data to the document to save those fields. Xerces also takes 74 * this approach. 75 */ 76 private WeakHashMap<NodeImpl, Map<String, UserData>> nodeToUserData; 77 78 public DocumentImpl(DOMImplementationImpl impl, String namespaceURI, 79 String qualifiedName, DocumentType doctype, String inputEncoding) { 80 super(null); 81 82 this.domImplementation = impl; 83 this.inputEncoding = inputEncoding; 84 this.document = this; 85 86 if (doctype != null) { 87 appendChild(doctype); 88 } 89 90 if (qualifiedName != null) { 91 appendChild(createElementNS(namespaceURI, qualifiedName)); 92 } 93 } 94 95 private static boolean isXMLIdentifierStart(char c) { 96 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'); 97 } 98 99 private static boolean isXMLIdentifierPart(char c) { 100 return isXMLIdentifierStart(c) || (c >= '0' && c <= '9') || (c == '-') || (c == '.'); 101 } 102 103 static boolean isXMLIdentifier(String s) { 104 if (s.length() == 0) { 105 return false; 106 } 107 108 if (!isXMLIdentifierStart(s.charAt(0))) { 109 return false; 110 } 111 112 for (int i = 1; i < s.length(); i++) { 113 if (!isXMLIdentifierPart(s.charAt(i))) { 114 return false; 115 } 116 } 117 118 return true; 119 } 120 121 /** 122 * Returns a shallow copy of the given node. If the node is an element node, 123 * its attributes are always copied. 124 * 125 * @param node a node belonging to any document or DOM implementation. 126 * @param operation the operation type to use when notifying user data 127 * handlers of copied element attributes. It is the caller's 128 * responsibility to notify user data handlers of the returned node. 129 * @return a new node whose document is this document and whose DOM 130 * implementation is this DOM implementation. 131 */ 132 private NodeImpl shallowCopy(short operation, Node node) { 133 switch (node.getNodeType()) { 134 case Node.ATTRIBUTE_NODE: 135 Attr attr = (Attr) node; 136 AttrImpl attrCopy = createAttributeNS(attr.getNamespaceURI(), attr.getLocalName()); 137 attrCopy.setPrefix(attr.getPrefix()); 138 attrCopy.setNodeValue(attr.getNodeValue()); 139 return attrCopy; 140 141 case Node.CDATA_SECTION_NODE: 142 return createCDATASection(((CharacterData) node).getData()); 143 144 case Node.COMMENT_NODE: 145 return createComment(((Comment) node).getData()); 146 147 case Node.DOCUMENT_FRAGMENT_NODE: 148 return createDocumentFragment(); 149 150 case Node.DOCUMENT_NODE: 151 case Node.DOCUMENT_TYPE_NODE: 152 throw new DOMException(DOMException.NOT_SUPPORTED_ERR, 153 "Cannot copy node of type " + node.getNodeType()); 154 155 case Node.ELEMENT_NODE: 156 Element element = (Element) node; 157 ElementImpl elementCopy = createElementNS( 158 element.getNamespaceURI(), element.getLocalName()); 159 elementCopy.setPrefix(element.getPrefix()); 160 NamedNodeMap attributes = element.getAttributes(); 161 for (int i = 0; i < attributes.getLength(); i++) { 162 Node elementAttr = attributes.item(i); 163 AttrImpl elementAttrCopy = (AttrImpl) shallowCopy(operation, elementAttr); 164 notifyUserDataHandlers(operation, elementAttr, elementAttrCopy); 165 elementCopy.setAttributeNodeNS(elementAttrCopy); 166 } 167 return elementCopy; 168 169 case Node.ENTITY_NODE: 170 case Node.NOTATION_NODE: 171 // TODO: implement this when we support these node types 172 throw new UnsupportedOperationException(); 173 174 case Node.ENTITY_REFERENCE_NODE: 175 /* 176 * When we support entities in the doctype, this will need to 177 * behave differently for clones vs. imports. Clones copy 178 * entities by value, copying the referenced subtree from the 179 * original document. Imports copy entities by reference, 180 * possibly referring to a different subtree in the new 181 * document. 182 */ 183 return createEntityReference(node.getNodeName()); 184 185 case Node.PROCESSING_INSTRUCTION_NODE: 186 ProcessingInstruction pi = (ProcessingInstruction) node; 187 return createProcessingInstruction(pi.getTarget(), pi.getData()); 188 189 case Node.TEXT_NODE: 190 return createTextNode(((Text) node).getData()); 191 192 default: 193 throw new DOMException(DOMException.NOT_SUPPORTED_ERR, 194 "Unsupported node type " + node.getNodeType()); 195 } 196 } 197 198 /** 199 * Returns a copy of the given node or subtree with this document as its 200 * owner. 201 * 202 * @param operation either {@link UserDataHandler#NODE_CLONED} or 203 * {@link UserDataHandler#NODE_IMPORTED}. 204 * @param node a node belonging to any document or DOM implementation. 205 * @param deep true to recursively copy any child nodes; false to do no such 206 * copying and return a node with no children. 207 */ 208 Node cloneOrImportNode(short operation, Node node, boolean deep) { 209 NodeImpl copy = shallowCopy(operation, node); 210 211 if (deep) { 212 NodeList list = node.getChildNodes(); 213 for (int i = 0; i < list.getLength(); i++) { 214 copy.appendChild(cloneOrImportNode(operation, list.item(i), deep)); 215 } 216 } 217 218 notifyUserDataHandlers(operation, node, copy); 219 return copy; 220 } 221 222 public Node importNode(Node importedNode, boolean deep) { 223 return cloneOrImportNode(UserDataHandler.NODE_IMPORTED, importedNode, deep); 224 } 225 226 /** 227 * Detaches the node from its parent (if any) and changes its document to 228 * this document. The node's subtree and attributes will remain attached, 229 * but their document will be changed to this document. 230 */ 231 public Node adoptNode(Node node) { 232 if (!(node instanceof NodeImpl)) { 233 return null; // the API specifies this quiet failure 234 } 235 NodeImpl nodeImpl = (NodeImpl) node; 236 switch (nodeImpl.getNodeType()) { 237 case Node.ATTRIBUTE_NODE: 238 AttrImpl attr = (AttrImpl) node; 239 if (attr.ownerElement != null) { 240 attr.ownerElement.removeAttributeNode(attr); 241 } 242 break; 243 244 case Node.DOCUMENT_FRAGMENT_NODE: 245 case Node.ENTITY_REFERENCE_NODE: 246 case Node.PROCESSING_INSTRUCTION_NODE: 247 case Node.TEXT_NODE: 248 case Node.CDATA_SECTION_NODE: 249 case Node.COMMENT_NODE: 250 case Node.ELEMENT_NODE: 251 break; 252 253 case Node.DOCUMENT_NODE: 254 case Node.DOCUMENT_TYPE_NODE: 255 case Node.ENTITY_NODE: 256 case Node.NOTATION_NODE: 257 throw new DOMException(DOMException.NOT_SUPPORTED_ERR, 258 "Cannot adopt nodes of type " + nodeImpl.getNodeType()); 259 260 default: 261 throw new DOMException(DOMException.NOT_SUPPORTED_ERR, 262 "Unsupported node type " + node.getNodeType()); 263 } 264 265 Node parent = nodeImpl.getParentNode(); 266 if (parent != null) { 267 parent.removeChild(nodeImpl); 268 } 269 270 changeDocumentToThis(nodeImpl); 271 notifyUserDataHandlers(UserDataHandler.NODE_ADOPTED, node, null); 272 return nodeImpl; 273 } 274 275 /** 276 * Recursively change the document of {@code node} without also changing its 277 * parent node. Only adoptNode() should invoke this method, otherwise nodes 278 * will be left in an inconsistent state. 279 */ 280 private void changeDocumentToThis(NodeImpl node) { 281 Map<String, UserData> userData = node.document.getUserDataMapForRead(node); 282 if (!userData.isEmpty()) { 283 getUserDataMap(node).putAll(userData); 284 } 285 node.document = this; 286 287 // change the document on all child nodes 288 NodeList list = node.getChildNodes(); 289 for (int i = 0; i < list.getLength(); i++) { 290 changeDocumentToThis((NodeImpl) list.item(i)); 291 } 292 293 // change the document on all attribute nodes 294 if (node.getNodeType() == Node.ELEMENT_NODE) { 295 NamedNodeMap attributes = node.getAttributes(); 296 for (int i = 0; i < attributes.getLength(); i++) { 297 changeDocumentToThis((AttrImpl) attributes.item(i)); 298 } 299 } 300 } 301 302 public Node renameNode(Node node, String namespaceURI, String qualifiedName) { 303 if (node.getOwnerDocument() != this) { 304 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null); 305 } 306 307 setNameNS((NodeImpl) node, namespaceURI, qualifiedName); 308 notifyUserDataHandlers(UserDataHandler.NODE_RENAMED, node, null); 309 return node; 310 } 311 312 public AttrImpl createAttribute(String name) { 313 return new AttrImpl(this, name); 314 } 315 316 public AttrImpl createAttributeNS(String namespaceURI, String qualifiedName) { 317 return new AttrImpl(this, namespaceURI, qualifiedName); 318 } 319 320 public CDATASectionImpl createCDATASection(String data) { 321 return new CDATASectionImpl(this, data); 322 } 323 324 public CommentImpl createComment(String data) { 325 return new CommentImpl(this, data); 326 } 327 328 public DocumentFragmentImpl createDocumentFragment() { 329 return new DocumentFragmentImpl(this); 330 } 331 332 public ElementImpl createElement(String tagName) { 333 return new ElementImpl(this, tagName); 334 } 335 336 public ElementImpl createElementNS(String namespaceURI, String qualifiedName) { 337 return new ElementImpl(this, namespaceURI, qualifiedName); 338 } 339 340 public EntityReferenceImpl createEntityReference(String name) { 341 return new EntityReferenceImpl(this, name); 342 } 343 344 public ProcessingInstructionImpl createProcessingInstruction(String target, String data) { 345 return new ProcessingInstructionImpl(this, target, data); 346 } 347 348 public TextImpl createTextNode(String data) { 349 return new TextImpl(this, data); 350 } 351 352 public DocumentType getDoctype() { 353 for (LeafNodeImpl child : children) { 354 if (child instanceof DocumentType) { 355 return (DocumentType) child; 356 } 357 } 358 359 return null; 360 } 361 362 public Element getDocumentElement() { 363 for (LeafNodeImpl child : children) { 364 if (child instanceof Element) { 365 return (Element) child; 366 } 367 } 368 369 return null; 370 } 371 372 public Element getElementById(String elementId) { 373 ElementImpl root = (ElementImpl) getDocumentElement(); 374 375 return (root == null ? null : root.getElementById(elementId)); 376 } 377 378 public NodeList getElementsByTagName(String tagname) { 379 ElementImpl root = (ElementImpl) getDocumentElement(); 380 381 return (root == null ? new NodeListImpl() 382 : root.getElementsByTagName(tagname)); 383 } 384 385 public NodeList getElementsByTagNameNS(String namespaceURI, String localName) { 386 ElementImpl root = (ElementImpl) getDocumentElement(); 387 388 return (root == null ? new NodeListImpl() : root.getElementsByTagNameNS( 389 namespaceURI, localName)); 390 } 391 392 public DOMImplementation getImplementation() { 393 return domImplementation; 394 } 395 396 @Override 397 public String getNodeName() { 398 return "#document"; 399 } 400 401 @Override 402 public short getNodeType() { 403 return Node.DOCUMENT_NODE; 404 } 405 406 @Override 407 public Node insertChildAt(Node newChild, int index) { 408 // Make sure we have at most one root element and one DTD element. 409 if (newChild instanceof Element && getDocumentElement() != null) { 410 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 411 "Only one root element allowed"); 412 } else if (newChild instanceof DocumentType && getDoctype() != null) { 413 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 414 "Only one DOCTYPE element allowed"); 415 } 416 417 return super.insertChildAt(newChild, index); 418 } 419 420 @Override public String getTextContent() { 421 return null; 422 } 423 424 public String getInputEncoding() { 425 return inputEncoding; 426 } 427 428 public String getXmlEncoding() { 429 return xmlEncoding; 430 } 431 432 public boolean getXmlStandalone() { 433 return xmlStandalone; 434 } 435 436 public void setXmlStandalone(boolean xmlStandalone) { 437 this.xmlStandalone = xmlStandalone; 438 } 439 440 public String getXmlVersion() { 441 return xmlVersion; 442 } 443 444 public void setXmlVersion(String xmlVersion) { 445 this.xmlVersion = xmlVersion; 446 } 447 448 public boolean getStrictErrorChecking() { 449 return strictErrorChecking; 450 } 451 452 public void setStrictErrorChecking(boolean strictErrorChecking) { 453 this.strictErrorChecking = strictErrorChecking; 454 } 455 456 public String getDocumentURI() { 457 return documentUri; 458 } 459 460 public void setDocumentURI(String documentUri) { 461 this.documentUri = documentUri; 462 } 463 464 public DOMConfiguration getDomConfig() { 465 if (domConfiguration == null) { 466 domConfiguration = new DOMConfigurationImpl(); 467 } 468 return domConfiguration; 469 } 470 471 public void normalizeDocument() { 472 Element root = getDocumentElement(); 473 if (root == null) { 474 return; 475 } 476 477 ((DOMConfigurationImpl) getDomConfig()).normalize(root); 478 } 479 480 /** 481 * Returns a map with the user data objects attached to the specified node. 482 * This map is readable and writable. 483 */ 484 Map<String, UserData> getUserDataMap(NodeImpl node) { 485 if (nodeToUserData == null) { 486 nodeToUserData = new WeakHashMap<NodeImpl, Map<String, UserData>>(); 487 } 488 Map<String, UserData> userDataMap = nodeToUserData.get(node); 489 if (userDataMap == null) { 490 userDataMap = new HashMap<String, UserData>(); 491 nodeToUserData.put(node, userDataMap); 492 } 493 return userDataMap; 494 } 495 496 /** 497 * Returns a map with the user data objects attached to the specified node. 498 * The returned map may be read-only. 499 */ 500 Map<String, UserData> getUserDataMapForRead(NodeImpl node) { 501 if (nodeToUserData == null) { 502 return Collections.emptyMap(); 503 } 504 Map<String, UserData> userDataMap = nodeToUserData.get(node); 505 return userDataMap == null 506 ? Collections.<String, UserData>emptyMap() 507 : userDataMap; 508 } 509 510 /** 511 * Calls {@link UserDataHandler#handle} on each of the source node's 512 * value/handler pairs. 513 * 514 * <p>If the source node comes from another DOM implementation, user data 515 * handlers will <strong>not</strong> be notified. The DOM API provides no 516 * mechanism to inspect a foreign node's user data. 517 */ 518 private static void notifyUserDataHandlers( 519 short operation, Node source, NodeImpl destination) { 520 if (!(source instanceof NodeImpl)) { 521 return; 522 } 523 524 NodeImpl srcImpl = (NodeImpl) source; 525 if (srcImpl.document == null) { 526 return; 527 } 528 529 for (Map.Entry<String, UserData> entry 530 : srcImpl.document.getUserDataMapForRead(srcImpl).entrySet()) { 531 UserData userData = entry.getValue(); 532 if (userData.handler != null) { 533 userData.handler.handle( 534 operation, entry.getKey(), userData.value, source, destination); 535 } 536 } 537 } 538} 539