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