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