| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.harmony.xml.dom; |
| |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.CDATASection; |
| import org.w3c.dom.CharacterData; |
| import org.w3c.dom.Comment; |
| import org.w3c.dom.DOMConfiguration; |
| import org.w3c.dom.DOMException; |
| import org.w3c.dom.DOMImplementation; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.DocumentFragment; |
| import org.w3c.dom.DocumentType; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.EntityReference; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.ProcessingInstruction; |
| import org.w3c.dom.Text; |
| import org.w3c.dom.UserDataHandler; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.WeakHashMap; |
| |
| /** |
| * Provides a straightforward implementation of the corresponding W3C DOM |
| * interface. The class is used internally only, thus only notable members that |
| * are not in the original interface are documented (the W3C docs are quite |
| * extensive). Hope that's ok. |
| * <p> |
| * Some of the fields may have package visibility, so other classes belonging to |
| * the DOM implementation can easily access them while maintaining the DOM tree |
| * structure. |
| */ |
| public final class DocumentImpl extends InnerNodeImpl implements Document { |
| |
| private DOMImplementation domImplementation; |
| private DOMConfigurationImpl domConfiguration; |
| |
| /* |
| * The default values of these fields are specified by the Document |
| * interface. |
| */ |
| private String documentUri; |
| private String inputEncoding; |
| private String xmlEncoding; |
| private String xmlVersion = "1.0"; |
| private boolean xmlStandalone = false; |
| private boolean strictErrorChecking = true; |
| |
| /** |
| * A lazily initialized map of user data values for this document's own |
| * nodes. The map is weak because the document may live longer than its |
| * nodes. |
| * |
| * <p>Attaching user data directly to the corresponding node would cost a |
| * field per node. Under the assumption that user data is rarely needed, we |
| * attach user data to the document to save those fields. Xerces also takes |
| * this approach. |
| */ |
| private WeakHashMap<Node, Map<String, UserData>> nodeToUserData; |
| |
| public DocumentImpl(DOMImplementationImpl impl, String namespaceURI, |
| String qualifiedName, DocumentType doctype, String inputEncoding) { |
| super(null); |
| |
| this.domImplementation = impl; |
| this.inputEncoding = inputEncoding; |
| this.document = this; |
| |
| if (doctype != null) { |
| appendChild(doctype); |
| } |
| |
| if (qualifiedName != null) { |
| appendChild(createElementNS(namespaceURI, qualifiedName)); |
| } |
| } |
| |
| private static boolean isXMLIdentifierStart(char c) { |
| return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'); |
| } |
| |
| private static boolean isXMLIdentifierPart(char c) { |
| return isXMLIdentifierStart(c) || (c >= '0' && c <= '9') || (c == '-') || (c == '.'); |
| } |
| |
| static boolean isXMLIdentifier(String s) { |
| if (s.length() == 0) { |
| return false; |
| } |
| |
| if (!isXMLIdentifierStart(s.charAt(0))) { |
| return false; |
| } |
| |
| for (int i = 1; i < s.length(); i++) { |
| if (!isXMLIdentifierPart(s.charAt(i))) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Clones a node and (if requested) its children. The source node(s) may |
| * have been created by a different DocumentImpl or even DOM implementation. |
| * |
| * @param node The node to clone. |
| * @param deep If true, a deep copy is created (including all child nodes). |
| * |
| * @return The new node. |
| */ |
| Node cloneNode(Node node, boolean deep) throws DOMException { |
| Node target; |
| |
| switch (node.getNodeType()) { |
| case Node.ATTRIBUTE_NODE: { |
| Attr source = (Attr)node; |
| target = createAttributeNS(source.getNamespaceURI(), source.getLocalName()); |
| target.setPrefix(source.getPrefix()); |
| target.setNodeValue(source.getNodeValue()); |
| break; |
| } |
| case Node.CDATA_SECTION_NODE: { |
| CharacterData source = (CharacterData)node; |
| target = createCDATASection(source.getData()); |
| break; |
| } |
| case Node.COMMENT_NODE: { |
| Comment source = (Comment)node; |
| target = createComment(source.getData()); |
| break; |
| } |
| case Node.DOCUMENT_FRAGMENT_NODE: { |
| // Source is irrelevant in this case. |
| target = createDocumentFragment(); |
| break; |
| } |
| case Node.DOCUMENT_NODE: { |
| throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Cannot clone a Document node"); |
| } |
| case Node.DOCUMENT_TYPE_NODE: { |
| throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Cannot clone a DocumentType node"); |
| } |
| case Node.ELEMENT_NODE: { |
| Element source = (Element)node; |
| target = createElementNS(source.getNamespaceURI(), source.getLocalName()); |
| target.setPrefix(source.getPrefix()); |
| |
| NamedNodeMap map = source.getAttributes(); |
| for (int i = 0; i < map.getLength(); i++) { |
| Attr attr = (Attr)map.item(i); |
| ((Element)target).setAttributeNodeNS((Attr)cloneNode(attr, deep)); |
| } |
| break; |
| } |
| case Node.ENTITY_NODE: { |
| throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Cannot clone an Entity node"); |
| } |
| case Node.ENTITY_REFERENCE_NODE: { |
| EntityReference source = (EntityReference)node; |
| target = createEntityReference(source.getNodeName()); |
| break; |
| } |
| case Node.NOTATION_NODE: { |
| throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Cannot clone a Notation node"); |
| } |
| case Node.PROCESSING_INSTRUCTION_NODE: { |
| ProcessingInstruction source = (ProcessingInstruction)node; |
| target = createProcessingInstruction(source.getTarget(), source.getData()); |
| break; |
| } |
| case Node.TEXT_NODE: { |
| Text source = (Text)node; |
| target = createTextNode(source.getData()); |
| break; |
| } |
| default: { |
| throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Cannot clone unknown node type " + node.getNodeType() + " (" + node.getClass().getSimpleName() + ")"); |
| } |
| } |
| |
| if (deep) { |
| NodeList list = node.getChildNodes(); |
| for (int i = 0; i < list.getLength(); i++) { |
| Node child = cloneNode(list.item(i), deep); |
| target.appendChild(child); |
| } |
| } |
| |
| notifyUserDataHandlers(UserDataHandler.NODE_CLONED, node, target); |
| |
| return target; |
| } |
| |
| public AttrImpl createAttribute(String name) throws DOMException { |
| return new AttrImpl(this, name); |
| } |
| |
| public Attr createAttributeNS(String namespaceURI, String qualifiedName) |
| throws DOMException { |
| return new AttrImpl(this, namespaceURI, qualifiedName); |
| } |
| |
| public CDATASection createCDATASection(String data) throws DOMException { |
| return new CDATASectionImpl(this, data); |
| } |
| |
| public Comment createComment(String data) { |
| return new CommentImpl(this, data); |
| } |
| |
| public DocumentFragment createDocumentFragment() { |
| return new DocumentFragmentImpl(this); |
| } |
| |
| public Element createElement(String tagName) throws DOMException { |
| return new ElementImpl(this, tagName); |
| } |
| |
| public Element createElementNS(String namespaceURI, String qualifiedName) |
| throws DOMException { |
| return new ElementImpl(this, namespaceURI, qualifiedName); |
| } |
| |
| public EntityReference createEntityReference(String name) |
| throws DOMException { |
| return new EntityReferenceImpl(this, name); |
| } |
| |
| public ProcessingInstruction createProcessingInstruction(String target, |
| String data) throws DOMException { |
| return new ProcessingInstructionImpl(this, target, data); |
| } |
| |
| public Text createTextNode(String data) { |
| return new TextImpl(this, data); |
| } |
| |
| public DocumentType getDoctype() { |
| for (int i = 0; i < children.size(); i++) { |
| if (children.get(i) instanceof DocumentType) { |
| return (DocumentType) children.get(i); |
| } |
| } |
| |
| return null; |
| } |
| |
| public Element getDocumentElement() { |
| for (int i = 0; i < children.size(); i++) { |
| if (children.get(i) instanceof Element) { |
| return (Element) children.get(i); |
| } |
| } |
| |
| return null; |
| } |
| |
| public Element getElementById(String elementId) { |
| ElementImpl root = (ElementImpl) getDocumentElement(); |
| |
| return (root == null ? null : root.getElementById(elementId)); |
| } |
| |
| public NodeList getElementsByTagName(String tagname) { |
| ElementImpl root = (ElementImpl) getDocumentElement(); |
| |
| return (root == null ? new NodeListImpl() |
| : root.getElementsByTagName(tagname)); |
| } |
| |
| public NodeList getElementsByTagNameNS(String namespaceURI, String localName) { |
| ElementImpl root = (ElementImpl) getDocumentElement(); |
| |
| return (root == null ? new NodeListImpl() : root.getElementsByTagNameNS( |
| namespaceURI, localName)); |
| } |
| |
| public DOMImplementation getImplementation() { |
| return domImplementation; |
| } |
| |
| @Override |
| public String getNodeName() { |
| return "#document"; |
| } |
| |
| @Override |
| public short getNodeType() { |
| return Node.DOCUMENT_NODE; |
| } |
| |
| public Node importNode(Node importedNode, boolean deep) throws DOMException { |
| // TODO: callback the UserDataHandler with a NODE_IMPORTED event |
| return cloneNode(importedNode, deep); |
| } |
| |
| @Override |
| public Node insertChildAt(Node newChild, int index) throws DOMException { |
| // Make sure we have at most one root element and one DTD element. |
| if (newChild instanceof Element && getDocumentElement() != null) { |
| throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, |
| "Only one root element allowed"); |
| } else if (newChild instanceof DocumentType && getDoctype() != null) { |
| throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, |
| "Only one DOCTYPE element allowed"); |
| } |
| |
| return super.insertChildAt(newChild, index); |
| } |
| |
| @Override public String getTextContent() throws DOMException { |
| return null; |
| } |
| |
| public String getInputEncoding() { |
| return inputEncoding; |
| } |
| |
| public String getXmlEncoding() { |
| return xmlEncoding; |
| } |
| |
| public boolean getXmlStandalone() { |
| return xmlStandalone; |
| } |
| |
| public void setXmlStandalone(boolean xmlStandalone) throws DOMException { |
| this.xmlStandalone = xmlStandalone; |
| } |
| |
| public String getXmlVersion() { |
| return xmlVersion; |
| } |
| |
| public void setXmlVersion(String xmlVersion) throws DOMException { |
| this.xmlVersion = xmlVersion; |
| } |
| |
| public boolean getStrictErrorChecking() { |
| return strictErrorChecking; |
| } |
| |
| public void setStrictErrorChecking(boolean strictErrorChecking) { |
| this.strictErrorChecking = strictErrorChecking; |
| } |
| |
| public String getDocumentURI() { |
| return documentUri; |
| } |
| |
| public void setDocumentURI(String documentUri) { |
| this.documentUri = documentUri; |
| } |
| |
| public Node adoptNode(Node source) throws DOMException { |
| // TODO: callback the UserDataHandler with a NODE_ADOPTED event |
| throw new UnsupportedOperationException(); // TODO |
| } |
| |
| public DOMConfiguration getDomConfig() { |
| if (domConfiguration == null) { |
| domConfiguration = new DOMConfigurationImpl(); |
| } |
| return domConfiguration; |
| } |
| |
| public void normalizeDocument() { |
| Element root = getDocumentElement(); |
| if (root == null) { |
| return; |
| } |
| |
| ((DOMConfigurationImpl) getDomConfig()).normalize(root); |
| } |
| |
| public Node renameNode(Node n, String namespaceURI, String qualifiedName) |
| throws DOMException { |
| // TODO: callback the UserDataHandler with a NODE_RENAMED event |
| throw new UnsupportedOperationException(); // TODO |
| } |
| |
| /** |
| * Returns a map with the user data objects attached to the specified node. |
| * This map is readable and writable. |
| */ |
| Map<String, UserData> getUserDataMap(Node node) { |
| if (nodeToUserData == null) { |
| nodeToUserData = new WeakHashMap<Node, Map<String, UserData>>(); |
| } |
| Map<String, UserData> userDataMap = nodeToUserData.get(node); |
| if (userDataMap == null) { |
| userDataMap = new HashMap<String, UserData>(); |
| nodeToUserData.put(node, userDataMap); |
| } |
| return userDataMap; |
| } |
| |
| /** |
| * Returns a map with the user data objects attached to the specified node. |
| * The returned map may be read-only. |
| */ |
| Map<String, UserData> getUserDataMapForRead(Node node) { |
| if (nodeToUserData == null) { |
| return Collections.emptyMap(); |
| } |
| Map<String, UserData> userDataMap = nodeToUserData.get(node); |
| return userDataMap == null |
| ? Collections.<String, UserData>emptyMap() |
| : userDataMap; |
| } |
| |
| /** |
| * Calls {@link UserDataHandler#handle} on each of the source node's |
| * value/handler pairs. |
| */ |
| private void notifyUserDataHandlers(short operation, Node src, Node dst) { |
| for (Map.Entry<String, UserData> entry : getUserDataMapForRead(src).entrySet()) { |
| UserData userData = entry.getValue(); |
| if (userData.handler != null) { |
| userData.handler.handle( |
| operation, entry.getKey(), userData.value, src, dst); |
| } |
| } |
| } |
| } |