Home | History | Annotate | Download | only in dom
      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 
     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 
     92     private static boolean isXMLIdentifierStart(char c) {
     93         return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_');
     94     }
     95 
     96     private static boolean isXMLIdentifierPart(char c) {
     97         return isXMLIdentifierStart(c) || (c >= '0' && c <= '9') || (c == '-') || (c == '.');
     98     }
     99 
    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      */
    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      */
    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 
    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      */
    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      */
    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 
    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 
    323     public AttrImpl createAttribute(String name) {
    324         return new AttrImpl(this, name);
    325     }
    326 
    327     public AttrImpl createAttributeNS(String namespaceURI, String qualifiedName) {
    328         return new AttrImpl(this, namespaceURI, qualifiedName);
    329     }
    330 
    331     public CDATASectionImpl createCDATASection(String data) {
    332         return new CDATASectionImpl(this, data);
    333     }
    334 
    335     public CommentImpl createComment(String data) {
    336         return new CommentImpl(this, data);
    337     }
    338 
    339     public DocumentFragmentImpl createDocumentFragment() {
    340         return new DocumentFragmentImpl(this);
    341     }
    342 
    343     public ElementImpl createElement(String tagName) {
    344         return new ElementImpl(this, tagName);
    345     }
    346 
    347     public ElementImpl createElementNS(String namespaceURI, String qualifiedName) {
    348         return new ElementImpl(this, namespaceURI, qualifiedName);
    349     }
    350 
    351     public EntityReferenceImpl createEntityReference(String name) {
    352         return new EntityReferenceImpl(this, name);
    353     }
    354 
    355     public ProcessingInstructionImpl createProcessingInstruction(String target, String data) {
    356         return new ProcessingInstructionImpl(this, target, data);
    357     }
    358 
    359     public TextImpl createTextNode(String data) {
    360         return new TextImpl(this, data);
    361     }
    362 
    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 
    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 
    383     public Element getElementById(String elementId) {
    384         ElementImpl root = (ElementImpl) getDocumentElement();
    385 
    386         return (root == null ? null : root.getElementById(elementId));
    387     }
    388 
    389     public NodeList getElementsByTagName(String name) {
    390         NodeListImpl result = new NodeListImpl();
    391         getElementsByTagName(result, name);
    392         return result;
    393     }
    394 
    395     public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
    396         NodeListImpl result = new NodeListImpl();
    397         getElementsByTagNameNS(result, namespaceURI, localName);
    398         return result;
    399     }
    400 
    401     public DOMImplementation getImplementation() {
    402         return domImplementation;
    403     }
    404 
    405     @Override
    406     public String getNodeName() {
    407         return "#document";
    408     }
    409 
    410     @Override
    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      */
    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 
    431     @Override public String getTextContent() {
    432         return null;
    433     }
    434 
    435     public String getInputEncoding() {
    436         return inputEncoding;
    437     }
    438 
    439     public String getXmlEncoding() {
    440         return xmlEncoding;
    441     }
    442 
    443     public boolean getXmlStandalone() {
    444         return xmlStandalone;
    445     }
    446 
    447     public void setXmlStandalone(boolean xmlStandalone) {
    448         this.xmlStandalone = xmlStandalone;
    449     }
    450 
    451     public String getXmlVersion() {
    452         return xmlVersion;
    453     }
    454 
    455     public void setXmlVersion(String xmlVersion) {
    456         this.xmlVersion = xmlVersion;
    457     }
    458 
    459     public boolean getStrictErrorChecking() {
    460         return strictErrorChecking;
    461     }
    462 
    463     public void setStrictErrorChecking(boolean strictErrorChecking) {
    464         this.strictErrorChecking = strictErrorChecking;
    465     }
    466 
    467     public String getDocumentURI() {
    468         return documentUri;
    469     }
    470 
    471     public void setDocumentURI(String documentUri) {
    472         this.documentUri = documentUri;
    473     }
    474 
    475     public DOMConfiguration getDomConfig() {
    476         if (domConfiguration == null) {
    477             domConfiguration = new DOMConfigurationImpl();
    478         }
    479         return domConfiguration;
    480     }
    481 
    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      */
    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      */
    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      */
    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