Home | History | Annotate | Download | only in dom
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.apache.harmony.xml.dom;
     18 
     19 import java.util.ArrayList;
     20 import java.util.List;
     21 import libcore.util.Objects;
     22 import org.w3c.dom.DOMException;
     23 import org.w3c.dom.DocumentFragment;
     24 import org.w3c.dom.Node;
     25 import org.w3c.dom.NodeList;
     26 
     27 /**
     28  * Provides a straightforward implementation of the corresponding W3C DOM
     29  * interface. The class is used internally only, thus only notable members that
     30  * are not in the original interface are documented (the W3C docs are quite
     31  * extensive).
     32  *
     33  * <p>Some of the fields may have package visibility, so other classes belonging
     34  * to the DOM implementation can easily access them while maintaining the DOM
     35  * tree structure.
     36  *
     37  * <p>This class represents a Node that has a parent Node as well as
     38  * (potentially) a number of children.
     39  *
     40  * <p>Some code was adapted from Apache Xerces.
     41  */
     42 public abstract class InnerNodeImpl extends LeafNodeImpl {
     43 
     44     // Maintained by LeafNodeImpl and ElementImpl.
     45     List<LeafNodeImpl> children = new ArrayList<LeafNodeImpl>();
     46 
     47     protected InnerNodeImpl(DocumentImpl document) {
     48         super(document);
     49     }
     50 
     51     public Node appendChild(Node newChild) throws DOMException {
     52         return insertChildAt(newChild, children.size());
     53     }
     54 
     55     public NodeList getChildNodes() {
     56         NodeListImpl list = new NodeListImpl();
     57 
     58         for (NodeImpl node : children) {
     59             list.add(node);
     60         }
     61 
     62         return list;
     63     }
     64 
     65     public Node getFirstChild() {
     66         return (!children.isEmpty() ? children.get(0) : null);
     67     }
     68 
     69     public Node getLastChild() {
     70         return (!children.isEmpty() ? children.get(children.size() - 1) : null);
     71     }
     72 
     73     public Node getNextSibling() {
     74         if (parent == null || index + 1 >= parent.children.size()) {
     75             return null;
     76         }
     77 
     78         return parent.children.get(index + 1);
     79     }
     80 
     81     public boolean hasChildNodes() {
     82         return children.size() != 0;
     83     }
     84 
     85     public Node insertBefore(Node newChild, Node refChild) throws DOMException {
     86         LeafNodeImpl refChildImpl = (LeafNodeImpl) refChild;
     87 
     88         if (refChildImpl == null) {
     89             return appendChild(newChild);
     90         }
     91 
     92         if (refChildImpl.document != document) {
     93             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
     94         }
     95 
     96         if (refChildImpl.parent != this) {
     97             throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
     98         }
     99 
    100         return insertChildAt(newChild, refChildImpl.index);
    101     }
    102 
    103     /**
    104      * Inserts {@code newChild} at {@code index}. If it is already child of
    105      * another node, it is removed from there.
    106      */
    107     Node insertChildAt(Node newChild, int index) throws DOMException {
    108         if (newChild instanceof DocumentFragment) {
    109             NodeList toAdd = newChild.getChildNodes();
    110             for (int i = 0; i < toAdd.getLength(); i++) {
    111                 insertChildAt(toAdd.item(i), index + i);
    112             }
    113             return newChild;
    114         }
    115 
    116         LeafNodeImpl toInsert = (LeafNodeImpl) newChild;
    117         if (toInsert.document != null && document != null && toInsert.document != document) {
    118             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
    119         }
    120         if (toInsert.isParentOf(this)) {
    121             throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
    122         }
    123 
    124         if (toInsert.parent != null) {
    125             int oldIndex = toInsert.index;
    126             toInsert.parent.children.remove(oldIndex);
    127             toInsert.parent.refreshIndices(oldIndex);
    128         }
    129 
    130         children.add(index, toInsert);
    131         toInsert.parent = this;
    132         refreshIndices(index);
    133 
    134         return newChild;
    135     }
    136 
    137     public boolean isParentOf(Node node) {
    138         LeafNodeImpl nodeImpl = (LeafNodeImpl) node;
    139 
    140         while (nodeImpl != null) {
    141             if (nodeImpl == this) {
    142                 return true;
    143             }
    144 
    145             nodeImpl = nodeImpl.parent;
    146         }
    147 
    148         return false;
    149     }
    150 
    151     /**
    152      * Normalize the text nodes within this subtree. Although named similarly,
    153      * this method is unrelated to Document.normalize.
    154      */
    155     @Override
    156     public final void normalize() {
    157         Node next;
    158         for (Node node = getFirstChild(); node != null; node = next) {
    159             next = node.getNextSibling();
    160             node.normalize();
    161 
    162             if (node.getNodeType() == Node.TEXT_NODE) {
    163                 ((TextImpl) node).minimize();
    164             }
    165         }
    166     }
    167 
    168     private void refreshIndices(int fromIndex) {
    169         for (int i = fromIndex; i < children.size(); i++) {
    170             children.get(i).index = i;
    171         }
    172     }
    173 
    174     public Node removeChild(Node oldChild) throws DOMException {
    175         LeafNodeImpl oldChildImpl = (LeafNodeImpl) oldChild;
    176 
    177         if (oldChildImpl.document != document) {
    178             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
    179         }
    180         if (oldChildImpl.parent != this) {
    181             throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, null);
    182         }
    183 
    184         int index = oldChildImpl.index;
    185         children.remove(index);
    186         oldChildImpl.parent = null;
    187         refreshIndices(index);
    188 
    189         return oldChild;
    190     }
    191 
    192     /**
    193      * Removes {@code oldChild} and adds {@code newChild} in its place. This
    194      * is not atomic.
    195      */
    196     public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
    197         int index = ((LeafNodeImpl) oldChild).index;
    198         removeChild(oldChild);
    199         insertChildAt(newChild, index);
    200         return oldChild;
    201     }
    202 
    203     public String getTextContent() throws DOMException {
    204         Node child = getFirstChild();
    205         if (child == null) {
    206             return "";
    207         }
    208 
    209         Node next = child.getNextSibling();
    210         if (next == null) {
    211             return hasTextContent(child) ? child.getTextContent() : "";
    212         }
    213 
    214         StringBuilder buf = new StringBuilder();
    215         getTextContent(buf);
    216         return buf.toString();
    217     }
    218 
    219     void getTextContent(StringBuilder buf) throws DOMException {
    220         Node child = getFirstChild();
    221         while (child != null) {
    222             if (hasTextContent(child)) {
    223                 ((NodeImpl) child).getTextContent(buf);
    224             }
    225             child = child.getNextSibling();
    226         }
    227     }
    228 
    229     final boolean hasTextContent(Node child) {
    230         // TODO: skip text nodes with ignorable whitespace?
    231         return child.getNodeType() != Node.COMMENT_NODE
    232                 && child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE;
    233     }
    234 
    235     void getElementsByTagName(NodeListImpl out, String name) {
    236         for (NodeImpl node : children) {
    237             if (node.getNodeType() == Node.ELEMENT_NODE) {
    238                 ElementImpl element = (ElementImpl) node;
    239                 if (matchesNameOrWildcard(name, element.getNodeName())) {
    240                     out.add(element);
    241                 }
    242                 element.getElementsByTagName(out, name);
    243             }
    244         }
    245     }
    246 
    247     void getElementsByTagNameNS(NodeListImpl out, String namespaceURI, String localName) {
    248         for (NodeImpl node : children) {
    249             if (node.getNodeType() == Node.ELEMENT_NODE) {
    250                 ElementImpl element = (ElementImpl) node;
    251                 if (matchesNameOrWildcard(namespaceURI, element.getNamespaceURI())
    252                         && matchesNameOrWildcard(localName, element.getLocalName())) {
    253                     out.add(element);
    254                 }
    255                 element.getElementsByTagNameNS(out, namespaceURI, localName);
    256             }
    257         }
    258     }
    259 
    260     /**
    261      * Returns true if {@code pattern} equals either "*" or {@code s}. Pattern
    262      * may be {@code null}.
    263      */
    264     private static boolean matchesNameOrWildcard(String pattern, String s) {
    265         return "*".equals(pattern) || Objects.equal(pattern, s);
    266     }
    267 }
    268