/* * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.ukit.dom; import org.w3c.dom.Node; import org.w3c.dom.Text; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.dom.DOMException; /** * DOM parent node implementation. * * @see org.w3c.dom.Node */ public abstract class XParent extends XNode { /** Default children list capacity. */ private final static int DEFAULT_CHILD_NUM = 8; /** Children list. */ private XNode[] chlst; /** Number of children in the list. */ private int chnum; /** * Constructs node object from other node. */ protected XParent(XParent node, boolean deep) { super(node, deep); if (deep == true) { chlst = new XNode[node.chlst.length]; for (int idx = 0; idx < node.chnum; idx++) { chlst[idx] = (XNode)(node.chlst[idx].cloneNode(deep)); chlst[idx]._setParent(this); } chnum = node.chnum; } else { chlst = new XNode[DEFAULT_CHILD_NUM]; } } /** * Constructs parent node object from its qualified name and namespace URI and * its owner document. */ protected XParent(String namespaceURI, String qName, XDoc ownerDocument) { super(namespaceURI, qName, ownerDocument); chlst = new XNode[DEFAULT_CHILD_NUM]; } /** * A code representing the type of the underlying object, as defined above. */ public abstract short getNodeType(); /** * A <code>NodeList</code> that contains all children of this node. If * there are no children, this is a <code>NodeList</code> containing no * nodes. */ public NodeList getChildNodes() { return this; } /** * The first child of this node. If there is no such node, this returns * <code>null</code>. */ public Node getFirstChild() { return (chnum > 0)? chlst[0]: null; } /** * The last child of this node. If there is no such node, this returns * <code>null</code>. */ public Node getLastChild() { return (chnum > 0)? chlst[chnum - 1]: null; } /** * Inserts the node <code>newChild</code> before the existing child node * <code>refChild</code>. If <code>refChild</code> is <code>null</code>, * insert <code>newChild</code> at the end of the list of children. * <br>If <code>newChild</code> is a <code>DocumentFragment</code> object, * all of its children are inserted, in the same order, before * <code>refChild</code>. If the <code>newChild</code> is already in the * tree, it is first removed. * * @param newChild The node to insert. * @param refChild The reference node, i.e., the node before which the new * node must be inserted. * @return The node being inserted. * @exception DOMException * HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not * allow children of the type of the <code>newChild</code> node, or if * the node to insert is one of this node's ancestors. * <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created * from a different document than the one that created this node. * <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly or * if the parent of the node being inserted is readonly. * <br>NOT_FOUND_ERR: Raised if <code>refChild</code> is not a child of * this node. */ public Node insertBefore(Node newChild, Node refChild) throws DOMException { if (refChild == null) return appendChild(newChild); if (_isRO()) throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, ""); int idx = _childIdx(refChild); if (idx < 0) throw new DOMException(DOMException.NOT_FOUND_ERR, ""); _insertChild(idx, (XNode)newChild); return newChild; } /** * Replaces the child node <code>oldChild</code> with <code>newChild</code> * in the list of children, and returns the <code>oldChild</code> node. * <br>If <code>newChild</code> is a <code>DocumentFragment</code> object, * <code>oldChild</code> is replaced by all of the * <code>DocumentFragment</code> children, which are inserted in the * same order. If the <code>newChild</code> is already in the tree, it * is first removed. * * @param newChild The new node to put in the child list. * @param oldChild The node being replaced in the list. * @return The node replaced. * @exception DOMException * HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not * allow children of the type of the <code>newChild</code> node, or if * the node to put in is one of this node's ancestors. * <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created * from a different document than the one that created this node. * <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node or the parent of * the new node is readonly. * <br>NOT_FOUND_ERR: Raised if <code>oldChild</code> is not a child of * this node. */ public Node replaceChild(Node newChild, Node oldChild) throws DOMException { if (_isRO()) throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, ""); if (oldChild.getParentNode() != this) throw new DOMException(DOMException.NOT_FOUND_ERR, ""); if (newChild == oldChild) return oldChild; XNode nchild = (XNode)newChild; XNode ochild = (XNode)oldChild; if (nchild.getNodeType() != DOCUMENT_FRAGMENT_NODE) { // Replace old child with new child node. _checkNewChild(nchild, true); Node pnode = nchild.getParentNode(); if (pnode != null) pnode.removeChild(nchild); _childRemoving(ochild); chlst[_childIdx(ochild)] = nchild; nchild._setParent(this); ochild._setParent(null); _childAdded(nchild); } else { // Content of a DocumenFragment is new child. int idx = _childIdx(ochild); removeChild(ochild); _insertChild(idx, nchild); } return ochild; } /** * Removes the child node indicated by <code>oldChild</code> from the list * of children, and returns it. * * @param oldChild The node being removed. * @return The node removed. * @exception DOMException * NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. * <br>NOT_FOUND_ERR: Raised if <code>oldChild</code> is not a child of * this node. */ public Node removeChild(Node oldChild) throws DOMException { if (_isRO()) throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, ""); int idx = _childIdx(oldChild); if (idx < 0) throw new DOMException(DOMException.NOT_FOUND_ERR, ""); return _removeChild(idx); } /** * Adds the node <code>newChild</code> to the end of the list of children * of this node. If the <code>newChild</code> is already in the tree, it * is first removed. * * @param newChild The node to add. If it is a <code>DocumentFragment</code> * object, the entire contents of the document fragment are moved * into the child list of this node * @return The node added. * @exception DOMException * HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not * allow children of the type of the <code>newChild</code> node, or if * the node to append is one of this node's ancestors. * <br>WRONG_DOCUMENT_ERR: Raised if <code>newChild</code> was created * from a different document than the one that created this node. * <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. */ public Node appendChild(Node newChild) throws DOMException { if (_isRO()) throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, ""); XNode nchild = (XNode)newChild; if (newChild.getNodeType() != DOCUMENT_FRAGMENT_NODE) { // Append a new child node. _appendChild(nchild); _childAdded(nchild); } else { // Recursive call to appendChild for each child of doc fragment while (nchild.getLength() > 0) appendChild(nchild.item(0)); } return nchild; } /** * Returns whether this node has any children. * * @return <code>true</code> if this node has any children, * <code>false</code> otherwise. */ public boolean hasChildNodes() { return (chnum > 0); } /** * This attribute returns the text content of this node and its * descendants. When it is defined to be <code>null</code>, setting it * has no effect. * <br> On getting, no serialization is performed, the returned string * does not contain any markup. No whitespace normalization is performed * and the returned string does not contain the white spaces in element * content. * <br>The string returned is made of the text content of this node * depending on its type, as defined below: * <table border='1' cellpadding='3'> * <tr> * <th>Node type</th> * <th>Content</th> * </tr> * <tr> * <td valign='top' rowspan='1' colspan='1'> * ELEMENT_NODE, ATTRIBUTE_NODE, ENTITY_NODE, ENTITY_REFERENCE_NODE, * DOCUMENT_FRAGMENT_NODE</td> * <td valign='top' rowspan='1' colspan='1'>concatenation of the <code>textContent</code> * attribute value of every child node, excluding COMMENT_NODE and * PROCESSING_INSTRUCTION_NODE nodes. This is the empty string if the * node has no children.</td> * </tr> * <tr> * <td valign='top' rowspan='1' colspan='1'>TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE, * PROCESSING_INSTRUCTION_NODE</td> * <td valign='top' rowspan='1' colspan='1'><code>nodeValue</code></td> * </tr> * <tr> * <td valign='top' rowspan='1' colspan='1'>DOCUMENT_NODE, * DOCUMENT_TYPE_NODE, NOTATION_NODE</td> * <td valign='top' rowspan='1' colspan='1'><em>null</em></td> * </tr> * </table> * * @exception DOMException * DOMSTRING_SIZE_ERR: Raised when it would return more characters than * fit in a <code>DOMString</code> variable on the implementation * platform. * * @since DOM Level 3 */ public String getTextContent() throws DOMException { switch(getNodeType()) { case ELEMENT_NODE: case ATTRIBUTE_NODE: case DOCUMENT_FRAGMENT_NODE: case ENTITY_REFERENCE_NODE: case ENTITY_NODE: StringBuffer text = new StringBuffer(); _appendText(text); return text.toString(); default: } return super.getTextContent(); } /** * On setting, any possible children this node may have * are removed and, if it the new string is not empty or * <code>null</code>, replaced by a single <code>Text</code> node * containing the string this attribute is set to. * <br>No parsing is performed, the input string is taken as pure * textual content. * * @exception DOMException * NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly. * * @since DOM Level 3 */ public void setTextContent(String textContent) throws DOMException { if (_isRO()) throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, ""); switch(getNodeType()) { case ELEMENT_NODE: case ATTRIBUTE_NODE: case DOCUMENT_FRAGMENT_NODE: case ENTITY_REFERENCE_NODE: case ENTITY_NODE: while (chnum > 0) _removeChild(0); if (textContent != null && textContent.length() > 0) { appendChild(_getDoc().createTextNode(textContent)); } break; default: super.setTextContent(textContent); } } /** * Puts all <code>Text</code> nodes in the full depth of the sub-tree * underneath this <code>Node</code>, including attribute nodes, into a * "normal" form where only structure (e.g., elements, comments, * processing instructions, CDATA sections, and entity references) * separates <code>Text</code> nodes, i.e., there are neither adjacent * <code>Text</code> nodes nor empty <code>Text</code> 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 * <code>CDATASections</code>, the normalize operation alone may not be * sufficient, since XPointers do not differentiate between * <code>Text</code> nodes and <code>CDATASection</code> nodes. * * @since DOM Level 2 */ public void normalize() { Text text = null; int idx = 0; while (idx < chnum) { Node node = chlst[idx]; if (node.getNodeType() == Node.TEXT_NODE) { if (text == null) { // first text node text = (Text)node; if (text.getLength() == 0) { // empty text node _removeChild(idx); text = null; continue; } } else { // second text node text.appendData(((Text)node).getData()); _removeChild(idx); continue; } } else { // not a text node node.normalize(); text = null; } idx++; } } /** * Returns the <code>index</code>th item in the collection. If * <code>index</code> is greater than or equal to the number of nodes in * the list, this returns <code>null</code>. * * @param index Index into the collection. * @return The node at the <code>index</code>th position in the * <code>NodeList</code>, or <code>null</code> if that is not a valid * index. */ public Node item(int index) { return ((index >= 0) && (index < chnum))? chlst[index]: null; } /** * The number of nodes in the list. The range of valid child node indices * is 0 to <code>length - 1</code> inclusive. */ public int getLength() { return chnum; } /** * Sets node's owner document. * * @param ownerDoc New owner document. */ protected void _setDoc(XDoc ownerDoc) { super._setDoc(ownerDoc); for(int i = 0; i < chnum; i++) { chlst[i]._setDoc(ownerDoc); } } /** * Return index of child node or a negative number. */ protected int _childIdx(Node child) { for(int i = 0; i < chnum; i++) { if (child == chlst[i]) return i; } return -2147483648; // the biggest possible negative int. } /** * Check a new child node. This method throws appropriate exception if this * node does not like new child. A subclass MUST chain this method with * implementation provided by its parent class. */ protected void _checkNewChild(Node newChild, boolean replaceChild) throws DOMException { if (newChild == this || _isAncestor(newChild) == true) throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, ""); switch(newChild.getNodeType()) { case DOCUMENT_TYPE_NODE: if (getNodeType() == DOCUMENT_NODE) break; case DOCUMENT_NODE: case DOCUMENT_FRAGMENT_NODE: case ATTRIBUTE_NODE: case ENTITY_NODE: case NOTATION_NODE: throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, ""); default: } if (newChild.getOwnerDocument() != _getDoc() && newChild.getNodeType() != DOCUMENT_TYPE_NODE) throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, ""); } /** * Appends a child without read-only check. */ protected void _appendChild(XNode newChild) throws DOMException { _checkNewChild(newChild, false); Node oldParent = newChild.getParentNode(); if (oldParent != null) oldParent.removeChild(newChild); if (chnum >= chlst.length) _extendChlst(); chlst[chnum++] = newChild; if (newChild.getNodeType() == DOCUMENT_TYPE_NODE) newChild._setDoc(_getDoc()); newChild._setParent(this); } /** * Inserts a child at specified position. */ private void _insertChild(int index, XNode newChild) { if (newChild.getNodeType() != DOCUMENT_FRAGMENT_NODE) { // Insert single node. _checkNewChild(newChild, false); Node pnode = newChild.getParentNode(); if (pnode != null) pnode.removeChild(newChild); if (chnum + 1 >= chlst.length) _extendChlst(); System.arraycopy(chlst, index, chlst, index + 1, chnum - index); chlst[index] = newChild; if (newChild.getNodeType() == DOCUMENT_TYPE_NODE) newChild._setDoc(_getDoc()); newChild._setParent(this); chnum += 1; _childAdded(newChild); } else { // Recursive call to _insertChild for each child of doc fragment. for (int idx = index; newChild.getLength() > 0; idx++) _insertChild(idx, (XNode)newChild.item(0)); } } /** * Removes a child at specified position. */ private Node _removeChild(int index) throws DOMException { XNode child = chlst[index]; _childRemoving(child); if (index < (chnum - 1)) System.arraycopy(chlst, index + 1, chlst, index, chnum - index); chnum -= 1; child._setParent(null); _childRemoved(child); return child; } /** * Extends capacity of list of children. */ private void _extendChlst() { XNode list[] = new XNode[chlst.length + DEFAULT_CHILD_NUM]; System.arraycopy(chlst, 0, list, 0, chnum); chlst = list; } /** * Lets filter to process each child node. */ protected void _procEachChild(XList filter) { super._procEachChild(filter); for (int idx = 0; idx < chnum; idx++) { chlst[idx]._procEachChild(filter); } } /** * Appends textual content of the node to the buffer. */ protected void _appendText(StringBuffer buffer) { switch(getNodeType()) { case ELEMENT_NODE: case ATTRIBUTE_NODE: case DOCUMENT_FRAGMENT_NODE: case ENTITY_REFERENCE_NODE: case ENTITY_NODE: for (int idx = 0; idx < chnum; idx++) { chlst[idx]._appendText(buffer); } break; default: } } /** * Retrieves next child element. If <code>start</code> parameter * is null, it returns first child element, if any. */ protected XElm _nextElm(XElm start) { int idx = (start != null) ? _childIdx(start) + 1 : 0; for (; idx < chnum; idx++) { if (chlst[idx].getNodeType() == ELEMENT_NODE) return (XElm) chlst[idx]; } return null; } /** * Retrieves previous child element. If <code>start</code> parameter * is null, it returns last child element, if any. */ protected XElm _prevElm(XElm start) { int idx = ((start != null) ? _childIdx(start) : chnum) - 1; for (; idx >= 0 ; idx--) { if (chlst[idx].getNodeType() == ELEMENT_NODE) return (XElm) chlst[idx]; } return null; } /** * Retrieves the number of child elements. * * @return the current number of element nodes that are children * of this element. <code>0</code> if this element has no child * elements. */ protected int _countChildElm() { int cnt = 0; for (int i = 0; i < chnum; i++) { if (chlst[i].getNodeType() == ELEMENT_NODE) cnt++; } return cnt; } /** * Returns string representation of all children the node. */ public String toString() { StringBuffer out = new StringBuffer(); for (int i = 0; i < getLength(); i++) out.append(item(i).toString()); return out.toString(); } }