blob: 7a874238722595d5eacdcaa45df91bf707c495bd [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xerces" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, International
* Business Machines, Inc., http://www.apache.org. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.xerces.dom;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import org.w3c.dom.range.*;
import org.w3c.dom.events.*;
import org.apache.xerces.dom.events.*;
/**
* 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 DocumentImpl class also implements the DOM Level 2 DocumentTraversal
* interface. This interface is comprised of factory methods needed to
* create NodeIterators and TreeWalkers. The process of creating NodeIterator
* objects also adds these references to this document.
* After finishing with an iterator it is important to remove the object
* using the remove methods in this implementation. This allows the release of
* the references from the iterator objects to the DOM Nodes.
* <p>
* <b>Note:</b> When any node in the document is serialized, the
* entire document is serialized along with it.
*
* @version
* @since PR-DOM-Level-1-19980818.
*/
public class DocumentImpl
extends ParentNode
implements Document, DocumentTraversal, DocumentEvent, DocumentRange {
//
// Constants
//
/** Serialization version. */
static final long serialVersionUID = 515687835542616694L;
//
// Data
//
// document information
/** Document type. */
protected DocumentTypeImpl docType;
/** Document element. */
protected ElementImpl docElement;
/** Identifiers. */
protected Hashtable identifiers;
/** Iterators */
// REVISIT: Should this be transient? -Ac
protected Vector iterators;
/** Ranges */
// REVISIT: Should this be transient? -Ac
protected Vector ranges;
/** Table for quick check of child insertion. */
protected static int[] kidOK;
/** Table for user data attached to this document nodes. */
protected Hashtable userData;
/** Table for event listeners registered to this document nodes. */
protected Hashtable eventListeners;
/**
* 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;
/** Bypass mutation events firing. */
protected boolean mutationEvents = false;
//
// 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
//
// 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 DocumentImpl() {
this(false);
}
/** Experimental constructor. */
public DocumentImpl(boolean grammarAccess) {
super(null);
ownerDocument = this;
allowGrammarAccess = grammarAccess;
}
// For DOM2: support.
// The createDocument factory method is in DOMImplementation.
public DocumentImpl(DocumentType doctype)
{
this(doctype, false);
}
/** Experimental constructor. */
public DocumentImpl(DocumentType doctype, boolean grammarAccess) {
this(grammarAccess);
this.docType = (DocumentTypeImpl)doctype;
if (this.docType != null) {
docType.ownerDocument = this;
}
}
//
// 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) {
// clone the node itself
DocumentImpl newdoc = new DocumentImpl();
// then the children by importing them
if (needsSyncChildren()) {
synchronizeChildren();
}
if (deep) {
for (int i = 0; i < length; i++) {
newdoc.appendChild(newdoc.importNode(children[i], true));
}
}
// REVISIT: What to do about identifiers that are cloned? -Ac
//newdoc.identifiers = (Hashtable)identifiers.clone(); // WRONG!
newdoc.identifiers = null;
newdoc.iterators = null;
newdoc.ranges = null;
newdoc.userData = null;
newdoc.eventListeners = null;
// experimental
newdoc.allowGrammarAccess = allowGrammarAccess;
newdoc.errorChecking = errorChecking;
newdoc.mutationEvents = mutationEvents;
// return new document
return newdoc;
} // cloneNode(boolean):Node
/**
* 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((type == Node.ELEMENT_NODE && docElement != null) ||
(type == Node.DOCUMENT_TYPE_NODE && docType != null)) {
throw new DOMExceptionImpl(DOMException.HIERARCHY_REQUEST_ERR,
"DOM006 Hierarchy request error");
}
}
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 {
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
//
// 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)) {
throw new DOMExceptionImpl(DOMException.INVALID_CHARACTER_ERR,
"DOM002 Illegal character");
}
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)) {
throw new DOMExceptionImpl(DOMException.INVALID_CHARACTER_ERR,
"DOM002 Illegal character");
}
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)) {
throw new DOMExceptionImpl(DOMException.INVALID_CHARACTER_ERR,
"DOM002 Illegal character");
}
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)) {
throw new DOMExceptionImpl(DOMException.INVALID_CHARACTER_ERR,
"DOM002 Illegal character");
}
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 DOMImplementationImpl.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;
}
/**
* Returns true if the DOM implementation performs error checking.
*/
public boolean getErrorChecking() {
return errorChecking;
}
/**
* Sets whether the DOM implementation generates mutation events
* upon operations.
*/
public void setMutationEvents(boolean set) {
mutationEvents = set;
}
/**
* Returns true if the DOM implementation generates mutation events.
*/
public boolean getMutationEvents() {
return mutationEvents;
}
// 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 name The name of the Entity we wish to provide a value for.
*
* @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 {
if (errorChecking && !isXMLName(qualifiedName)) {
throw new DOMExceptionImpl(DOMException.INVALID_CHARACTER_ERR,
"DOM002 Illegal character");
}
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 {
// REVISIT: Should we be checking XML name chars?
/***
if (errorChecking && !isXMLName(name)) {
throw new DOMExceptionImpl(DOMException.INVALID_CHARACTER_ERR,
"DOM002 Illegal character");
}
/***/
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 {
// REVISIT: Should we be checking XML name chars?
/***
if (errorChecking && !isXMLName(name)) {
throw new DOMExceptionImpl(DOMException.INVALID_CHARACTER_ERR,
"DOM002 Illegal character");
}
/***/
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 {
// REVISIT: Should we be checking XML name chars?
/***
if (errorChecking && !isXMLName(name)) {
throw new DOMExceptionImpl(DOMException.INVALID_CHARACTER_ERR,
"DOM002 Illegal character");
}
/***/
return new ElementDefinitionImpl(this, name);
} // createElementDefinition(String):ElementDefinitionImpl
// other non-DOM methods
/**
* Copies data from the source node. Unlike cloneNode, this
* _can_ copy data from another document. If the source document is also
* based on org.apache.xerces.dom, we will attempt to preserve the domimpl-
* internal data by doing a clone-and-reparent. If not, we will use
* the source's public methods, and this document's Factory methods,
* to copy data defined by the DOM interfaces.
*
* Its behavior is otherwise similar to that of cloneNode.
*
* Attempting to import a Document into another Document is meaningless --
* a new Document would not improve matters much, and a DocumentFragment
* couldn't carry the DocumentType child (if any). Best thing we can do
* is throw a HIERARCHY_REQUEST_ERR.
*
* ????? Should we push some of this down to copy-ctors, so
* subclassed DOMs have the option of special-casing each other
* (as we do for ourself)?
*/
public Node importNode(Node source, boolean deep)
throws DOMException {
Node newnode=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
DOMImplementation domImplementation =
source.getOwnerDocument().getImplementation(); // get source implementation
boolean domLevel20 =
domImplementation.hasFeature("XML", "2.0" ); //DOM Level 2.0 implementation
int type = source.getNodeType();
switch (type) {
case ELEMENT_NODE: {
Element newelement;
if( domLevel20 == true ){
if( source.getLocalName() == null ){
newelement = createElement(source.getNodeName());
} else {
newelement = createElementNS(source.getNamespaceURI(),
source.getNodeName());
}
} else {
newelement = createElement( source.getNodeName() );
}
NamedNodeMap srcattr = source.getAttributes();
if (srcattr != null) {
for(int i = 0; i < srcattr.getLength(); i++) {
Attr attr = (Attr) srcattr.item(i);
if (attr.getSpecified()) { // not a default attribute
Attr nattr = (Attr) importNode(attr, true);
if( domLevel20 == true ) {
if (attr.getLocalName() == null)
newelement.setAttributeNode(nattr);
else
newelement.setAttributeNodeNS(nattr);
} else {
newelement.setAttributeNode(nattr);
}
}
}
}
newnode = newelement;
break;
}
case ATTRIBUTE_NODE: {
if( domLevel20 == true ){
if (source.getLocalName() == null) {
newnode = createAttribute(source.getNodeName());
} else {
newnode = createAttributeNS(source.getNamespaceURI(),
source.getNodeName());
}
} else {
newnode = createAttribute(source.getNodeName());
}
deep = true;
// Kids carry value
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());
// allow deep import temporarily
((EntityReferenceImpl)newnode).isReadOnly(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
newentity.isReadOnly(false); // allow deep import temporarily
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: {
DocumentType srcdoctype = (DocumentType)source;
DocumentTypeImpl newdoctype = (DocumentTypeImpl)
createDocumentType(srcdoctype.getNodeName(),
srcdoctype.getPublicId(),
srcdoctype.getSystemId());
// 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));
}
}
smap = srcdoctype.getNotations();
tmap = newdoctype.getNotations();
if (smap != null) {
for(int i = 0; i < smap.getLength(); i++) {
tmap.setNamedItem(importNode(smap.item(i), true));
}
}
// 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 : // Document can't be child of Document
default: { // Unknown node type
throw new DOMExceptionImpl(DOMException.HIERARCHY_REQUEST_ERR,
"DOM006 Hierarchy request error");
}
}
// If deep, replicate and attach the kids.
if (deep) {
for (Node srckid = source.getFirstChild();
srckid != null;
srckid = srckid.getNextSibling()) {
newnode.appendChild(importNode(srckid, true));
}
}
if (newnode.getNodeType() == Node.ENTITY_REFERENCE_NODE
|| newnode.getNodeType() == Node.ENTITY_NODE) {
((NodeImpl)newnode).setReadOnly(true, true);
}
return newnode;
} // importNode(Node,boolean):Node
/**
* NON-DOM:
* Change the node's ownerDocument, and its subtree, to this Document
*
* @param source The node to move in to this document.
* @exception NOT_SUPPORTED_ERR DOMException, raised if the implementation
* cannot handle the request, such as when the source node comes from a
* different DOMImplementation
* @see DocumentImpl.importNode
**/
public void adoptNode(Node source) {
if (!(source instanceof NodeImpl)) {
throw new DOMExceptionImpl(DOMException.NOT_SUPPORTED_ERR,
"cannot move a node in from another DOM implementation");
}
Node parent = source.getParentNode();
if (parent != null) {
parent.removeChild(source);
}
((NodeImpl)source).setOwnerDocument(this);
}
// 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);
}
/**
* 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;
}
return (Element)identifiers.get(idName);
} // 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);
}
/**
* 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);
}
/**
* 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);
}
//
// DocumentTraversal methods
//
/**
* NON-DOM extension:
* Create and return a NodeIterator. The NodeIterator is
* added to a list of NodeIterators so that it can be
* removed to free up the DOM Nodes it references.
* @see #removeNodeIterator
* @see #removeNodeIterators
*
* @param root The root of the iterator.
* @param whatToShow The whatToShow mask.
* @param filter The NodeFilter installed. Null means no filter.
*/
public NodeIterator createNodeIterator(Node root,
short whatToShow,
NodeFilter filter)
{
return createNodeIterator(root,whatToShow,filter,true);
}
/**
* Create and return a NodeIterator. The NodeIterator is
* added to a list of NodeIterators so that it can be
* removed to free up the DOM Nodes it references.
* @see #removeNodeIterator
* @see #removeNodeIterators
*
* @param root The root of the iterator.
* @param whatToShow The whatToShow mask.
* @param filter The NodeFilter installed. Null means no filter.
* @param entityReferenceExpansion true to expand the contents of EntityReference nodes
* @since WD-DOM-Level-2-19990923
*/
public NodeIterator createNodeIterator(Node root,
int whatToShow,
NodeFilter filter,
boolean entityReferenceExpansion)
{
NodeIterator iterator = new NodeIteratorImpl(
this,
root,
whatToShow,
filter,
entityReferenceExpansion);
if (iterators == null) {
iterators = new Vector();
}
iterators.addElement(iterator);
return iterator;
}
/**
* NON-DOM extension:
* Create and return a TreeWalker.
*
* @param root The root of the iterator.
* @param whatToShow The whatToShow mask.
* @param filter The NodeFilter installed. Null means no filter.
*/
public TreeWalker createTreeWalker(Node root,
short whatToShow,
NodeFilter filter)
{
return createTreeWalker(root,whatToShow,filter,true);
}
/**
* Create and return a TreeWalker.
*
* @param root The root of the iterator.
* @param whatToShow The whatToShow mask.
* @param filter The NodeFilter installed. Null means no filter.
* @param entityReferenceExpansion true to expand the contents of EntityReference nodes
* @since WD-DOM-Level-2-19990923
*/
public TreeWalker createTreeWalker(Node root,
int whatToShow,
NodeFilter filter,
boolean entityReferenceExpansion)
{
if( root==null) {
throw new DOMExceptionImpl(
DOMException.NOT_SUPPORTED_ERR,
"DOM007 Not supported");
}
TreeWalker treeWalker = new TreeWalkerImpl(root,
whatToShow,
filter,
entityReferenceExpansion);
return treeWalker;
}
//
// Not DOM Level 2. Support DocumentTraversal methods.
//
/** This is not called by the developer client. The
* developer client uses the detach() function on the
* NodeIterator itself. <p>
*
* This function is called from the NodeIterator#detach().
*/
void removeNodeIterator(NodeIterator nodeIterator) {
if (nodeIterator == null) return;
if (iterators == null) return;
iterators.removeElement(nodeIterator);
}
//
// DocumentRange methods
//
/**
*/
public Range createRange() {
if (ranges == null) {
ranges = new Vector();
}
Range range = new RangeImpl(this);
ranges.addElement(range);
return range;
}
/** Not a client function. Called by Range.detach(),
* so a Range can remove itself from the list of
* Ranges.
*/
void removeRange(Range range) {
if (range == null) return;
if (ranges == null) return;
ranges.removeElement(range);
}
/**
* A method to be called when some text was changed in a text node,
* so that live objects can be notified.
*/
void replacedText(Node node) {
// notify ranges
if (ranges != null) {
Enumeration enum = ranges.elements();
while (enum.hasMoreElements()) {
((RangeImpl)enum.nextElement()).receiveReplacedText(node);
}
}
}
/**
* A method to be called when some text was deleted from a text node,
* so that live objects can be notified.
*/
void deletedText(Node node, int offset, int count) {
// notify ranges
if (ranges != null) {
Enumeration enum = ranges.elements();
while (enum.hasMoreElements()) {
((RangeImpl)enum.nextElement()).receiveDeletedText(node,
offset,
count);
}
}
}
/**
* A method to be called when some text was inserted into a text node,
* so that live objects can be notified.
*/
void insertedText(Node node, int offset, int count) {
// notify ranges
if (ranges != null) {
Enumeration enum = ranges.elements();
while (enum.hasMoreElements()) {
((RangeImpl)enum.nextElement()).receiveInsertedText(node,
offset,
count);
}
}
}
/**
* A method to be called when a text node has been split,
* so that live objects can be notified.
*/
void splitData(Node node, Node newNode, int offset) {
// notify ranges
if (ranges != null) {
Enumeration enum = ranges.elements();
while (enum.hasMoreElements()) {
((RangeImpl)enum.nextElement()).receiveSplitData(node,
newNode,
offset);
}
}
}
/**
* A method to be called when a node is removed from the tree so that live
* objects can be notified.
*/
void removedChildNode(Node oldChild) {
// notify iterators
if (iterators != null) {
Enumeration enum = iterators.elements();
while (enum.hasMoreElements()) {
((NodeIteratorImpl)enum.nextElement()).removeNode(oldChild);
}
}
// notify ranges
if (ranges != null) {
Enumeration enum = ranges.elements();
while (enum.hasMoreElements()) {
((RangeImpl)enum.nextElement()).removeNode(oldChild);
}
}
}
//
// DocumentEvent methods
//
/**
* Introduced in DOM Level 2. Optional. <p>
* Create and return Event objects.
* <p>
* @param type The event set name, defined as the interface name
* minus package qualifiers. For example, to create a DOMNodeInserted
* event you'd call <code>createEvent("MutationEvent")</code>,
* then use its initMutationEvent() method to configure it properly
* as DOMNodeInserted. This parameter is case-sensitive.
* @return an uninitialized Event object. Call the appropriate
* <code>init...Event()</code> method before dispatching it.
* @exception DOMException UNSUPPORTED_EVENT_TYPE if the requested
* event set is not supported in this DOM.
* @since WD-DOM-Level-2-19990923
*/
public Event createEvent(String type)
throws DOMException {
if("Event".equals(type))
return new EventImpl();
if("MutationEvent".equals(type))
return new MutationEventImpl();
else
throw new DOMExceptionImpl(DOMExceptionImpl.UNSUPPORTED_EVENT_TYPE,
"DOM007 Not supported");
}
//
// Object methods
//
/** Clone. */
public Object clone() throws CloneNotSupportedException {
DocumentImpl newdoc = (DocumentImpl)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. From the XML spec:
* <p>
* [Definition:] A Name is a token beginning with a letter or one of a
* few punctuation characters, and continuing with letters, digits,
* hyphens,underscores, colons, or full stops, together known as name
* characters.
* <p>
* Unfortunately, that spec goes on to say that after the first character,
* names may use "combining characters" and "extender characters",
* which are explicitly listed rather than defined in terms of Java's
* Unicode character types... and which in fact can't be expressed solely
* in terms of those types.
* <p>
* I've empirically derived some tests which are partly based on the
* Java Unicode space (which simplifies them considerably), but they
* still wind up having to be further qualified. This may not remain
* valid if future extensions of Java and/or Unicode introduce other
* characters having these type numbers.
* <p>
* Suggestions for alternative implementations would be welcome.
*/
public static boolean isXMLName(String s) {
// REVISIT: Use the parser's character checking code. -Ac
if (s == null) {
return false;
}
char [] ca=new char[s.length()];
s.getChars(0,s.length(),ca,0);
// First character must be letter, underscore, or colon.
if (!Character.isLetter(ca[0]) && "_:".indexOf((int)ca[0]) == -1) {
return false;
}
// Remaining characters must be letter, digit, underscore,
// colon, period, dash, an XML "Combining Character", or an
// XML "Extender Character".
for (int i = 1; i < s.length(); ++i) {
char c = ca[i];
int ctype = Character.getType(c);
if (!Character.isLetterOrDigit(c) &&
(".-_:".indexOf(c) == -1) &&
// Right type
(!(ctype >= 6 && ctype <= 8 &&
// Bad ranges, combined from the three types:
!((c >= 0x06dd && c <= 0x06de) ||
(c >= 0x20dd && c <= 0x20e0) ||
c >= 0x309b
)
)
) &&
// XML Extender chars are all type 4 (uppercase) except
// for two which are type 24. (titlecase modifier)
// Right type
(!(ctype == 4 &&
// Bad ranges
!((c >= 0x02d0 && c <= 0x0559) ||
(c >= 0x06e5 && c <= 0x06e6) ||
(c >= 0x309b && c <= 0x309c)
)
|| c == 0x00b7 // Type 24
|| c == 0x0387 // Type 24
))
) {
return false;
}
}
// All characters passed the tests.
return true;
} // isXMLName(String):boolean
/**
* 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) {
if (userData == null) {
userData = new Hashtable();
}
if (data == null) {
userData.remove(n);
} else {
userData.put(n, data);
}
}
/**
* Retreive user data related to a given node
*/
protected Object getUserData(NodeImpl n) {
if (userData == null) {
return null;
}
return userData.get(n);
}
/**
* Store event listener registered on a given node
* This is another place where we could use weak references! Indeed, the
* node here won't be GC'ed as long as some listener is registered on it,
* since the eventsListeners table will have a reference to the node.
*/
protected void setEventListeners(NodeImpl n, Vector listeners) {
if (eventListeners == null) {
eventListeners = new Hashtable();
}
if (listeners == null) {
eventListeners.remove(n);
if (eventListeners.isEmpty()) {
// stop firing events when there isn't any listener
mutationEvents = false;
}
} else {
eventListeners.put(n, listeners);
// turn mutation events on
mutationEvents = true;
}
}
/**
* Retreive event listener registered on a given node
*/
protected Vector getEventListeners(NodeImpl n) {
if (eventListeners == null) {
return null;
}
return (Vector) eventListeners.get(n);
}
//
// 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;
}
} // class DocumentImpl