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.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