/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.xerces.dom; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.WeakHashMap; import org.apache.xerces.util.URI; import org.apache.xerces.util.XML11Char; import org.apache.xerces.util.XMLChar; import org.apache.xerces.xni.NamespaceContext; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; 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.Entity; import org.w3c.dom.EntityReference; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Notation; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import org.w3c.dom.UserDataHandler; import org.w3c.dom.events.Event; import org.w3c.dom.events.EventListener; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSSerializer; import at.ac.tuwien.dsg.scaledom.dom.ScaleDomDocument; /** * The Document interface represents the entire HTML or XML document. Conceptually, it is the root of the document tree, * and provides the primary access to the document's data. * <P> * Since elements, text nodes, comments, processing instructions, etc. cannot exist outside the context of a Document, * the Document interface also contains the factory methods needed to create these objects. The Node objects created * have a ownerDocument attribute which associates them with the Document within whose context they were created. * <p> * The CoreDocumentImpl class only implements the DOM Core. Additional modules are supported by the more complete * DocumentImpl subclass. * <p> * <b>Note:</b> When any node in the document is serialized, the entire document is serialized along with it. * * @xerces.internal * * @author Arnaud Le Hors, IBM * @author Joe Kesselman, IBM * @author Andy Clark, IBM * @author Ralf Pfeiffer, IBM * @version $Id: CoreDocumentImpl.java 950601 2010-06-02 15:38:55Z mrglavas $ * @since PR-DOM-Level-1-19980818. */ @SuppressWarnings("all") public class CoreDocumentImpl extends ParentNode implements Document { /** * TODO:: 1. Change XML11Char method names similar to XMLChar. That will prevent lot of dirty version checking code. * * 2. IMO during cloneNode qname/isXMLName check should not be made. */ // // Constants // /** Serialization version. */ static final long serialVersionUID = 0; // // Data // // document information /** Document type. */ protected DocumentTypeImpl docType; /** Document element. */ protected ElementImpl docElement; /** NodeListCache free list */ transient NodeListCache fFreeNLCache; /** Experimental DOM Level 3 feature: Document encoding */ protected String encoding; /** Experimental DOM Level 3 feature: Document actualEncoding */ protected String actualEncoding; /** Experimental DOM Level 3 feature: Document version */ protected String version; /** Experimental DOM Level 3 feature: Document standalone */ protected boolean standalone; /** Experimental DOM Level 3 feature: documentURI */ protected String fDocumentURI; /** Table for user data attached to this document nodes. */ protected Map userData; // serialized as Hashtable /** Identifiers. */ protected Hashtable identifiers; // DOM Level 3: normalizeDocument transient DOMNormalizer domNormalizer = null; transient DOMConfigurationImpl fConfiguration = null; // support of XPath API transient Object fXPathEvaluator = null; /** Table for quick check of child insertion. */ private final static int[] kidOK; /** * Number of alterations made to this document since its creation. Serves as a "dirty bit" so that live objects such * as NodeList can recognize when an alteration has been made and discard its cached state information. * <p> * Any method that alters the tree structure MUST cause or be accompanied by a call to changed(), to inform it that * any outstanding NodeLists may have to be updated. * <p> * (Required because NodeList is simultaneously "live" and integer- indexed -- a bad decision in the DOM's design.) * <p> * Note that changes which do not affect the tree's structure -- changing the node's name, for example -- do _not_ * have to call changed(). * <p> * Alternative implementation would be to use a cryptographic Digest value rather than a count. This would have the * advantage that "harmless" changes (those producing equal() trees) would not force NodeList to resynchronize. * Disadvantage is that it's slightly more prone to "false negatives", though that's the difference between "wildly * unlikely" and "absurdly unlikely". IF we start maintaining digests, we should consider taking advantage of them. * * Note: This used to be done a node basis, so that we knew what subtree changed. But since only DeepNodeList really * use this today, the gain appears to be really small compared to the cost of having an int on every (parent) node * plus having to walk up the tree all the way to the root to mark the branch as changed everytime a node is * changed. So we now have a single counter global to the document. It means that some objects may flush their cache * more often than necessary, but this makes nodes smaller and only the document needs to be marked as changed. */ protected int changes = 0; // experimental /** Allow grammar access. */ protected boolean allowGrammarAccess; /** Bypass error checking. */ protected boolean errorChecking = true; // Did version change at any point when the document was created ? // this field helps us to optimize when normalizingDocument. protected boolean xmlVersionChanged = false; /** * The following are required for compareDocumentPosition */ // Document number. Documents are ordered across the implementation using // positive integer values. Documents are assigned numbers on demand. private int documentNumber = 0; // Node counter and table. Used to assign numbers to nodes for this // document. Node number values are negative integers. Nodes are // assigned numbers on demand. private int nodeCounter = 0; private Map nodeTable; // serialized as Hashtable private boolean xml11Version = false; // by default 1.0 // // Static initialization // static { kidOK = new int[13]; kidOK[DOCUMENT_NODE] = 1 << ELEMENT_NODE | 1 << PROCESSING_INSTRUCTION_NODE | 1 << COMMENT_NODE | 1 << DOCUMENT_TYPE_NODE; kidOK[DOCUMENT_FRAGMENT_NODE] = kidOK[ENTITY_NODE] = kidOK[ENTITY_REFERENCE_NODE] = kidOK[ELEMENT_NODE] = 1 << ELEMENT_NODE | 1 << PROCESSING_INSTRUCTION_NODE | 1 << COMMENT_NODE | 1 << TEXT_NODE | 1 << CDATA_SECTION_NODE | 1 << ENTITY_REFERENCE_NODE; kidOK[ATTRIBUTE_NODE] = 1 << TEXT_NODE | 1 << ENTITY_REFERENCE_NODE; kidOK[DOCUMENT_TYPE_NODE] = kidOK[PROCESSING_INSTRUCTION_NODE] = kidOK[COMMENT_NODE] = kidOK[TEXT_NODE] = kidOK[CDATA_SECTION_NODE] = kidOK[NOTATION_NODE] = 0; } // static // <ScaleDOM> protected boolean isScaleDomEnabled() { return this instanceof ScaleDomDocument; } // </ScaleDOM> // // Constructors // /** * NON-DOM: Actually creating a Document is outside the DOM's spec, since it has to operate in terms of a particular * implementation. */ public CoreDocumentImpl() { this(false); } /** Constructor. */ public CoreDocumentImpl(boolean grammarAccess) { super(null); ownerDocument = this; allowGrammarAccess = grammarAccess; } /** * For DOM2 support. The createDocument factory method is in DOMImplementation. */ public CoreDocumentImpl(DocumentType doctype) { this(doctype, false); } /** For DOM2 support. */ public CoreDocumentImpl(DocumentType doctype, boolean grammarAccess) { this(grammarAccess); if (doctype != null) { DocumentTypeImpl doctypeImpl; try { doctypeImpl = (DocumentTypeImpl) doctype; } catch (ClassCastException e) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); } doctypeImpl.ownerDocument = this; appendChild(doctype); } } // // Node methods // // even though ownerDocument refers to this in this implementation // the DOM Level 2 spec says it must be null, so make it appear so final public Document getOwnerDocument() { return null; } /** Returns the node type. */ public short getNodeType() { return Node.DOCUMENT_NODE; } /** Returns the node name. */ public String getNodeName() { return "#document"; } /** * Deep-clone a document, including fixing ownerDoc for the cloned children. Note that this requires bypassing the * WRONG_DOCUMENT_ERR protection. I've chosen to implement it by calling importNode which is DOM Level 2. * * @return org.w3c.dom.Node * @param deep boolean, iff true replicate children */ public Node cloneNode(boolean deep) { CoreDocumentImpl newdoc = new CoreDocumentImpl(); callUserDataHandlers(this, newdoc, UserDataHandler.NODE_CLONED); cloneNode(newdoc, deep); return newdoc; } // cloneNode(boolean):Node /** * internal method to share code with subclass **/ protected void cloneNode(CoreDocumentImpl newdoc, boolean deep) { // clone the children by importing them if (needsSyncChildren()) { synchronizeChildren(); } if (deep) { HashMap reversedIdentifiers = null; if (identifiers != null) { // Build a reverse mapping from element to identifier. reversedIdentifiers = new HashMap(); Iterator entries = identifiers.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); Object elementId = entry.getKey(); Object elementNode = entry.getValue(); reversedIdentifiers.put(elementNode, elementId); } } // Copy children into new document. // <ScaleDOM> ChildNode firstChild = null; if(isScaleDomEnabled()) { firstChild = getFirstLoadedChildNode(); } else { firstChild = this.firstChild; } // </ScaleDOM> for (ChildNode kid = firstChild; kid != null; kid = kid.nextSibling) { newdoc.appendChild(newdoc.importNode(kid, true, true, reversedIdentifiers)); } } // experimental newdoc.allowGrammarAccess = allowGrammarAccess; newdoc.errorChecking = errorChecking; } // cloneNode(CoreDocumentImpl,boolean):void /** * Since a Document may contain at most one top-level Element child, and at most one DocumentType declaraction, we * need to subclass our add-children methods to implement this constraint. Since appendChild() is implemented as * insertBefore(,null), altering the latter fixes both. * <p> * While I'm doing so, I've taken advantage of the opportunity to cache documentElement and docType so we don't have * to search for them. * * REVISIT: According to the spec it is not allowed to alter neither the document element nor the document type in * any way */ public Node insertBefore(Node newChild, Node refChild) throws DOMException { // Only one such child permitted int type = newChild.getNodeType(); if (errorChecking) { if (needsSyncChildren()) { synchronizeChildren(); } if ((type == Node.ELEMENT_NODE && docElement != null) || (type == Node.DOCUMENT_TYPE_NODE && docType != null)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null); throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg); } } // Adopt orphan doctypes if (newChild.getOwnerDocument() == null && newChild instanceof DocumentTypeImpl) { ((DocumentTypeImpl) newChild).ownerDocument = this; } super.insertBefore(newChild, refChild); // If insert succeeded, cache the kid appropriately if (type == Node.ELEMENT_NODE) { docElement = (ElementImpl) newChild; } else if (type == Node.DOCUMENT_TYPE_NODE) { docType = (DocumentTypeImpl) newChild; } return newChild; } // insertBefore(Node,Node):Node /** * Since insertBefore caches the docElement (and, currently, docType), removeChild has to know how to undo the cache * * REVISIT: According to the spec it is not allowed to alter neither the document element nor the document type in * any way */ public Node removeChild(Node oldChild) throws DOMException { super.removeChild(oldChild); // If remove succeeded, un-cache the kid appropriately int type = oldChild.getNodeType(); if (type == Node.ELEMENT_NODE) { docElement = null; } else if (type == Node.DOCUMENT_TYPE_NODE) { docType = null; } return oldChild; } // removeChild(Node):Node /** * Since we cache the docElement (and, currently, docType), replaceChild has to update the cache * * REVISIT: According to the spec it is not allowed to alter neither the document element nor the document type in * any way */ public Node replaceChild(Node newChild, Node oldChild) throws DOMException { // Adopt orphan doctypes if (newChild.getOwnerDocument() == null && newChild instanceof DocumentTypeImpl) { ((DocumentTypeImpl) newChild).ownerDocument = this; } if (errorChecking && ((docType != null && oldChild.getNodeType() != Node.DOCUMENT_TYPE_NODE && newChild.getNodeType() == Node.DOCUMENT_TYPE_NODE) || (docElement != null && oldChild.getNodeType() != Node.ELEMENT_NODE && newChild.getNodeType() == Node.ELEMENT_NODE))) { throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, DOMMessageFormatter.formatMessage( DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null)); } super.replaceChild(newChild, oldChild); int type = oldChild.getNodeType(); if (type == Node.ELEMENT_NODE) { docElement = (ElementImpl) newChild; } else if (type == Node.DOCUMENT_TYPE_NODE) { docType = (DocumentTypeImpl) newChild; } return oldChild; } // replaceChild(Node,Node):Node /* * Get Node text content * * @since DOM Level 3 */ public String getTextContent() throws DOMException { return null; } /* * Set Node text content * * @since DOM Level 3 */ public void setTextContent(String textContent) throws DOMException { // no-op } /** * @since DOM Level 3 */ public Object getFeature(String feature, String version) { boolean anyVersion = version == null || version.length() == 0; // if a plus sign "+" is prepended to any feature name, implementations // are considered in which the specified feature may not be directly // castable DOMImplementation.getFeature(feature, version). Without a // plus, only features whose interfaces are directly castable are // considered. if ((feature.equalsIgnoreCase("+XPath")) && (anyVersion || version.equals("3.0"))) { // If an XPathEvaluator was created previously // return it otherwise create a new one. if (fXPathEvaluator != null) { return fXPathEvaluator; } try { Class xpathClass = ObjectFactory.findProviderClass("org.apache.xpath.domapi.XPathEvaluatorImpl", ObjectFactory.findClassLoader(), true); Constructor xpathClassConstr = xpathClass.getConstructor(new Class[] { Document.class }); // Check if the DOM XPath implementation implements // the interface org.w3c.dom.XPathEvaluator Class interfaces[] = xpathClass.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { if (interfaces[i].getName().equals("org.w3c.dom.xpath.XPathEvaluator")) { fXPathEvaluator = xpathClassConstr.newInstance(new Object[] { this }); return fXPathEvaluator; } } return null; } catch (Exception e) { return null; } } return super.getFeature(feature, version); } // // Document methods // // factory methods /** * Factory method; creates an Attribute having this Document as its OwnerDoc. * * @param name The name of the attribute. Note that the attribute's value is _not_ established at the factory; * remember to set it! * * @throws DOMException(INVALID_NAME_ERR) if the attribute name is not acceptable. */ public Attr createAttribute(String name) throws DOMException { if (errorChecking && !isXMLName(name, xml11Version)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null); throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg); } return new AttrImpl(this, name); } // createAttribute(String):Attr /** * Factory method; creates a CDATASection having this Document as its OwnerDoc. * * @param data The initial contents of the CDATA * * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents. (HTML not yet implemented.) */ public CDATASection createCDATASection(String data) throws DOMException { return new CDATASectionImpl(this, data); } /** * Factory method; creates a Comment having this Document as its OwnerDoc. * * @param data The initial contents of the Comment. */ public Comment createComment(String data) { return new CommentImpl(this, data); } /** * Factory method; creates a DocumentFragment having this Document as its OwnerDoc. */ public DocumentFragment createDocumentFragment() { return new DocumentFragmentImpl(this); } /** * Factory method; creates an Element having this Document as its OwnerDoc. * * @param tagName The name of the element type to instantiate. For XML, this is case-sensitive. For HTML, the * tagName parameter may be provided in any case, but it must be mapped to the canonical uppercase form * by the DOM implementation. * * @throws DOMException(INVALID_NAME_ERR) if the tag name is not acceptable. */ public Element createElement(String tagName) throws DOMException { if (errorChecking && !isXMLName(tagName, xml11Version)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null); throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg); } return new ElementImpl(this, tagName); } // createElement(String):Element /** * Factory method; creates an EntityReference having this Document as its OwnerDoc. * * @param name The name of the Entity we wish to refer to * * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents, where nonstandard entities are not permitted. (HTML * not yet implemented.) */ public EntityReference createEntityReference(String name) throws DOMException { if (errorChecking && !isXMLName(name, xml11Version)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null); throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg); } return new EntityReferenceImpl(this, name); } // createEntityReference(String):EntityReference /** * Factory method; creates a ProcessingInstruction having this Document as its OwnerDoc. * * @param target The target "processor channel" * @param data Parameter string to be passed to the target. * * @throws DOMException(INVALID_NAME_ERR) if the target name is not acceptable. * * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents. (HTML not yet implemented.) */ public ProcessingInstruction createProcessingInstruction(String target, String data) throws DOMException { if (errorChecking && !isXMLName(target, xml11Version)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null); throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg); } return new ProcessingInstructionImpl(this, target, data); } // createProcessingInstruction(String,String):ProcessingInstruction /** * Factory method; creates a Text node having this Document as its OwnerDoc. * * @param data The initial contents of the Text. */ public Text createTextNode(String data) { return new TextImpl(this, data); } // other document methods /** * For XML, this provides access to the Document Type Definition. For HTML documents, and XML documents which don't * specify a DTD, it will be null. */ public DocumentType getDoctype() { if (needsSyncChildren()) { synchronizeChildren(); } return docType; } /** * Convenience method, allowing direct access to the child node which is considered the root of the actual document * content. For HTML, where it is legal to have more than one Element at the top level of the document, we pick the * one with the tagName "HTML". For XML there should be only one top-level * * (HTML not yet supported.) */ public Element getDocumentElement() { if (needsSyncChildren()) { synchronizeChildren(); } return docElement; } /** * Return a <em>live</em> collection of all descendent Elements (not just immediate children) having the specified * tag name. * * @param tagname The type of Element we want to gather. "*" will be taken as a wildcard, meaning * "all elements in the document." * * @see DeepNodeListImpl */ public NodeList getElementsByTagName(String tagname) { return new DeepNodeListImpl(this, tagname); } /** * Retrieve information describing the abilities of this particular DOM implementation. Intended to support * applications that may be using DOMs retrieved from several different sources, potentially with different * underlying representations. */ public DOMImplementation getImplementation() { // Currently implemented as a singleton, since it's hardcoded // information anyway. return CoreDOMImplementationImpl.getDOMImplementation(); } // // Public methods // // properties /** * Sets whether the DOM implementation performs error checking upon operations. Turning off error checking only * affects the following DOM checks: * <ul> * <li>Checking strings to make sure that all characters are legal XML characters * <li>Hierarchy checking such as allowed children, checks for cycles, etc. * </ul> * <p> * Turning off error checking does <em>not</em> turn off the following checks: * <ul> * <li>Read only checks * <li>Checks related to DOM events * </ul> */ public void setErrorChecking(boolean check) { errorChecking = check; } /* * DOM Level 3 WD - Experimental. */ public void setStrictErrorChecking(boolean check) { errorChecking = check; } /** * Returns true if the DOM implementation performs error checking. */ public boolean getErrorChecking() { return errorChecking; } /* * DOM Level 3 WD - Experimental. */ public boolean getStrictErrorChecking() { return errorChecking; } /** * DOM Level 3 CR - Experimental. (Was getActualEncoding) * * An attribute specifying the encoding used for this document at the time of the parsing. This is <code>null</code> * when it is not known, such as when the <code>Document</code> was created in memory. * * @since DOM Level 3 */ public String getInputEncoding() { return actualEncoding; } /** * DOM Internal (Was a DOM L3 Core WD public interface method setActualEncoding ) * * An attribute specifying the actual encoding of this document. This is <code>null</code> otherwise. <br> * This attribute represents the property [character encoding scheme] defined in . */ public void setInputEncoding(String value) { actualEncoding = value; } /** * DOM Internal (Was a DOM L3 Core WD public interface method setXMLEncoding ) * * An attribute specifying, as part of the XML declaration, the encoding of this document. This is null when * unspecified. */ public void setXmlEncoding(String value) { encoding = value; } /** * @deprecated This method is internal and only exists for compatibility with older applications. New applications * should never call this method. */ public void setEncoding(String value) { setXmlEncoding(value); } /** * DOM Level 3 WD - Experimental. The encoding of this document (part of XML Declaration) */ public String getXmlEncoding() { return encoding; } /** * @deprecated This method is internal and only exists for compatibility with older applications. New applications * should never call this method. */ public String getEncoding() { return getXmlEncoding(); } /** * DOM Level 3 CR - Experimental. version - An attribute specifying, as part of the XML declaration, the version * number of this document. */ public void setXmlVersion(String value) { if (value.equals("1.0") || value.equals("1.1")) { // we need to change the flag value only -- // when the version set is different than already set. if (!getXmlVersion().equals(value)) { xmlVersionChanged = true; // change the normalization value back to false isNormalized(false); version = value; } } else { // NOT_SUPPORTED_ERR: Raised if the vesion is set to a value that is not supported by // this document // we dont support any other XML version String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null); throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg); } if ((getXmlVersion()).equals("1.1")) { xml11Version = true; } else { xml11Version = false; } } /** * @deprecated This method is internal and only exists for compatibility with older applications. New applications * should never call this method. */ public void setVersion(String value) { setXmlVersion(value); } /** * DOM Level 3 WD - Experimental. The version of this document (part of XML Declaration) */ public String getXmlVersion() { return (version == null) ? "1.0" : version; } /** * @deprecated This method is internal and only exists for compatibility with older applications. New applications * should never call this method. */ public String getVersion() { return getXmlVersion(); } /** * DOM Level 3 CR - Experimental. * * Xmlstandalone - An attribute specifying, as part of the XML declaration, whether this document is standalone * * @exception DOMException NOT_SUPPORTED_ERR: Raised if this document does not support the "XML" feature. * @since DOM Level 3 */ public void setXmlStandalone(boolean value) throws DOMException { standalone = value; } /** * @deprecated This method is internal and only exists for compatibility with older applications. New applications * should never call this method. */ public void setStandalone(boolean value) { setXmlStandalone(value); } /** * DOM Level 3 WD - Experimental. standalone that specifies whether this document is standalone (part of XML * Declaration) */ public boolean getXmlStandalone() { return standalone; } /** * @deprecated This method is internal and only exists for compatibility with older applications. New applications * should never call this method. */ public boolean getStandalone() { return getXmlStandalone(); } /** * DOM Level 3 WD - Experimental. The location of the document or <code>null</code> if undefined. <br> * Beware that when the <code>Document</code> supports the feature "HTML" , the href attribute of the HTML BASE * element takes precedence over this attribute. * * @since DOM Level 3 */ public String getDocumentURI() { return fDocumentURI; } /* * NON-DOM Used by DOM Level 3 WD remameNode. * * Some DOM implementations do not allow nodes to be renamed and require creating new elements. In this case this * method should be overwritten. * * @return true if the given element can be renamed, false, if it must be replaced. */ protected boolean canRenameElements(String newNamespaceURI, String newNodeName, ElementImpl el) { return true; } /** * DOM Level 3 WD - Experimental. Renaming node */ public Node renameNode(Node n, String namespaceURI, String name) throws DOMException { if (errorChecking && n.getOwnerDocument() != this && n != this) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); } switch (n.getNodeType()) { case ELEMENT_NODE: { ElementImpl el = (ElementImpl) n; if (el instanceof ElementNSImpl) { if (canRenameElements(namespaceURI, name, el)) { ((ElementNSImpl) el).rename(namespaceURI, name); // fire user data NODE_RENAMED event callUserDataHandlers(el, null, UserDataHandler.NODE_RENAMED); } else { el = replaceRenameElement(el, namespaceURI, name); } } else { if (namespaceURI == null && canRenameElements(null, name, el)) { el.rename(name); // fire user data NODE_RENAMED event callUserDataHandlers(el, null, UserDataHandler.NODE_RENAMED); } else { el = replaceRenameElement(el, namespaceURI, name); } } // fire ElementNameChanged event renamedElement((Element) n, el); return el; } case ATTRIBUTE_NODE: { AttrImpl at = (AttrImpl) n; // detach attr from element Element el = at.getOwnerElement(); if (el != null) { el.removeAttributeNode(at); } if (n instanceof AttrNSImpl) { ((AttrNSImpl) at).rename(namespaceURI, name); // reattach attr to element if (el != null) { el.setAttributeNodeNS(at); } // fire user data NODE_RENAMED event callUserDataHandlers(at, null, UserDataHandler.NODE_RENAMED); } else { if (namespaceURI == null) { at.rename(name); // reattach attr to element if (el != null) { el.setAttributeNode(at); } // fire user data NODE_RENAMED event callUserDataHandlers(at, null, UserDataHandler.NODE_RENAMED); } else { // we need to create a new object AttrNSImpl nat = (AttrNSImpl) createAttributeNS(namespaceURI, name); // register event listeners on new node copyEventListeners(at, nat); // remove user data from old node Hashtable data = removeUserDataTable(at); // move children to new node Node child = at.getFirstChild(); while (child != null) { at.removeChild(child); nat.appendChild(child); child = at.getFirstChild(); } // attach user data to new node setUserDataTable(nat, data); // and fire user data NODE_RENAMED event callUserDataHandlers(at, nat, UserDataHandler.NODE_RENAMED); // reattach attr to element if (el != null) { el.setAttributeNode(nat); } at = nat; } } // fire AttributeNameChanged event renamedAttrNode((Attr) n, at); return at; } default: { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null); throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg); } } } private ElementImpl replaceRenameElement(ElementImpl el, String namespaceURI, String name) { // we need to create a new object ElementNSImpl nel = (ElementNSImpl) createElementNS(namespaceURI, name); // register event listeners on new node copyEventListeners(el, nel); // remove user data from old node Hashtable data = removeUserDataTable(el); // remove old node from parent if any Node parent = el.getParentNode(); Node nextSib = el.getNextSibling(); if (parent != null) { parent.removeChild(el); } // move children to new node Node child = el.getFirstChild(); while (child != null) { el.removeChild(child); nel.appendChild(child); child = el.getFirstChild(); } // move specified attributes to new node nel.moveSpecifiedAttributes(el); // attach user data to new node setUserDataTable(nel, data); // and fire user data NODE_RENAMED event callUserDataHandlers(el, nel, UserDataHandler.NODE_RENAMED); // insert new node where old one was if (parent != null) { parent.insertBefore(nel, nextSib); } return nel; } /** * DOM Level 3 WD - Experimental Normalize document. */ public void normalizeDocument() { // No need to normalize if already normalized. if (isNormalized() && !isNormalizeDocRequired()) { return; } if (needsSyncChildren()) { synchronizeChildren(); } if (domNormalizer == null) { domNormalizer = new DOMNormalizer(); } if (fConfiguration == null) { fConfiguration = new DOMConfigurationImpl(); } else { fConfiguration.reset(); } domNormalizer.normalizeDocument(this, fConfiguration); isNormalized(true); // set the XMLversion changed value to false -- once we have finished // doing normalization xmlVersionChanged = false; } /** * DOM Level 3 CR - Experimental * * The configuration used when <code>Document.normalizeDocument</code> is invoked. * * @since DOM Level 3 */ public DOMConfiguration getDomConfig() { if (fConfiguration == null) { fConfiguration = new DOMConfigurationImpl(); } return fConfiguration; } /** * Returns the absolute base URI of this node or null if the implementation wasn't able to obtain an absolute URI. * Note: If the URI is malformed, a null is returned. * * @return The absolute base URI of this node or null. * @since DOM Level 3 */ public String getBaseURI() { if (fDocumentURI != null && fDocumentURI.length() != 0) {// attribute value is always empty string try { return new URI(fDocumentURI).toString(); } catch (org.apache.xerces.util.URI.MalformedURIException e) { // REVISIT: what should happen in this case? return null; } } return fDocumentURI; } /** * DOM Level 3 WD - Experimental. */ public void setDocumentURI(String documentURI) { fDocumentURI = documentURI; } // // DOM L3 LS // /** * DOM Level 3 WD - Experimental. Indicates whether the method load should be synchronous or asynchronous. When the * async attribute is set to <code>true</code> the load method returns control to the caller before the document has * completed loading. The default value of this property is <code>false</code>. <br> * Setting the value of this attribute might throw NOT_SUPPORTED_ERR if the implementation doesn't support the mode * the attribute is being set to. Should the DOM spec define the default value of this property? What if * implementing both async and sync IO is impractical in some systems? 2001-09-14. default is <code>false</code> but * we need to check with Mozilla and IE. */ public boolean getAsync() { return false; } /** * DOM Level 3 WD - Experimental. Indicates whether the method load should be synchronous or asynchronous. When the * async attribute is set to <code>true</code> the load method returns control to the caller before the document has * completed loading. The default value of this property is <code>false</code>. <br> * Setting the value of this attribute might throw NOT_SUPPORTED_ERR if the implementation doesn't support the mode * the attribute is being set to. Should the DOM spec define the default value of this property? What if * implementing both async and sync IO is impractical in some systems? 2001-09-14. default is <code>false</code> but * we need to check with Mozilla and IE. */ public void setAsync(boolean async) { if (async) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null); throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg); } } /** * DOM Level 3 WD - Experimental. If the document is currently being loaded as a result of the method * <code>load</code> being invoked the loading and parsing is immediately aborted. The possibly partial result of * parsing the document is discarded and the document is cleared. */ public void abort() { } /** * DOM Level 3 WD - Experimental. * * Replaces the content of the document with the result of parsing the given URI. Invoking this method will either * block the caller or return to the caller immediately depending on the value of the async attribute. Once the * document is fully loaded a "load" event (as defined in [<a * href='http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331'>DOM Level 3 Events</a>] , except that the * <code>Event.targetNode</code> will be the document, not an element) will be dispatched on the document. If an * error occurs, an implementation dependent "error" event will be dispatched on the document. If this method is * called on a document that is currently loading, the current load is interrupted and the new URI load is * initiated. <br> * When invoking this method the parameters used in the <code>DOMParser</code> interface are assumed to have their * default values with the exception that the parameters <code>"entities"</code> , * <code>"normalize-characters"</code>, <code>"check-character-normalization"</code> are set to <code>"false"</code> * . <br> * The result of a call to this method is the same the result of a call to <code>DOMParser.parseWithContext</code> * with an input stream referencing the URI that was passed to this call, the document as the context node, and the * action <code>ACTION_REPLACE_CHILDREN</code>. * * @param uri The URI reference for the XML file to be loaded. If this is a relative URI, the base URI used by the * implementation is implementation dependent. * @return If async is set to <code>true</code> <code>load</code> returns <code>true</code> if the document load was * successfully initiated. If an error occurred when initiating the document load, <code>load</code> returns * <code>false</code>.If async is set to <code>false</code> <code>load</code> returns <code>true</code> if * the document was successfully loaded and parsed. If an error occurred when either loading or parsing the * URI, <code>load</code> returns <code>false</code>. */ public boolean load(String uri) { return false; } /** * DOM Level 3 WD - Experimental. Replace the content of the document with the result of parsing the input string, * this method is always synchronous. * * @param source A string containing an XML document. * @return <code>true</code> if parsing the input string succeeded without errors, otherwise <code>false</code>. */ public boolean loadXML(String source) { return false; } /** * DOM Level 3 WD - Experimental. Save the document or the given node and all its descendants to a string (i.e. * serialize the document or node). <br> * The parameters used in the <code>LSSerializer</code> interface are assumed to have their default values when * invoking this method. <br> * The result of a call to this method is the same the result of a call to <code>LSSerializer.writeToString</code> * with the document as the node to write. * * @param node Specifies what to serialize, if this parameter is <code>null</code> the whole document is serialized, * if it's non-null the given node is serialized. * @return The serialized document or <code>null</code> in case an error occurred. * @exception DOMException WRONG_DOCUMENT_ERR: Raised if the node passed in as the node parameter is from an other * document. */ public String saveXML(Node node) throws DOMException { if (errorChecking && node != null && this != node.getOwnerDocument()) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); } DOMImplementationLS domImplLS = (DOMImplementationLS) DOMImplementationImpl.getDOMImplementation(); LSSerializer xmlWriter = domImplLS.createLSSerializer(); if (node == null) { node = this; } return xmlWriter.writeToString(node); } /** * Sets whether the DOM implementation generates mutation events upon operations. */ void setMutationEvents(boolean set) { // does nothing by default - overidden in subclass } /** * Returns true if the DOM implementation generates mutation events. */ boolean getMutationEvents() { // does nothing by default - overriden in subclass return false; } // non-DOM factory methods /** * NON-DOM Factory method; creates a DocumentType having this Document as its OwnerDoc. (REC-DOM-Level-1-19981001 * left the process of building DTD information unspecified.) * * @param qualifiedName * @param publicID * @param systemID * * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents, where DTDs are not permitted. (HTML not yet * implemented.) */ public DocumentType createDocumentType(String qualifiedName, String publicID, String systemID) throws DOMException { return new DocumentTypeImpl(this, qualifiedName, publicID, systemID); } // createDocumentType(String):DocumentType /** * NON-DOM Factory method; creates an Entity having this Document as its OwnerDoc. (REC-DOM-Level-1-19981001 left * the process of building DTD information unspecified.) * * @param name The name of the Entity we wish to provide a value for. * * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents, where nonstandard entities are not permitted. (HTML * not yet implemented.) */ public Entity createEntity(String name) throws DOMException { if (errorChecking && !isXMLName(name, xml11Version)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null); throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg); } return new EntityImpl(this, name); } // createEntity(String):Entity /** * NON-DOM Factory method; creates a Notation having this Document as its OwnerDoc. (REC-DOM-Level-1-19981001 left * the process of building DTD information unspecified.) * * @param name The name of the Notation we wish to describe * * @throws DOMException(NOT_SUPPORTED_ERR) for HTML documents, where notations are not permitted. (HTML not yet * implemented.) */ public Notation createNotation(String name) throws DOMException { if (errorChecking && !isXMLName(name, xml11Version)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null); throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg); } return new NotationImpl(this, name); } // createNotation(String):Notation /** * NON-DOM Factory method: creates an element definition. Element definitions hold default attribute values. */ public ElementDefinitionImpl createElementDefinition(String name) throws DOMException { if (errorChecking && !isXMLName(name, xml11Version)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null); throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg); } return new ElementDefinitionImpl(this, name); } // createElementDefinition(String):ElementDefinitionImpl // other non-DOM methods /** * NON-DOM: Get the number associated with this document. Used to order documents in the implementation. */ protected int getNodeNumber() { if (documentNumber == 0) { CoreDOMImplementationImpl cd = (CoreDOMImplementationImpl) CoreDOMImplementationImpl.getDOMImplementation(); documentNumber = cd.assignDocumentNumber(); } return documentNumber; } /** * NON-DOM: Get a number associated with a node created with respect to this document. Needed for * compareDocumentPosition when nodes are disconnected. This is only used on demand. */ protected int getNodeNumber(Node node) { // Check if the node is already in the hash // If so, retrieve the node number // If not, assign a number to the node // Node numbers are negative, from -1 to -n int num; if (nodeTable == null) { nodeTable = new WeakHashMap(); num = --nodeCounter; nodeTable.put(node, new Integer(num)); } else { Integer n = (Integer) nodeTable.get(node); if (n == null) { num = --nodeCounter; nodeTable.put(node, new Integer(num)); } else num = n.intValue(); } return num; } /** * Copies a node from another document to this document. The new nodes are created using this document's factory * methods and are populated with the data from the source's accessor methods defined by the DOM interfaces. Its * behavior is otherwise similar to that of cloneNode. * <p> * According to the DOM specifications, document nodes cannot be imported and a NOT_SUPPORTED_ERR exception is * thrown if attempted. */ public Node importNode(Node source, boolean deep) throws DOMException { return importNode(source, deep, false, null); } // importNode(Node,boolean):Node /** * Overloaded implementation of DOM's importNode method. This method provides the core functionality for the public * importNode and cloneNode methods. * * The reversedIdentifiers parameter is provided for cloneNode to preserve the document's identifiers. The HashMap * has Elements as the keys and their identifiers as the values. When an element is being imported, a check is done * for an associated identifier. If one exists, the identifier is registered with the new, imported element. If * reversedIdentifiers is null, the parameter is not applied. */ private Node importNode(Node source, boolean deep, boolean cloningDoc, HashMap reversedIdentifiers) throws DOMException { Node newnode = null; Hashtable userData = null; // Sigh. This doesn't work; too many nodes have private data that // would have to be manually tweaked. May be able to add local // shortcuts to each nodetype. Consider ????? // if(source instanceof NodeImpl && // !(source instanceof DocumentImpl)) // { // // Can't clone DocumentImpl since it invokes us... // newnode=(NodeImpl)source.cloneNode(false); // newnode.ownerDocument=this; // } // else if (source instanceof NodeImpl) userData = ((NodeImpl) source).getUserDataRecord(); int type = source.getNodeType(); switch (type) { case ELEMENT_NODE: { Element newElement; boolean domLevel20 = source.getOwnerDocument().getImplementation().hasFeature("XML", "2.0"); // Create element according to namespace support/qualification. if (domLevel20 == false || source.getLocalName() == null) newElement = createElement(source.getNodeName()); else newElement = createElementNS(source.getNamespaceURI(), source.getNodeName()); // Copy element's attributes, if any. NamedNodeMap sourceAttrs = source.getAttributes(); if (sourceAttrs != null) { int length = sourceAttrs.getLength(); for (int index = 0; index < length; index++) { Attr attr = (Attr) sourceAttrs.item(index); // NOTE: this methods is used for both importingNode // and cloning the document node. In case of the // clonning default attributes should be copied. // But for importNode defaults should be ignored. if (attr.getSpecified() || cloningDoc) { Attr newAttr = (Attr) importNode(attr, true, cloningDoc, reversedIdentifiers); // Attach attribute according to namespace // support/qualification. if (domLevel20 == false || attr.getLocalName() == null) newElement.setAttributeNode(newAttr); else newElement.setAttributeNodeNS(newAttr); } } } // Register element identifier. if (reversedIdentifiers != null) { // Does element have an associated identifier? Object elementId = reversedIdentifiers.get(source); if (elementId != null) { if (identifiers == null) identifiers = new Hashtable(); identifiers.put(elementId, newElement); } } newnode = newElement; break; } case ATTRIBUTE_NODE: { if (source.getOwnerDocument().getImplementation().hasFeature("XML", "2.0")) { if (source.getLocalName() == null) { newnode = createAttribute(source.getNodeName()); } else { newnode = createAttributeNS(source.getNamespaceURI(), source.getNodeName()); } } else { newnode = createAttribute(source.getNodeName()); } // if source is an AttrImpl from this very same implementation // avoid creating the child nodes if possible if (source instanceof AttrImpl) { AttrImpl attr = (AttrImpl) source; if (attr.hasStringValue()) { AttrImpl newattr = (AttrImpl) newnode; newattr.setValue(attr.getValue()); deep = false; } else { deep = true; } } else { // According to the DOM spec the kids carry the value. // However, there are non compliant implementations out // there that fail to do so. To avoid ending up with no // value at all, in this case we simply copy the text value // directly. if (source.getFirstChild() == null) { newnode.setNodeValue(source.getNodeValue()); deep = false; } else { deep = true; } } break; } case TEXT_NODE: { newnode = createTextNode(source.getNodeValue()); break; } case CDATA_SECTION_NODE: { newnode = createCDATASection(source.getNodeValue()); break; } case ENTITY_REFERENCE_NODE: { newnode = createEntityReference(source.getNodeName()); // the subtree is created according to this doc by the method // above, so avoid carrying over original subtree deep = false; break; } case ENTITY_NODE: { Entity srcentity = (Entity) source; EntityImpl newentity = (EntityImpl) createEntity(source.getNodeName()); newentity.setPublicId(srcentity.getPublicId()); newentity.setSystemId(srcentity.getSystemId()); newentity.setNotationName(srcentity.getNotationName()); // Kids carry additional value, // allow deep import temporarily newentity.isReadOnly(false); newnode = newentity; break; } case PROCESSING_INSTRUCTION_NODE: { newnode = createProcessingInstruction(source.getNodeName(), source.getNodeValue()); break; } case COMMENT_NODE: { newnode = createComment(source.getNodeValue()); break; } case DOCUMENT_TYPE_NODE: { // unless this is used as part of cloning a Document // forbid it for the sake of being compliant to the DOM spec if (!cloningDoc) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null); throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg); } DocumentType srcdoctype = (DocumentType) source; DocumentTypeImpl newdoctype = (DocumentTypeImpl) createDocumentType(srcdoctype.getNodeName(), srcdoctype.getPublicId(), srcdoctype.getSystemId()); newdoctype.setInternalSubset(srcdoctype.getInternalSubset()); // Values are on NamedNodeMaps NamedNodeMap smap = srcdoctype.getEntities(); NamedNodeMap tmap = newdoctype.getEntities(); if (smap != null) { for (int i = 0; i < smap.getLength(); i++) { tmap.setNamedItem(importNode(smap.item(i), true, true, reversedIdentifiers)); } } smap = srcdoctype.getNotations(); tmap = newdoctype.getNotations(); if (smap != null) { for (int i = 0; i < smap.getLength(); i++) { tmap.setNamedItem(importNode(smap.item(i), true, true, reversedIdentifiers)); } } // NOTE: At this time, the DOM definition of DocumentType // doesn't cover Elements and their Attributes. domimpl's // extentions in that area will not be preserved, even if // copying from domimpl to domimpl. We could special-case // that here. Arguably we should. Consider. ????? newnode = newdoctype; break; } case DOCUMENT_FRAGMENT_NODE: { newnode = createDocumentFragment(); // No name, kids carry value break; } case NOTATION_NODE: { Notation srcnotation = (Notation) source; NotationImpl newnotation = (NotationImpl) createNotation(source.getNodeName()); newnotation.setPublicId(srcnotation.getPublicId()); newnotation.setSystemId(srcnotation.getSystemId()); // Kids carry additional value newnode = newnotation; // No name, no value break; } case DOCUMENT_NODE: // Can't import document nodes default: { // Unknown node type String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null); throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg); } } if (userData != null) callUserDataHandlers(source, newnode, UserDataHandler.NODE_IMPORTED, userData); // If deep, replicate and attach the kids. if (deep) { for (Node srckid = source.getFirstChild(); srckid != null; srckid = srckid.getNextSibling()) { newnode.appendChild(importNode(srckid, true, cloningDoc, reversedIdentifiers)); } } if (newnode.getNodeType() == Node.ENTITY_NODE) { ((NodeImpl) newnode).setReadOnly(true, true); } return newnode; } // importNode(Node,boolean,boolean,Hashtable):Node /** * DOM Level 3 WD - Experimental Change the node's ownerDocument, and its subtree, to this Document * * @param source The node to adopt. * @see #importNode **/ public Node adoptNode(Node source) { NodeImpl node; Hashtable userData = null; try { node = (NodeImpl) source; } catch (ClassCastException e) { // source node comes from a different DOMImplementation return null; } // Return null if the source is null if (source == null) { return null; } else if (source != null && source.getOwnerDocument() != null) { DOMImplementation thisImpl = this.getImplementation(); DOMImplementation otherImpl = source.getOwnerDocument().getImplementation(); // when the source node comes from a different implementation. if (thisImpl != otherImpl) { // Adopting from a deferred DOM to a non-deferred DOM if (thisImpl instanceof org.apache.xerces.dom.DOMImplementationImpl && otherImpl instanceof org.apache.xerces.dom.DeferredDOMImplementationImpl) { // traverse the DOM and expand deferred nodes and then allow adoption undeferChildren(node); } else if (thisImpl instanceof org.apache.xerces.dom.DeferredDOMImplementationImpl && otherImpl instanceof org.apache.xerces.dom.DOMImplementationImpl) { // Adopting from a non-deferred DOM into a deferred DOM, this should be okay } else { // Adopting between two dissimilar DOMs is not allowed return null; } } // Adopting from a deferred DOM into another deferred DOM else if (otherImpl instanceof org.apache.xerces.dom.DeferredDOMImplementationImpl) { // traverse the DOM and expand deferred nodes and then allow adoption undeferChildren(node); } } switch (node.getNodeType()) { case ATTRIBUTE_NODE: { AttrImpl attr = (AttrImpl) node; // remove node from wherever it is if (attr.getOwnerElement() != null) { // 1. owner element attribute is set to null attr.getOwnerElement().removeAttributeNode(attr); } // 2. specified flag is set to true attr.isSpecified(true); userData = node.getUserDataRecord(); // 3. change ownership attr.setOwnerDocument(this); if (userData != null) setUserDataTable(node, userData); break; } // entity, notation nodes are read only nodes.. so they can't be adopted. // runtime will fall through to NOTATION_NODE case ENTITY_NODE: case NOTATION_NODE: { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); } // document, documentype nodes can't be adopted. // runtime will fall through to DocumentTypeNode case DOCUMENT_NODE: case DOCUMENT_TYPE_NODE: { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null); throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg); } case ENTITY_REFERENCE_NODE: { userData = node.getUserDataRecord(); // remove node from wherever it is Node parent = node.getParentNode(); if (parent != null) { parent.removeChild(source); } // discard its replacement value Node child; while ((child = node.getFirstChild()) != null) { node.removeChild(child); } // change ownership node.setOwnerDocument(this); if (userData != null) setUserDataTable(node, userData); // set its new replacement value if any if (docType == null) { break; } NamedNodeMap entities = docType.getEntities(); Node entityNode = entities.getNamedItem(node.getNodeName()); if (entityNode == null) { break; } for (child = entityNode.getFirstChild(); child != null; child = child.getNextSibling()) { Node childClone = child.cloneNode(true); node.appendChild(childClone); } break; } case ELEMENT_NODE: { userData = node.getUserDataRecord(); // remove node from wherever it is Node parent = node.getParentNode(); if (parent != null) { parent.removeChild(source); } // change ownership node.setOwnerDocument(this); if (userData != null) setUserDataTable(node, userData); // reconcile default attributes ((ElementImpl) node).reconcileDefaultAttributes(); break; } default: { userData = node.getUserDataRecord(); // remove node from wherever it is Node parent = node.getParentNode(); if (parent != null) { parent.removeChild(source); } // change ownership node.setOwnerDocument(this); if (userData != null) setUserDataTable(node, userData); } } // DOM L3 Core CR // http://www.w3.org/TR/2003/CR-DOM-Level-3-Core-20031107/core.html#UserDataHandler-ADOPTED if (userData != null) callUserDataHandlers(source, null, UserDataHandler.NODE_ADOPTED, userData); return node; } /** * Traverses the DOM Tree and expands deferred nodes and their children. * */ protected void undeferChildren(Node node) { Node top = node; while (null != node) { if (((NodeImpl) node).needsSyncData()) { ((NodeImpl) node).synchronizeData(); } NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { int length = attributes.getLength(); for (int i = 0; i < length; ++i) { undeferChildren(attributes.item(i)); } } Node nextNode = null; nextNode = node.getFirstChild(); while (null == nextNode) { if (top.equals(node)) break; nextNode = node.getNextSibling(); if (null == nextNode) { node = node.getParentNode(); if ((null == node) || (top.equals(node))) { nextNode = null; break; } } } node = nextNode; } } // identifier maintenence /** * Introduced in DOM Level 2 Returns the Element whose ID is given by elementId. If no such element exists, returns * null. Behavior is not defined if more than one element has this ID. * <p> * Note: The DOM implementation must have information that says which attributes are of type ID. Attributes with the * name "ID" are not of type ID unless so defined. Implementations that do not know whether attributes are of type * ID or not are expected to return null. * * @see #getIdentifier */ public Element getElementById(String elementId) { return getIdentifier(elementId); } /** * Remove all identifiers from the ID table */ protected final void clearIdentifiers() { if (identifiers != null) { identifiers.clear(); } } /** * Registers an identifier name with a specified element node. If the identifier is already registered, the new * element node replaces the previous node. If the specified element node is null, removeIdentifier() is called. * * @see #getIdentifier * @see #removeIdentifier */ public void putIdentifier(String idName, Element element) { if (element == null) { removeIdentifier(idName); return; } if (needsSyncData()) { synchronizeData(); } if (identifiers == null) { identifiers = new Hashtable(); } identifiers.put(idName, element); } // putIdentifier(String,Element) /** * Returns a previously registered element with the specified identifier name, or null if no element is registered. * * @see #putIdentifier * @see #removeIdentifier */ public Element getIdentifier(String idName) { if (needsSyncData()) { synchronizeData(); } if (identifiers == null) { return null; } Element elem = (Element) identifiers.get(idName); if (elem != null) { // check that the element is in the tree Node parent = elem.getParentNode(); while (parent != null) { if (parent == this) { return elem; } parent = parent.getParentNode(); } } return null; } // getIdentifier(String):Element /** * Removes a previously registered element with the specified identifier name. * * @see #putIdentifier * @see #getIdentifier */ public void removeIdentifier(String idName) { if (needsSyncData()) { synchronizeData(); } if (identifiers == null) { return; } identifiers.remove(idName); } // removeIdentifier(String) /** Returns an enumeration registered of identifier names. */ public Enumeration getIdentifiers() { if (needsSyncData()) { synchronizeData(); } if (identifiers == null) { identifiers = new Hashtable(); } return identifiers.keys(); } // getIdentifiers():Enumeration // // DOM2: Namespace methods // /** * Introduced in DOM Level 2. * <p> * Creates an element of the given qualified name and namespace URI. If the given namespaceURI is null or an empty * string and the qualifiedName has a prefix that is "xml", the created element is bound to the predefined namespace * "http://www.w3.org/XML/1998/namespace" [Namespaces]. * * @param namespaceURI The namespace URI of the element to create. * @param qualifiedName The qualified name of the element type to instantiate. * @return Element A new Element object with the following attributes: * @throws DOMException INVALID_CHARACTER_ERR: Raised if the specified name contains an invalid character. * @throws DOMException NAMESPACE_ERR: Raised if the qualifiedName has a prefix that is "xml" and the namespaceURI * is neither null nor an empty string nor "http://www.w3.org/XML/1998/namespace", or if the * qualifiedName has a prefix different from "xml" and the namespaceURI is null or an empty string. * @since WD-DOM-Level-2-19990923 */ public Element createElementNS(String namespaceURI, String qualifiedName) throws DOMException { return new ElementNSImpl(this, namespaceURI, qualifiedName); } /** * NON-DOM: a factory method used by the Xerces DOM parser to create an element. * * @param namespaceURI The namespace URI of the element to create. * @param qualifiedName The qualified name of the element type to instantiate. * @param localpart The local name of the attribute to instantiate. * * @return Element A new Element object with the following attributes: * @exception DOMException INVALID_CHARACTER_ERR: Raised if the specified name contains an invalid character. */ public Element createElementNS(String namespaceURI, String qualifiedName, String localpart) throws DOMException { return new ElementNSImpl(this, namespaceURI, qualifiedName, localpart); } /** * Introduced in DOM Level 2. * <p> * Creates an attribute of the given qualified name and namespace URI. If the given namespaceURI is null or an empty * string and the qualifiedName has a prefix that is "xml", the created element is bound to the predefined namespace * "http://www.w3.org/XML/1998/namespace" [Namespaces]. * * @param namespaceURI The namespace URI of the attribute to create. When it is null or an empty string, this method * behaves like createAttribute. * @param qualifiedName The qualified name of the attribute to instantiate. * @return Attr A new Attr object. * @throws DOMException INVALID_CHARACTER_ERR: Raised if the specified name contains an invalid character. * @since WD-DOM-Level-2-19990923 */ public Attr createAttributeNS(String namespaceURI, String qualifiedName) throws DOMException { return new AttrNSImpl(this, namespaceURI, qualifiedName); } /** * NON-DOM: a factory method used by the Xerces DOM parser to create an element. * * @param namespaceURI The namespace URI of the attribute to create. When it is null or an empty string, this method * behaves like createAttribute. * @param qualifiedName The qualified name of the attribute to instantiate. * @param localpart The local name of the attribute to instantiate. * * @return Attr A new Attr object. * @throws DOMException INVALID_CHARACTER_ERR: Raised if the specified name contains an invalid character. */ public Attr createAttributeNS(String namespaceURI, String qualifiedName, String localpart) throws DOMException { return new AttrNSImpl(this, namespaceURI, qualifiedName, localpart); } /** * Introduced in DOM Level 2. * <p> * Returns a NodeList of all the Elements with a given local name and namespace URI in the order in which they would * be encountered in a preorder traversal of the Document tree. * * @param namespaceURI The namespace URI of the elements to match on. The special value "*" matches all namespaces. * When it is null or an empty string, this method behaves like getElementsByTagName. * @param localName The local name of the elements to match on. The special value "*" matches all local names. * @return NodeList A new NodeList object containing all the matched Elements. * @since WD-DOM-Level-2-19990923 */ public NodeList getElementsByTagNameNS(String namespaceURI, String localName) { return new DeepNodeListImpl(this, namespaceURI, localName); } // // Object methods // /** Clone. */ public Object clone() throws CloneNotSupportedException { CoreDocumentImpl newdoc = (CoreDocumentImpl) super.clone(); newdoc.docType = null; newdoc.docElement = null; return newdoc; } // // Public static methods // /** * Check the string against XML's definition of acceptable names for elements and attributes and so on using the * XMLCharacterProperties utility class */ public static final boolean isXMLName(String s, boolean xml11Version) { if (s == null) { return false; } if (!xml11Version) return XMLChar.isValidName(s); else return XML11Char.isXML11ValidName(s); } // isXMLName(String):boolean /** * Checks if the given qualified name is legal with respect to the version of XML to which this document must * conform. * * @param prefix prefix of qualified name * @param local local part of qualified name */ public static final boolean isValidQName(String prefix, String local, boolean xml11Version) { // check that both prefix and local part match NCName if (local == null) return false; boolean validNCName = false; if (!xml11Version) { validNCName = (prefix == null || XMLChar.isValidNCName(prefix)) && XMLChar.isValidNCName(local); } else { validNCName = (prefix == null || XML11Char.isXML11ValidNCName(prefix)) && XML11Char.isXML11ValidNCName(local); } return validNCName; } // // Protected methods // /** * Uses the kidOK lookup table to check whether the proposed tree structure is legal. */ protected boolean isKidOK(Node parent, Node child) { if (allowGrammarAccess && parent.getNodeType() == Node.DOCUMENT_TYPE_NODE) { return child.getNodeType() == Node.ELEMENT_NODE; } return 0 != (kidOK[parent.getNodeType()] & 1 << child.getNodeType()); } /** * Denotes that this node has changed. */ protected void changed() { changes++; } /** * Returns the number of changes to this node. */ protected int changes() { return changes; } // NodeListCache pool /** * Returns a NodeListCache for the given node. */ NodeListCache getNodeListCache(ParentNode owner) { if (fFreeNLCache == null) { return new NodeListCache(owner); } NodeListCache c = fFreeNLCache; fFreeNLCache = fFreeNLCache.next; c.fChild = null; c.fChildIndex = -1; c.fLength = -1; // revoke previous ownership if (c.fOwner != null) { c.fOwner.fNodeListCache = null; } c.fOwner = owner; // c.next = null; not necessary, except for confused people... return c; } /** * Puts the given NodeListCache in the free list. Note: The owner node can keep using it until we reuse it */ void freeNodeListCache(NodeListCache c) { c.next = fFreeNLCache; fFreeNLCache = c; } /** * Associate an object to a key on this node. The object can later be retrieved from this node by calling * <code>getUserData</code> with the same key. * * @param n The node to associate the object to. * @param key The key to associate the object to. * @param data The object to associate to the given key, or <code>null</code> to remove any existing association to * that key. * @param handler The handler to associate to that key, or <code>null</code>. * @return Returns the <code>DOMObject</code> previously associated to the given key on this node, or * <code>null</code> if there was none. * @since DOM Level 3 * * REVISIT: we could use a free list of UserDataRecord here */ public Object setUserData(Node n, String key, Object data, UserDataHandler handler) { if (data == null) { if (userData != null) { Hashtable t = (Hashtable) userData.get(n); if (t != null) { Object o = t.remove(key); if (o != null) { UserDataRecord r = (UserDataRecord) o; return r.fData; } } } return null; } else { Hashtable t; if (userData == null) { userData = new WeakHashMap(); t = new Hashtable(); userData.put(n, t); } else { t = (Hashtable) userData.get(n); if (t == null) { t = new Hashtable(); userData.put(n, t); } } Object o = t.put(key, new UserDataRecord(data, handler)); if (o != null) { UserDataRecord r = (UserDataRecord) o; return r.fData; } return null; } } /** * Retrieves the object associated to a key on a this node. The object must first have been set to this node by * calling <code>setUserData</code> with the same key. * * @param n The node the object is associated to. * @param key The key the object is associated to. * @return Returns the <code>DOMObject</code> associated to the given key on this node, or <code>null</code> if * there was none. * @since DOM Level 3 */ public Object getUserData(Node n, String key) { if (userData == null) { return null; } Hashtable t = (Hashtable) userData.get(n); if (t == null) { return null; } Object o = t.get(key); if (o != null) { UserDataRecord r = (UserDataRecord) o; return r.fData; } return null; } protected Hashtable getUserDataRecord(Node n) { if (userData == null) { return null; } Hashtable t = (Hashtable) userData.get(n); if (t == null) { return null; } return t; } /** * Remove user data table for the given node. * * @param n The node this operation applies to. * @return The removed table. */ Hashtable removeUserDataTable(Node n) { if (userData == null) { return null; } return (Hashtable) userData.get(n); } /** * Set user data table for the given node. * * @param n The node this operation applies to. * @param data The user data table. */ void setUserDataTable(Node n, Hashtable data) { if (userData == null) { userData = new WeakHashMap(); } if (data != null) { userData.put(n, data); } } /** * Call user data handlers when a node is deleted (finalized) * * @param n The node this operation applies to. * @param c The copy node or null. * @param operation The operation - import, clone, or delete. */ protected void callUserDataHandlers(Node n, Node c, short operation) { if (userData == null) { return; } // Hashtable t = (Hashtable) userData.get(n); if (n instanceof NodeImpl) { Hashtable t = ((NodeImpl) n).getUserDataRecord(); if (t == null || t.isEmpty()) { return; } callUserDataHandlers(n, c, operation, t); } } /** * Call user data handlers when a node is deleted (finalized) * * @param n The node this operation applies to. * @param c The copy node or null. * @param operation The operation - import, clone, or delete. * @param handlers Data associated with n. */ void callUserDataHandlers(Node n, Node c, short operation, Hashtable userData) { if (userData == null || userData.isEmpty()) { return; } Iterator entries = userData.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); String key = (String) entry.getKey(); UserDataRecord r = (UserDataRecord) entry.getValue(); if (r.fHandler != null) { r.fHandler.handle(operation, key, r.fData, n, c); } } } /** * Call user data handlers to let them know the nodes they are related to are being deleted. The alternative would * be to do that on Node but because the nodes are used as the keys we have a reference to them that prevents them * from being gc'ed until the document is. At the same time, doing it here has the advantage of avoiding a * finalize() method on Node, which would affect all nodes and not just the ones that have a user data. */ // Temporarily comment out this method, because // 1. It seems that finalizers are not guaranteed to be called, so the // functionality is not implemented. // 2. It affects the performance greatly in multi-thread environment. // -SG /* * public void finalize() { if (userData == null) { return; } Enumeration nodes = userData.keys(); while * (nodes.hasMoreElements()) { Object node = nodes.nextElement(); Hashtable t = (Hashtable) userData.get(node); if * (t != null && !t.isEmpty()) { Enumeration keys = t.keys(); while (keys.hasMoreElements()) { String key = (String) * keys.nextElement(); UserDataRecord r = (UserDataRecord) t.get(key); if (r.fHandler != null) { * r.fHandler.handle(UserDataHandler.NODE_DELETED, key, r.fData, null, null); } } } } } */ protected final void checkNamespaceWF(String qname, int colon1, int colon2) { if (!errorChecking) { return; } // it is an error for NCName to have more than one ':' // check if it is valid QName [Namespace in XML production 6] // :camera , nikon:camera:minolta, camera: if (colon1 == 0 || colon1 == qname.length() - 1 || colon2 != colon1) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NAMESPACE_ERR", null); throw new DOMException(DOMException.NAMESPACE_ERR, msg); } } protected final void checkDOMNSErr(String prefix, String namespace) { if (errorChecking) { if (namespace == null) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NAMESPACE_ERR", null); throw new DOMException(DOMException.NAMESPACE_ERR, msg); } else if (prefix.equals("xml") && !namespace.equals(NamespaceContext.XML_URI)) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NAMESPACE_ERR", null); throw new DOMException(DOMException.NAMESPACE_ERR, msg); } else if (prefix.equals("xmlns") && !namespace.equals(NamespaceContext.XMLNS_URI) || (!prefix.equals("xmlns") && namespace.equals(NamespaceContext.XMLNS_URI))) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NAMESPACE_ERR", null); throw new DOMException(DOMException.NAMESPACE_ERR, msg); } } } /** * Checks if the given qualified name is legal with respect to the version of XML to which this document must * conform. * * @param prefix prefix of qualified name * @param local local part of qualified name */ protected final void checkQName(String prefix, String local) { if (!errorChecking) { return; } // check that both prefix and local part match NCName boolean validNCName = false; if (!xml11Version) { validNCName = (prefix == null || XMLChar.isValidNCName(prefix)) && XMLChar.isValidNCName(local); } else { validNCName = (prefix == null || XML11Char.isXML11ValidNCName(prefix)) && XML11Char.isXML11ValidNCName(local); } if (!validNCName) { // REVISIT: add qname parameter to the message String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_CHARACTER_ERR", null); throw new DOMException(DOMException.INVALID_CHARACTER_ERR, msg); } } /** * We could have more xml versions in future , but for now we could do with this to handle XML 1.0 and 1.1 */ boolean isXML11Version() { return xml11Version; } boolean isNormalizeDocRequired() { // REVISIT: Implement to optimize when normalization // is required return true; } // we should be checking the (elements, attribute, entity etc.) names only when // version of the document is changed. boolean isXMLVersionChanged() { return xmlVersionChanged; } /** * NON-DOM: kept for backward compatibility Store user data related to a given node This is a place where we could * use weak references! Indeed, the node here won't be GC'ed as long as some user data is attached to it, since the * userData table will have a reference to the node. */ protected void setUserData(NodeImpl n, Object data) { setUserData(n, "XERCES1DOMUSERDATA", data, null); } /** * NON-DOM: kept for backward compatibility Retreive user data related to a given node */ protected Object getUserData(NodeImpl n) { return getUserData(n, "XERCES1DOMUSERDATA"); } // Event related methods overidden in subclass protected void addEventListener(NodeImpl node, String type, EventListener listener, boolean useCapture) { // does nothing by default - overidden in subclass } protected void removeEventListener(NodeImpl node, String type, EventListener listener, boolean useCapture) { // does nothing by default - overidden in subclass } protected void copyEventListeners(NodeImpl src, NodeImpl tgt) { // does nothing by default - overidden in subclass } protected boolean dispatchEvent(NodeImpl node, Event event) { // does nothing by default - overidden in subclass return false; } // Notification methods overidden in subclasses /** * A method to be called when some text was changed in a text node, so that live objects can be notified. */ void replacedText(CharacterDataImpl node) { } /** * A method to be called when some text was deleted from a text node, so that live objects can be notified. */ void deletedText(CharacterDataImpl node, int offset, int count) { } /** * A method to be called when some text was inserted into a text node, so that live objects can be notified. */ void insertedText(CharacterDataImpl node, int offset, int count) { } /** * A method to be called when a character data node is about to be modified */ void modifyingCharacterData(NodeImpl node, boolean replace) { } /** * A method to be called when a character data node has been modified */ void modifiedCharacterData(NodeImpl node, String oldvalue, String value, boolean replace) { } /** * A method to be called when a node is about to be inserted in the tree. */ void insertingNode(NodeImpl node, boolean replace) { } /** * A method to be called when a node has been inserted in the tree. */ void insertedNode(NodeImpl node, NodeImpl newInternal, boolean replace) { } /** * A method to be called when a node is about to be removed from the tree. */ void removingNode(NodeImpl node, NodeImpl oldChild, boolean replace) { } /** * A method to be called when a node has been removed from the tree. */ void removedNode(NodeImpl node, boolean replace) { } /** * A method to be called when a node is about to be replaced in the tree. */ void replacingNode(NodeImpl node) { } /** * A method to be called when a node has been replaced in the tree. */ void replacedNode(NodeImpl node) { } /** * A method to be called when a character data node is about to be replaced */ void replacingData(NodeImpl node) { } /** * method to be called when a character data node has been replaced. */ void replacedCharacterData(NodeImpl node, String oldvalue, String value) { } /** * A method to be called when an attribute value has been modified */ void modifiedAttrValue(AttrImpl attr, String oldvalue) { } /** * A method to be called when an attribute node has been set */ void setAttrNode(AttrImpl attr, AttrImpl previous) { } /** * A method to be called when an attribute node has been removed */ void removedAttrNode(AttrImpl attr, NodeImpl oldOwner, String name) { } /** * A method to be called when an attribute node has been renamed */ void renamedAttrNode(Attr oldAt, Attr newAt) { } /** * A method to be called when an element has been renamed */ void renamedElement(Element oldEl, Element newEl) { } /** * The serialized forms of the user data and node table maps are Hashtables. Convert them into WeakHashMaps on load. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if (userData != null) { userData = new WeakHashMap(userData); } if (nodeTable != null) { nodeTable = new WeakHashMap(nodeTable); } } /** * To allow DOM trees serialized by newer versions of Xerces to be read by older versions briefly move the user data * and node table into Hashtables. */ private void writeObject(ObjectOutputStream out) throws IOException { // Keep references to the original objects for restoration after serialization final Map oldUserData = this.userData; final Map oldNodeTable = this.nodeTable; try { if (oldUserData != null) { this.userData = new Hashtable(oldUserData); } if (oldNodeTable != null) { nodeTable = new Hashtable(oldNodeTable); } out.defaultWriteObject(); } // If the write fails for some reason ensure // that we restore the original objects. finally { this.userData = oldUserData; this.nodeTable = oldNodeTable; } } } // class CoreDocumentImpl