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