org.htmlunit.cyberneko.xerces.dom.NodeImpl Maven / Gradle / Ivy
/*
* Copyright (c) 2017-2024 Ronald Brill
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://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.htmlunit.cyberneko.xerces.dom;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
/**
* NodeImpl provides the basic structure of a DOM tree. It is never used
* directly, but instead is subclassed to add type and data information, and
* additional methods, appropriate to each node of the tree. Only its subclasses
* should be instantiated -- and those, with the exception of Document itself,
* only through a specific Document's factory methods.
*
* The Node interface provides shared behaviors such as siblings and children,
* both for consistancy and so that the most common tree operations may be
* performed without constantly having to downcast to specific node types. When
* there is no obvious mapping for one of these queries, it will respond with
* null. Note that the default behavior is that children are forbidden. To
* permit them, the subclass ParentNode overrides several methods.
*
* NodeImpl also implements NodeList, so it can return itself in response to the
* getChildNodes() query. This eliminiates the need for a separate ChildNodeList
* object. Note that this is an IMPLEMENTATION DETAIL; applications should
* _never_ assume that this identity exists.
*
* All nodes in a single document must originate in that document. (Note that
* this is much tighter than "must be same implementation") Nodes are all aware
* of their ownerDocument, and attempts to mismatch will throw
* WRONG_DOCUMENT_ERR.
*
* However, to save memory not all nodes always have a direct reference to their
* ownerDocument. When a node is owned by another node it relies on its owner to
* store its ownerDocument. Parent nodes always store it though, so there is
* never more than one level of indirection. And when a node doesn't have an
* owner, ownerNode refers to its ownerDocument.
*
* This class doesn't directly support mutation events, however, it still
* implements the EventTarget interface and forward all related calls to the
* document so that the document class do so.
*
*
* @author Arnaud Le Hors, IBM
* @author Joe Kesselman, IBM
*/
public abstract class NodeImpl implements Node, NodeList, EventTarget, Cloneable {
// TreePosition Constants.
// Taken from DOM L3 Node interface.
// DocumentPosition
public static final short DOCUMENT_POSITION_DISCONNECTED = 0x01;
public static final short DOCUMENT_POSITION_PRECEDING = 0x02;
public static final short DOCUMENT_POSITION_FOLLOWING = 0x04;
public static final short DOCUMENT_POSITION_CONTAINS = 0x08;
public static final short DOCUMENT_POSITION_IS_CONTAINED = 0x10;
public static final short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
/** Element definition node type. */
public static final short ELEMENT_DEFINITION_NODE = 21;
protected NodeImpl ownerNode; // typically the parent but not always!
private short flags;
protected static final short READONLY = 0x1 << 0;
protected static final short SYNCDATA = 0x1 << 1;
protected static final short SYNCCHILDREN = 0x1 << 2;
protected static final short OWNED = 0x1 << 3;
protected static final short FIRSTCHILD = 0x1 << 4;
protected static final short SPECIFIED = 0x1 << 5;
protected static final short IGNORABLEWS = 0x1 << 6;
protected static final short HASSTRING = 0x1 << 7;
protected static final short NORMALIZED = 0x1 << 8;
protected static final short ID = 0x1 << 9;
/**
* No public constructor; only subclasses of Node should be instantiated, and
* those normally via a Document's factory methods
*
* Every Node knows what Document it belongs to.
*
* @param ownerDocument the owner document
*/
protected NodeImpl(final CoreDocumentImpl ownerDocument) {
// as long as we do not have any owner, ownerNode is our ownerDocument
ownerNode = ownerDocument;
}
/**
* {@inheritDoc}
*
* A short integer indicating what type of node this is. The named constants for
* this value are defined in the org.w3c.dom.Node interface.
*/
@Override
public abstract short getNodeType();
/**
* {@inheritDoc}
*
* the name of this node.
*/
@Override
public abstract String getNodeName();
/**
* {@inheritDoc}
*
* Returns the node value.
*
* @throws DOMException DOMSTRING_SIZE_ERR
*/
@Override
public String getNodeValue() throws DOMException {
return null; // overridden in some subclasses
}
/**
* {@inheritDoc}
*
* Sets the node value.
*
* @throws DOMException NO_MODIFICATION_ALLOWED_ERR
*/
@Override
public void setNodeValue(final String x) throws DOMException {
// Default behavior is to do nothing, overridden in some subclasses
}
/**
* {@inheritDoc}
*
* Adds a child node to the end of the list of children for this node.
* Convenience shorthand for insertBefore(newChild,null).
*
* @see #insertBefore(Node, Node)
*
* By default we do not accept any children, ParentNode overrides this.
* @see ParentNode
*
* @return newChild, in its new state (relocated, or emptied in the case of
* DocumentNode.)
*
* @throws DOMException HIERARCHY_REQUEST_ERR if newChild is of a type that
* shouldn't be a child of this node.
*
* @throws DOMException WRONG_DOCUMENT_ERR if newChild has a different owner
* document than we do.
*
* @throws DOMException NO_MODIFICATION_ALLOWED_ERR if this node is read-only.
*/
@Override
public Node appendChild(final Node newChild) throws DOMException {
return insertBefore(newChild, null);
}
/**
* {@inheritDoc}
*
* Returns a duplicate of a given node. You can consider this a generic "copy
* constructor" for nodes. The newly returned object should be completely
* independent of the source object's subtree, so changes in one after the clone
* has been made will not affect the other.
*
* Note: since we never have any children deep is meaningless here, ParentNode
* overrides this behavior.
*
* @see ParentNode
*
*
* Example: Cloning a Text node will copy both the node and the text it
* contains.
*
* Example: Cloning something that has children -- Element or Attr, for
* example -- will _not_ clone those children unless a "deep clone" has
* been requested. A shallow clone of an Attr node will yield an empty Attr
* of the same name.
*
* NOTE: Clones will always be read/write, even if the node being cloned is
* read-only, to permit applications using only the DOM API to obtain
* editable copies of locked portions of the tree.
*/
@Override
public Node cloneNode(final boolean deep) {
if (needsSyncData()) {
synchronizeData();
}
final NodeImpl newnode;
try {
newnode = (NodeImpl) clone();
}
catch (final CloneNotSupportedException e) {
// if we get here we have an error in our program we may as well
// be vocal about it, so that people can take appropriate action.
throw new RuntimeException("**Internal Error**" + e);
}
// Need to break the association w/ original kids
newnode.ownerNode = ownerDocument();
newnode.isOwned(false);
return newnode;
}
/**
* {@inheritDoc}
*
* Find the Document that this Node belongs to (the document in whose context
* the Node was created). The Node may or may not currently be part of that
* Document's actual contents.
*/
@Override
public Document getOwnerDocument() {
// if we have an owner simply forward the request
// otherwise ownerNode is our ownerDocument
if (isOwned()) {
return ownerNode.ownerDocument();
}
return (Document) ownerNode;
}
/**
* same as above but returns internal type and this one is not overridden by
* CoreDocumentImpl to return null
*/
CoreDocumentImpl ownerDocument() {
// if we have an owner simply forward the request
// otherwise ownerNode is our ownerDocument
if (isOwned()) {
return ownerNode.ownerDocument();
}
return (CoreDocumentImpl) ownerNode;
}
// NON-DOM set the ownerDocument of this node
protected void setOwnerDocument(final CoreDocumentImpl doc) {
if (needsSyncData()) {
synchronizeData();
}
// if we have an owner we rely on it to have it right
// otherwise ownerNode is our ownerDocument
if (!isOwned()) {
ownerNode = doc;
}
}
/**
* @return the node number
*/
protected int getNodeNumber() {
final int nodeNumber;
final CoreDocumentImpl cd = (CoreDocumentImpl) (this.getOwnerDocument());
nodeNumber = cd.getNodeNumber(this);
return nodeNumber;
}
/**
* Obtain the DOM-tree parent of this node, or null if it is not currently
* active in the DOM tree (perhaps because it has just been created or removed).
* Note that Document, DocumentFragment, and Attribute will never have parents.
*/
@Override
public Node getParentNode() {
return null; // overriden by ChildNode
}
/*
* same as above but returns internal type
*/
NodeImpl parentNode() {
return null;
}
/** The next child of this node's parent, or null if none */
@Override
public Node getNextSibling() {
return null; // default behavior, overriden in ChildNode
}
/** The previous child of this node's parent, or null if none */
@Override
public Node getPreviousSibling() {
return null; // default behavior, overriden in ChildNode
}
ChildNode previousSibling() {
return null; // default behavior, overriden in ChildNode
}
/**
* Return the collection of attributes associated with this node, or null if
* none. At this writing, Element is the only type of node which will ever have
* attributes.
*
* @see ElementImpl
*/
@Override
public NamedNodeMap getAttributes() {
return null; // overridden in ElementImpl
}
/**
* Returns whether this node (if it is an element) has any attributes.
*
* @return true
if this node has any attributes, false
* otherwise.
* @see ElementImpl
*/
@Override
public boolean hasAttributes() {
return false; // overridden in ElementImpl
}
/**
* Test whether this node has any children. Convenience shorthand for
* (Node.getFirstChild()!=null)
*
* By default we do not have any children, ParentNode overrides this.
*
* @see ParentNode
*/
@Override
public boolean hasChildNodes() {
return false;
}
/**
* Obtain a NodeList enumerating all children of this node. If there are none,
* an (initially) empty NodeList is returned.
*
* NodeLists are "live"; as children are added/removed the NodeList will
* immediately reflect those changes. Also, the NodeList refers to the actual
* nodes, so changes to those nodes made via the DOM tree will be reflected in
* the NodeList and vice versa.
*
* In this implementation, Nodes implement the NodeList interface and provide
* their own getChildNodes() support. Other DOMs may solve this differently.
*/
@Override
public NodeList getChildNodes() {
return this;
}
/**
* The first child of this Node, or null if none.
*
* By default we do not have any children, ParentNode overrides this.
*
* @see ParentNode
*/
@Override
public Node getFirstChild() {
return null;
}
/**
* The first child of this Node, or null if none.
*
* By default we do not have any children, ParentNode overrides this.
*
* @see ParentNode
*/
@Override
public Node getLastChild() {
return null;
}
/**
* Move one or more node(s) to our list of children. Note that this implicitly
* removes them from their previous parent.
*
* By default we do not accept any children, ParentNode overrides this.
*
* @see ParentNode
*
* @param newChild The Node to be moved to our subtree. As a convenience
* feature, inserting a DocumentNode will instead insert all its
* children.
*
* @param refChild Current child which newChild should be placed immediately
* before. If refChild is null, the insertion occurs after all
* existing Nodes, like appendChild().
*
* @return newChild, in its new state (relocated, or emptied in the case of
* DocumentNode.)
*
* @throws DOMException HIERARCHY_REQUEST_ERR if newChild is of a type that
* shouldn't be a child of this node, or if newChild is an
* ancestor of this node.
*
* @throws DOMException WRONG_DOCUMENT_ERR if newChild has a different owner
* document than we do.
*
* @throws DOMException NOT_FOUND_ERR if refChild is not a child of this node.
*
* @throws DOMException NO_MODIFICATION_ALLOWED_ERR if this node is read-only.
*/
@Override
public Node insertBefore(final Node newChild, final Node refChild) throws DOMException {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
}
/**
* Remove a child from this Node. The removed child's subtree remains intact so
* it may be re-inserted elsewhere.
*
* By default we do not have any children, ParentNode overrides this.
*
* @see ParentNode
*
* @return oldChild, in its new state (removed).
*
* @throws DOMException NOT_FOUND_ERR if oldChild is not a child of this node.
*
* @throws DOMException NO_MODIFICATION_ALLOWED_ERR if this node is read-only.
*/
@Override
public Node removeChild(final Node oldChild) throws DOMException {
throw new DOMException(DOMException.NOT_FOUND_ERR,
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null));
}
/**
* Make newChild occupy the location that oldChild used to have. Note that
* newChild will first be removed from its previous parent, if any. Equivalent
* to inserting newChild before oldChild, then removing oldChild.
*
* By default we do not have any children, ParentNode overrides this.
*
* @see ParentNode
*
* @return oldChild, in its new state (removed).
*
* @throws DOMException HIERARCHY_REQUEST_ERR if newChild is of a type that
* shouldn't be a child of this node, or if newChild is one
* of our ancestors.
*
* @throws DOMException WRONG_DOCUMENT_ERR if newChild has a different owner
* document than we do.
*
* @throws DOMException NOT_FOUND_ERR if oldChild is not a child of this node.
*
* @throws DOMException NO_MODIFICATION_ALLOWED_ERR if this node is read-only.
*/
@Override
public Node replaceChild(final Node newChild, final Node oldChild) throws DOMException {
throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
}
/**
* {@inheritDoc} NodeList method: Count the immediate children of this node
*
* By default we do not have any children, ParentNode overrides this.
*/
@Override
public int getLength() {
return 0;
}
/**
* {@inheritDoc} NodeList method: Return the Nth immediate child of this node,
* or null if the index is out of bounds.
*
* By default we do not have any children, ParentNode overrides this.
*/
@Override
public Node item(final int index) {
return null;
}
/**
* Puts all Text
nodes in the full depth of the sub-tree underneath
* this Node
, including attribute nodes, into a "normal" form where
* only markup (e.g., tags, comments, processing instructions, CDATA sections,
* and entity references) separates Text
nodes, i.e., there are no
* adjacent Text
nodes. This can be used to ensure that the DOM
* view of a document is the same as if it were saved and re-loaded, and is
* useful when operations (such as XPointer lookups) that depend on a particular
* document tree structure are to be used.In cases where the document contains
* CDATASections
, the normalize operation alone may not be
* sufficient, since XPointers do not differentiate between Text
* nodes and CDATASection
nodes.
*
* Note that this implementation simply calls normalize() on this Node's
* children. It is up to implementors or Node to override normalize() to take
* action.
*/
@Override
public void normalize() {
/*
* by default we do not have any children, ParentNode overrides this behavior
*/
}
/**
* Introduced in DOM Level 2.
*
* Tests whether the DOM implementation implements a specific feature and that
* feature is supported by this node.
*
* @param feature The package name of the feature to test. This is the same name
* as what can be passed to the method hasFeature on
* DOMImplementation.
* @param version This is the version number of the package name to test. In
* Level 2, version 1, this is the string "2.0". If the version
* is not specified, supporting any version of the feature will
* cause the method to return true.
* @return boolean Returns true if this node defines a subtree within which the
* specified feature is supported, false otherwise.
*/
@Override
public boolean isSupported(final String feature, final String version) {
return ownerDocument().getImplementation().hasFeature(feature, version);
}
/**
* Introduced in DOM Level 2.
*
*
* The namespace URI of this node, or null if it is unspecified. When this node
* is of any type other than ELEMENT_NODE and ATTRIBUTE_NODE, this is always
* null and setting it has no effect.
*
*
* This is not a computed value that is the result of a namespace lookup based
* on an examination of the namespace declarations in scope. It is merely the
* namespace URI given at creation time.
*
*
* For nodes created with a DOM Level 1 method, such as createElement from the
* Document interface, this is null.
*
* @see AttrNSImpl
* @see ElementNSImpl
*/
@Override
public String getNamespaceURI() {
return null;
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*
*
* The namespace prefix of this node, or null if it is unspecified. When this
* node is of any type other than ELEMENT_NODE and ATTRIBUTE_NODE this is always
* null and setting it has no effect.
*
*
* For nodes created with a DOM Level 1 method, such as createElement from the
* Document interface, this is null.
*
* @see AttrNSImpl
* @see ElementNSImpl
*/
@Override
public String getPrefix() {
return null;
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*
*
* The namespace prefix of this node, or null if it is unspecified. When this
* node is of any type other than ELEMENT_NODE and ATTRIBUTE_NODE this is always
* null and setting it has no effect.
*
*
* For nodes created with a DOM Level 1 method, such as createElement from the
* Document interface, this is null.
*
*
* Note that setting this attribute changes the nodeName attribute, which holds
* the qualified name, as well as the tagName and name attributes of the Element
* and Attr interfaces, when applicable.
*
* @throws DOMException INVALID_CHARACTER_ERR Raised if the specified prefix
* contains an invalid character.
*
* @see AttrNSImpl
* @see ElementNSImpl
*/
@Override
public void setPrefix(final String prefix) throws DOMException {
throw new DOMException(DOMException.NAMESPACE_ERR,
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NAMESPACE_ERR", null));
}
/**
* {@inheritDoc}
*
* Introduced in DOM Level 2.
*
*
* Returns the local part of the qualified name of this node. For nodes created
* with a DOM Level 1 method, such as createElement from the Document interface,
* and for nodes of any type other than ELEMENT_NODE and ATTRIBUTE_NODE this is
* the same as the nodeName attribute.
*
* @see AttrNSImpl
* @see ElementNSImpl
*/
@Override
public String getLocalName() {
return null;
}
@Override
public void addEventListener(final String type, final EventListener listener, final boolean useCapture) {
// simply forward to Document
ownerDocument().addEventListener(this, type, listener, useCapture);
}
@Override
public void removeEventListener(final String type, final EventListener listener, final boolean useCapture) {
// simply forward to Document
ownerDocument().removeEventListener(this, type, listener, useCapture);
}
@Override
public boolean dispatchEvent(final Event event) {
return false;
}
/**
* The absolute base URI of this node or null
if undefined. This
* value is computed according to . However, when the Document
* supports the feature "HTML" , the base URI is computed using first the value
* of the href attribute of the HTML BASE element if any, and the value of the
* documentURI
attribute from the Document
interface
* otherwise.
* When the node is an Element
, a Document
or a a
* ProcessingInstruction
, this attribute represents the properties
* [base URI] defined in . When the node is a Notation
, an
* Entity
, or an EntityReference
, this attribute
* represents the properties [declaration base URI] in the . How will this be
* affected by resolution of relative namespace URIs issue?It's not.Should this
* only be on Document, Element, ProcessingInstruction, Entity, and Notation
* nodes, according to the infoset? If not, what is it equal to on other nodes?
* Null? An empty string? I think it should be the parent's.No.Should this be
* read-only and computed or and actual read-write attribute?Read-only and
* computed (F2F 19 Jun 2000 and teleconference 30 May 2001).If the base HTML
* element is not yet attached to a document, does the insert change the
* Document.baseURI? Yes. (F2F 26 Sep 2001)
*/
@Override
public String getBaseURI() {
return null;
}
/**
* Compares a node with this node with regard to their position in the document.
*
* @param other The node to compare against this node.
* @return Returns how the given node is positioned relatively to this node.
*/
@Override
public short compareDocumentPosition(final Node other) throws DOMException {
// If the nodes are the same, no flags should be set
if (this == other) {
return 0;
}
// check if other is from a different implementation
if (other != null && !(other instanceof NodeImpl)) {
// other comes from a different implementation
final String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_SUPPORTED_ERR", null);
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
}
final Document thisOwnerDoc;
final Document otherOwnerDoc;
// get the respective Document owners.
if (this.getNodeType() == Node.DOCUMENT_NODE) {
thisOwnerDoc = (Document) this;
}
else {
thisOwnerDoc = this.getOwnerDocument();
}
if (other.getNodeType() == Node.DOCUMENT_NODE) {
otherOwnerDoc = (Document) other;
}
else {
otherOwnerDoc = other.getOwnerDocument();
}
// If from different documents, we know they are disconnected.
// and have an implementation dependent order
if (thisOwnerDoc != otherOwnerDoc && thisOwnerDoc != null && otherOwnerDoc != null) {
final int otherDocNum = ((CoreDocumentImpl) otherOwnerDoc).getNodeNumber();
final int thisDocNum = ((CoreDocumentImpl) thisOwnerDoc).getNodeNumber();
if (otherDocNum > thisDocNum) {
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_FOLLOWING
| DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_PRECEDING
| DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
// Find the ancestor of each node, and the distance each node is from
// its ancestor.
// During this traversal, look for ancestor/descendent relationships
// between the 2 nodes in question.
// We do this now, so that we get this info correct for attribute nodes
// and their children.
Node node;
Node thisAncestor = this;
Node otherAncestor = other;
int thisDepth = 0;
int otherDepth = 0;
for (node = this; node != null; node = node.getParentNode()) {
thisDepth += 1;
if (node == other) {
// The other node is an ancestor of this one.
return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING;
}
thisAncestor = node;
}
for (node = other; node != null; node = node.getParentNode()) {
otherDepth += 1;
if (node == this) {
// The other node is a descendent of the reference node.
return DOCUMENT_POSITION_IS_CONTAINED | DOCUMENT_POSITION_FOLLOWING;
}
otherAncestor = node;
}
final int thisAncestorType = thisAncestor.getNodeType();
final int otherAncestorType = otherAncestor.getNodeType();
Node thisNode = this;
Node otherNode = other;
// Special casing for ENTITY, NOTATION, DOCTYPE and ATTRIBUTES
// LM: should rewrite this.
switch (thisAncestorType) {
case Node.NOTATION_NODE:
case Node.ENTITY_NODE: {
final DocumentType container = thisOwnerDoc.getDoctype();
if (container == otherAncestor) {
return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING;
}
switch (otherAncestorType) {
case Node.NOTATION_NODE:
case Node.ENTITY_NODE: {
if (thisAncestorType != otherAncestorType) {
// the nodes are of different types
return (thisAncestorType > otherAncestorType) ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING;
}
// the nodes are of the same type. Find order.
if (thisAncestorType == Node.NOTATION_NODE) {
if (((NamedNodeMapImpl) container.getNotations()).precedes(otherAncestor, thisAncestor)) {
return DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
return DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
if (((NamedNodeMapImpl) container.getEntities()).precedes(otherAncestor, thisAncestor)) {
return DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
return DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
}
thisNode = thisAncestor = thisOwnerDoc;
break;
}
case Node.DOCUMENT_TYPE_NODE: {
if (otherNode == thisOwnerDoc) {
return DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_CONTAINS;
}
else if (thisOwnerDoc != null && thisOwnerDoc == otherOwnerDoc) {
return DOCUMENT_POSITION_FOLLOWING;
}
break;
}
case Node.ATTRIBUTE_NODE: {
thisNode = ((AttrImpl) thisAncestor).getOwnerElement();
if (otherAncestorType == Node.ATTRIBUTE_NODE) {
otherNode = ((AttrImpl) otherAncestor).getOwnerElement();
if (otherNode == thisNode) {
if (((NamedNodeMapImpl) thisNode.getAttributes()).precedes(other, this)) {
return DOCUMENT_POSITION_PRECEDING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
return DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
}
// Now, find the ancestor of the element
thisDepth = 0;
for (node = thisNode; node != null; node = node.getParentNode()) {
thisDepth += 1;
if (node == otherNode) {
// The other node is an ancestor of the owning element
return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING;
}
thisAncestor = node;
}
}
}
switch (otherAncestorType) {
case Node.NOTATION_NODE:
case Node.ENTITY_NODE: {
final DocumentType container = thisOwnerDoc.getDoctype();
if (container == this) {
return DOCUMENT_POSITION_IS_CONTAINED | DOCUMENT_POSITION_FOLLOWING;
}
otherNode = otherAncestor = thisOwnerDoc;
break;
}
case Node.DOCUMENT_TYPE_NODE: {
if (thisNode == otherOwnerDoc) {
return DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IS_CONTAINED;
}
else if (otherOwnerDoc != null && thisOwnerDoc == otherOwnerDoc) {
return DOCUMENT_POSITION_PRECEDING;
}
break;
}
case Node.ATTRIBUTE_NODE: {
otherDepth = 0;
otherNode = ((AttrImpl) otherAncestor).getOwnerElement();
for (node = otherNode; node != null; node = node.getParentNode()) {
otherDepth += 1;
if (node == thisNode) {
// The other node is a descendent of the reference
// node's element
return DOCUMENT_POSITION_FOLLOWING | DOCUMENT_POSITION_IS_CONTAINED;
}
otherAncestor = node;
}
}
}
// thisAncestor and otherAncestor must be the same at this point,
// otherwise, the original nodes are disconnected
if (thisAncestor != otherAncestor) {
final int thisAncestorNum;
final int otherAncestorNum;
thisAncestorNum = ((NodeImpl) thisAncestor).getNodeNumber();
otherAncestorNum = ((NodeImpl) otherAncestor).getNodeNumber();
if (thisAncestorNum > otherAncestorNum) {
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_FOLLOWING
| DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_PRECEDING
| DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
}
// Go up the parent chain of the deeper node, until we find a node
// with the same depth as the shallower node
if (thisDepth > otherDepth) {
for (int i = 0; i < thisDepth - otherDepth; i++) {
thisNode = thisNode.getParentNode();
}
// Check if the node we have reached is in fact "otherNode". This can
// happen in the case of attributes. In this case, otherNode
// "precedes" this.
if (thisNode == otherNode) {
return DOCUMENT_POSITION_PRECEDING;
}
}
else {
for (int i = 0; i < otherDepth - thisDepth; i++) {
otherNode = otherNode.getParentNode();
}
// Check if the node we have reached is in fact "thisNode". This can
// happen in the case of attributes. In this case, otherNode
// "follows" this.
if (otherNode == thisNode) {
return DOCUMENT_POSITION_FOLLOWING;
}
}
// We now have nodes at the same depth in the tree. Find a common
// ancestor.
Node thisNodeP;
Node otherNodeP;
for (thisNodeP = thisNode.getParentNode(), otherNodeP = otherNode.getParentNode(); thisNodeP != otherNodeP;) {
thisNode = thisNodeP;
otherNode = otherNodeP;
thisNodeP = thisNodeP.getParentNode();
otherNodeP = otherNodeP.getParentNode();
}
// At this point, thisNode and otherNode are direct children of
// the common ancestor.
// See whether thisNode or otherNode is the leftmost
for (Node current = thisNodeP.getFirstChild(); current != null; current = current.getNextSibling()) {
if (current == otherNode) {
return DOCUMENT_POSITION_PRECEDING;
}
else if (current == thisNode) {
return DOCUMENT_POSITION_FOLLOWING;
}
}
// REVISIT: shouldn't get here. Should probably throw an
// exception
return 0;
}
/**
* This attribute returns the text content of this node and its descendants.
* When it is defined to be null, setting it has no effect. When set, any
* possible children this node may have are removed and replaced by a single
* Text
node containing the string this attribute is set to. On
* getting, no serialization is performed, the returned string does not contain
* any markup. No whitespace normalization is performed, the returned string
* does not contain the element content whitespaces . Similarly, on setting, no
* parsing is performed either, the input string is taken as pure textual
* content.
* The string returned is made of the text content of this node depending on its
* type, as defined below:
*
*
* Node type
* Content
*
*
* ELEMENT_NODE, ENTITY_NODE,
* ENTITY_REFERENCE_NODE, DOCUMENT_FRAGMENT_NODE
* concatenation of the
* textContent
attribute value of every child node, excluding
* COMMENT_NODE and PROCESSING_INSTRUCTION_NODE nodes
*
*
* ATTRIBUTE_NODE, TEXT_NODE,
* CDATA_SECTION_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE
* nodeValue
*
*
* DOCUMENT_NODE, DOCUMENT_TYPE_NODE,
* NOTATION_NODE
* null
*
*
*
* @exception DOMException DOMSTRING_SIZE_ERR: Raised when it would return more
* characters than fit in a DOMString
* variable on the implementation platform.
*/
@Override
public String getTextContent() throws DOMException {
return getNodeValue(); // overriden in some subclasses
}
// internal method taking a StringBuilder in parameter
void getTextContent(final StringBuilder builder) throws DOMException {
final String content = getNodeValue();
if (content != null) {
builder.append(content);
}
}
/**
* This attribute returns the text content of this node and its descendants.
* When it is defined to be null, setting it has no effect. When set, any
* possible children this node may have are removed and replaced by a single
* Text
node containing the string this attribute is set to. On
* getting, no serialization is performed, the returned string does not contain
* any markup. No whitespace normalization is performed, the returned string
* does not contain the element content whitespaces . Similarly, on setting, no
* parsing is performed either, the input string is taken as pure textual
* content.
* The string returned is made of the text content of this node depending on its
* type, as defined below:
*
*
* Node type
* Content
*
*
* ELEMENT_NODE, ENTITY_NODE,
* ENTITY_REFERENCE_NODE, DOCUMENT_FRAGMENT_NODE
* concatenation of the
* textContent
attribute value of every child node, excluding
* COMMENT_NODE and PROCESSING_INSTRUCTION_NODE nodes
*
*
* ATTRIBUTE_NODE, TEXT_NODE,
* CDATA_SECTION_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE
* nodeValue
*
*
* DOCUMENT_NODE, DOCUMENT_TYPE_NODE,
* NOTATION_NODE
* null
*
*
*
* @exception DOMException DOMSTRING_SIZE_ERR: Raised when it would return more
* characters than fit in a DOMString
* variable on the implementation platform.
*/
@Override
public void setTextContent(final String textContent) throws DOMException {
setNodeValue(textContent);
}
/**
* Returns whether this node is the same node as the given one.
* This method provides a way to determine whether two Node
* references returned by the implementation reference the same object. When two
* Node
references are references to the same object, even if
* through a proxy, the references may be used completely interchangably, such
* that all attributes have the same values and calling the same DOM method on
* either reference always has exactly the same effect.
*
* @param other The node to test against.
* @return Returns true
if the nodes are the same,
* false
otherwise.
*/
@Override
public boolean isSameNode(final Node other) {
// we do not use any wrapper so the answer is obvious
return this == other;
}
/**
* DOM Level 3: Experimental This method checks if the specified
* namespaceURI
is the default namespace or not.
*
* @param namespaceURI The namespace URI to look for.
* @return true
if the specified namespaceURI
is the
* default namespace, false
otherwise.
*/
@Override
public boolean isDefaultNamespace(final String namespaceURI) {
// REVISIT: remove casts when DOM L3 becomes REC.
final short type = this.getNodeType();
switch (type) {
case Node.ELEMENT_NODE: {
final String namespace = this.getNamespaceURI();
final String prefix = this.getPrefix();
// REVISIT: is it possible that prefix is empty string?
if (prefix == null || prefix.length() == 0) {
if (namespaceURI == null) {
return namespace == null;
}
return namespaceURI.equals(namespace);
}
if (this.hasAttributes()) {
final ElementImpl elem = (ElementImpl) this;
final NodeImpl attr = (NodeImpl) elem.getAttributeNodeNS("http://www.w3.org/2000/xmlns/", "xmlns");
if (attr != null) {
final String value = attr.getNodeValue();
if (namespaceURI == null) {
return namespace == value;
}
return namespaceURI.equals(value);
}
}
final NodeImpl ancestor = (NodeImpl) getElementAncestor(this);
if (ancestor != null) {
return ancestor.isDefaultNamespace(namespaceURI);
}
return false;
}
case Node.DOCUMENT_NODE: {
final Element docElement = ((Document) this).getDocumentElement();
if (docElement != null) {
return docElement.isDefaultNamespace(namespaceURI);
}
return false;
}
case Node.ENTITY_NODE:
case Node.NOTATION_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
case Node.DOCUMENT_TYPE_NODE:
// type is unknown
return false;
case Node.ATTRIBUTE_NODE: {
if (this.ownerNode.getNodeType() == Node.ELEMENT_NODE) {
return ownerNode.isDefaultNamespace(namespaceURI);
}
return false;
}
default: {
final NodeImpl ancestor = (NodeImpl) getElementAncestor(this);
if (ancestor != null) {
return ancestor.isDefaultNamespace(namespaceURI);
}
return false;
}
}
}
/**
* {@inheritDoc}
*
* DOM Level 3 - Experimental: Look up the prefix associated to the given
* namespace URI, starting from this node.
*
* @param namespaceURI the namespace uri
* @return the prefix for the namespace
*/
@Override
public String lookupPrefix(final String namespaceURI) {
// REVISIT: When Namespaces 1.1 comes out this may not be true
// Prefix can't be bound to null namespace
if (namespaceURI == null) {
return null;
}
final short type = this.getNodeType();
switch (type) {
case Node.ELEMENT_NODE: {
this.getNamespaceURI(); // to flip out children
return lookupNamespacePrefix(namespaceURI, (ElementImpl) this);
}
case Node.DOCUMENT_NODE: {
final Element docElement = ((Document) this).getDocumentElement();
if (docElement != null) {
return docElement.lookupPrefix(namespaceURI);
}
return null;
}
case Node.ENTITY_NODE:
case Node.NOTATION_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
case Node.DOCUMENT_TYPE_NODE:
// type is unknown
return null;
case Node.ATTRIBUTE_NODE: {
if (this.ownerNode.getNodeType() == Node.ELEMENT_NODE) {
return ownerNode.lookupPrefix(namespaceURI);
}
return null;
}
default: {
final NodeImpl ancestor = (NodeImpl) getElementAncestor(this);
if (ancestor != null) {
return ancestor.lookupPrefix(namespaceURI);
}
return null;
}
}
}
/**
* {@inheritDoc}
*
* DOM Level 3 - Experimental: Look up the namespace URI associated to the given
* prefix, starting from this node. Use lookupNamespaceURI(null) to lookup the
* default namespace
*/
@Override
public String lookupNamespaceURI(final String specifiedPrefix) {
final short type = this.getNodeType();
switch (type) {
case Node.ELEMENT_NODE: {
String namespace = this.getNamespaceURI();
final String prefix = this.getPrefix();
if (namespace != null) {
// REVISIT: is it possible that prefix is empty string?
if (specifiedPrefix == null && prefix == null) {
// looking for default namespace
return namespace;
}
else if (prefix != null && prefix.equals(specifiedPrefix)) {
// non default namespace
return namespace;
}
}
if (this.hasAttributes()) {
final NamedNodeMap map = this.getAttributes();
final int length = map.getLength();
for (int i = 0; i < length; i++) {
final Node attr = map.item(i);
namespace = attr.getNamespaceURI();
if (namespace != null && "http://www.w3.org/2000/xmlns/".equals(namespace)) {
final String attrPrefix = attr.getPrefix();
final String value = attr.getNodeValue();
// at this point we are dealing with DOM Level 2 nodes only
if (specifiedPrefix == null && "xmlns".equals(attr.getNodeName())) {
// default namespace
return value.length() > 0 ? value : null;
}
else if (attrPrefix != null && "xmlns".equals(attrPrefix)
&& attr.getLocalName().equals(specifiedPrefix)) {
// non default namespace
return value.length() > 0 ? value : null;
}
}
}
}
final NodeImpl ancestor = (NodeImpl) getElementAncestor(this);
if (ancestor != null) {
return ancestor.lookupNamespaceURI(specifiedPrefix);
}
return null;
}
case Node.DOCUMENT_NODE: {
final Element docElement = ((Document) this).getDocumentElement();
if (docElement != null) {
return docElement.lookupNamespaceURI(specifiedPrefix);
}
return null;
}
case Node.ENTITY_NODE:
case Node.NOTATION_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
case Node.DOCUMENT_TYPE_NODE:
// type is unknown
return null;
case Node.ATTRIBUTE_NODE: {
if (this.ownerNode.getNodeType() == Node.ELEMENT_NODE) {
return ownerNode.lookupNamespaceURI(specifiedPrefix);
}
return null;
}
default: {
final NodeImpl ancestor = (NodeImpl) getElementAncestor(this);
if (ancestor != null) {
return ancestor.lookupNamespaceURI(specifiedPrefix);
}
return null;
}
}
}
Node getElementAncestor(final Node currentNode) {
Node parent = currentNode.getParentNode();
while (parent != null) {
final short type = parent.getNodeType();
if (type == Node.ELEMENT_NODE) {
return parent;
}
parent = parent.getParentNode();
}
return null;
}
String lookupNamespacePrefix(final String namespaceURI, final ElementImpl el) {
String namespace = this.getNamespaceURI();
// REVISIT: if no prefix is available is it null or empty string, or
// could be both?
final String prefix = this.getPrefix();
if (namespace != null && namespace.equals(namespaceURI)) {
if (prefix != null) {
final String foundNamespace = el.lookupNamespaceURI(prefix);
if (foundNamespace != null && foundNamespace.equals(namespaceURI)) {
return prefix;
}
}
}
if (this.hasAttributes()) {
final NamedNodeMap map = this.getAttributes();
final int length = map.getLength();
for (int i = 0; i < length; i++) {
final Node attr = map.item(i);
namespace = attr.getNamespaceURI();
if (namespace != null && "http://www.w3.org/2000/xmlns/".equals(namespace)) {
final String attrPrefix = attr.getPrefix();
final String value = attr.getNodeValue();
// DOM Level 2 nodes
if (("xmlns".equals(attr.getNodeName()))
|| (attrPrefix != null && "xmlns".equals(attrPrefix)) && value.equals(namespaceURI)) {
final String localname = attr.getLocalName();
final String foundNamespace = el.lookupNamespaceURI(localname);
if (foundNamespace != null && foundNamespace.equals(namespaceURI)) {
return localname;
}
}
}
}
}
final NodeImpl ancestor = (NodeImpl) getElementAncestor(this);
if (ancestor != null) {
return ancestor.lookupNamespacePrefix(namespaceURI, el);
}
return null;
}
/**
* Tests whether two nodes are equal.
* This method tests for equality of nodes, not sameness (i.e., whether the two
* nodes are references to the same object) which can be tested with
* Node.isSameNode
. All nodes that are the same will also be equal,
* though the reverse may not be true.
* Two nodes are equal if and only if the following conditions are satisfied:
* The two nodes are of the same type.The following string attributes are equal:
* nodeName
, localName
, namespaceURI
,
* prefix
, nodeValue
, baseURI
. This is:
* they are both null
, or they have the same length and are
* character for character identical. The attributes
* NamedNodeMaps
are equal. This is: they are both
* null
, or they have the same length and for each node that exists
* in one map there is a node that exists in the other map and is equal,
* although not necessarily at the same index.The childNodes
* NodeLists
are equal. This is: they are both null
,
* or they have the same length and contain equal nodes at the same index. This
* is true for Attr
nodes as for any other type of node. Note that
* normalization can affect equality; to avoid this, nodes should be normalized
* before being compared.
* For two DocumentType
nodes to be equal, the following conditions
* must also be satisfied: The following string attributes are equal:
* publicId
, systemId
, internalSubset
.The
* entities
NamedNodeMaps
are equal.The
* notations
NamedNodeMaps
are equal.
* On the other hand, the following do not affect equality: the
* ownerDocument
attribute, the specified
attribute
* for Attr
nodes, the isWhitespaceInElementContent
* attribute for Text
nodes, as well as any user data or event
* listeners registered on the nodes.
*
* @param arg The node to compare equality with.
* @return If the nodes, and possibly subtrees are equal, true
* otherwise false
.
*/
@Override
public boolean isEqualNode(final Node arg) {
if (arg == this) {
return true;
}
if (arg.getNodeType() != getNodeType()) {
return false;
}
// in theory nodeName can't be null but better be careful
// who knows what other implementations may be doing?...
if (getNodeName() == null) {
if (arg.getNodeName() != null) {
return false;
}
}
else if (!getNodeName().equals(arg.getNodeName())) {
return false;
}
if (getLocalName() == null) {
if (arg.getLocalName() != null) {
return false;
}
}
else if (!getLocalName().equals(arg.getLocalName())) {
return false;
}
if (getNamespaceURI() == null) {
if (arg.getNamespaceURI() != null) {
return false;
}
}
else if (!getNamespaceURI().equals(arg.getNamespaceURI())) {
return false;
}
if (getPrefix() == null) {
if (arg.getPrefix() != null) {
return false;
}
}
else if (!getPrefix().equals(arg.getPrefix())) {
return false;
}
if (getNodeValue() == null) {
if (arg.getNodeValue() != null) {
return false;
}
}
else if (!getNodeValue().equals(arg.getNodeValue())) {
return false;
}
return true;
}
@Override
public Object getFeature(final String feature, final String version) {
// we don't have any alternate node, either this node does the job
// or we don't have anything that does
return isSupported(feature, version) ? this : null;
}
/**
* Associate an object to a key on this node. The object can later be retrieved
* from this node by calling getUserData
with the same key.
*
* @param key The key to associate the object to.
* @param data The object to associate to the given key, or null
* to remove any existing association to that key.
* @param handler The handler to associate to that key, or null
.
* @return Returns the DOMObject
previously associated to the given
* key on this node, or null
if there was none.
*/
@Override
public Object setUserData(final String key, final Object data, final UserDataHandler handler) {
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 setUserData
with the
* same key.
*
* @param key The key the object is associated to.
* @return Returns the DOMObject
associated to the given key on
* this node, or null
if there was none.
*/
@Override
public Object getUserData(final String key) {
return null;
}
/**
* Denotes that this node has changed.
*/
protected void changed() {
// we do not actually store this information on every node, we only
// have a global indicator on the Document. Doing otherwise cost us too
// much for little gain.
ownerDocument().changed();
}
/**
* @return the number of changes to this node.
*/
protected int changes() {
// we do not actually store this information on every node, we only
// have a global indicator on the Document. Doing otherwise cost us too
// much for little gain.
return ownerDocument().changes();
}
/**
* Override this method in subclass to hook in efficient internal data
* structure.
*/
protected void synchronizeData() {
// By default just change the flag to avoid calling this method again
needsSyncData(false);
}
final boolean needsSyncData() {
return (flags & SYNCDATA) != 0;
}
final void needsSyncData(final boolean value) {
flags = (short) (value ? flags | SYNCDATA : flags & ~SYNCDATA);
}
final boolean needsSyncChildren() {
return (flags & SYNCCHILDREN) != 0;
}
public final void needsSyncChildren(final boolean value) {
flags = (short) (value ? flags | SYNCCHILDREN : flags & ~SYNCCHILDREN);
}
final boolean isOwned() {
return (flags & OWNED) != 0;
}
final void isOwned(final boolean value) {
flags = (short) (value ? flags | OWNED : flags & ~OWNED);
}
final boolean isFirstChild() {
return (flags & FIRSTCHILD) != 0;
}
final void isFirstChild(final boolean value) {
flags = (short) (value ? flags | FIRSTCHILD : flags & ~FIRSTCHILD);
}
final boolean isSpecified() {
return (flags & SPECIFIED) != 0;
}
final void isSpecified(final boolean value) {
flags = (short) (value ? flags | SPECIFIED : flags & ~SPECIFIED);
}
// inconsistent name to avoid clash with public method on TextImpl
final boolean internalIsIgnorableWhitespace() {
return (flags & IGNORABLEWS) != 0;
}
final void isIgnorableWhitespace(final boolean value) {
flags = (short) (value ? flags | IGNORABLEWS : flags & ~IGNORABLEWS);
}
final boolean hasStringValue() {
return (flags & HASSTRING) != 0;
}
final void hasStringValue(final boolean value) {
flags = (short) (value ? flags | HASSTRING : flags & ~HASSTRING);
}
final boolean isNormalized() {
return (flags & NORMALIZED) != 0;
}
final void isNormalized(final boolean value) {
// See if flag should propagate to parent.
if (!value && isNormalized() && ownerNode != null) {
ownerNode.isNormalized(false);
}
flags = (short) (value ? flags | NORMALIZED : flags & ~NORMALIZED);
}
final boolean isIdAttribute() {
return (flags & ID) != 0;
}
final void isIdAttribute(final boolean value) {
flags = (short) (value ? flags | ID : flags & ~ID);
}
// NON-DOM method for debugging convenience.
@Override
public String toString() {
return "[" + getNodeName() + ": " + getNodeValue() + "]";
}
}