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