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 org.w3c.dom.DOMException;
     20 import org.w3c.dom.Node;
     21 import org.w3c.dom.Text;
     22 
     23 /**
     24  * Provides a straightforward implementation of the corresponding W3C DOM
     25  * interface. The class is used internally only, thus only notable members that
     26  * are not in the original interface are documented (the W3C docs are quite
     27  * extensive). Hope that's ok.
     28  * <p>
     29  * Some of the fields may have package visibility, so other classes belonging to
     30  * the DOM implementation can easily access them while maintaining the DOM tree
     31  * structure.
     32  */
     33 public class TextImpl extends CharacterDataImpl implements Text {
     34 
     35     public TextImpl(DocumentImpl document, String data) {
     36         super(document, data);
     37     }
     38 
     39     @Override
     40     public String getNodeName() {
     41         return "#text";
     42     }
     43 
     44     @Override
     45     public short getNodeType() {
     46         return Node.TEXT_NODE;
     47     }
     48 
     49     public final Text splitText(int offset) throws DOMException {
     50         Text newText = document.createTextNode(
     51                 substringData(offset, getLength() - offset));
     52         deleteData(0, offset);
     53 
     54         Node refNode = getNextSibling();
     55         if (refNode == null) {
     56             getParentNode().appendChild(newText);
     57         } else {
     58             getParentNode().insertBefore(newText, refNode);
     59         }
     60 
     61         return this;
     62     }
     63 
     64     public final boolean isElementContentWhitespace() {
     65         // Undefined because we don't validate. Whether whitespace characters
     66         // constitute "element content whitespace" is defined by the containing
     67         // element's declaration (DTD) and we don't parse that.
     68         // TODO: wire this up when we support document validation
     69         return false;
     70     }
     71 
     72     public final String getWholeText() {
     73         // TODO: support entity references. This code should expand through
     74         // the child elements of entity references.
     75         //     http://code.google.com/p/android/issues/detail?id=6807
     76 
     77         StringBuilder result = new StringBuilder();
     78         for (TextImpl n = firstTextNodeInCurrentRun(); n != null; n = n.nextTextNode()) {
     79             n.appendDataTo(result);
     80         }
     81         return result.toString();
     82     }
     83 
     84     public final Text replaceWholeText(String content) throws DOMException {
     85         // TODO: support entity references. This code should expand and replace
     86         // the child elements of entity references.
     87         //     http://code.google.com/p/android/issues/detail?id=6807
     88 
     89         Node parent = getParentNode();
     90         Text result = null;
     91 
     92         // delete all nodes in the current run of text...
     93         for (TextImpl n = firstTextNodeInCurrentRun(); n != null; ) {
     94 
     95             // ...except the current node if we have content for it
     96             if (n == this && content != null && content.length() > 0) {
     97                 setData(content);
     98                 result = this;
     99                 n = n.nextTextNode();
    100 
    101             } else {
    102                 Node toRemove = n; // because removeChild() detaches siblings
    103                 n = n.nextTextNode();
    104                 parent.removeChild(toRemove);
    105             }
    106         }
    107 
    108         return result;
    109     }
    110 
    111     /**
    112      * Returns the first text or CDATA node in the current sequence of text and
    113      * CDATA nodes.
    114      */
    115     private TextImpl firstTextNodeInCurrentRun() {
    116         TextImpl firstTextInCurrentRun = this;
    117         for (Node p = getPreviousSibling(); p != null; p = p.getPreviousSibling()) {
    118             short nodeType = p.getNodeType();
    119             if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
    120                 firstTextInCurrentRun = (TextImpl) p;
    121             } else {
    122                 break;
    123             }
    124         }
    125         return firstTextInCurrentRun;
    126     }
    127 
    128     /**
    129      * Returns the next sibling node if it exists and it is text or CDATA.
    130      * Otherwise returns null.
    131      */
    132     private TextImpl nextTextNode() {
    133         Node nextSibling = getNextSibling();
    134         if (nextSibling == null) {
    135             return null;
    136         }
    137 
    138         short nodeType = nextSibling.getNodeType();
    139         return nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE
    140                 ? (TextImpl) nextSibling
    141                 : null;
    142     }
    143 
    144     /**
    145      * Tries to remove this node using itself and the previous node as context.
    146      * If this node's text is empty, this node is removed and null is returned.
    147      * If the previous node exists and is a text node, this node's text will be
    148      * appended to that node's text and this node will be removed.
    149      *
    150      * <p>Although this method alters the structure of the DOM tree, it does
    151      * not alter the document's semantics.
    152      *
    153      * @return the node holding this node's text and the end of the operation.
    154      *     Can be null if this node contained the empty string.
    155      */
    156     public final TextImpl minimize() {
    157         if (getLength() == 0) {
    158             parent.removeChild(this);
    159             return null;
    160         }
    161 
    162         Node previous = getPreviousSibling();
    163         if (previous == null || previous.getNodeType() != Node.TEXT_NODE) {
    164             return this;
    165         }
    166 
    167         TextImpl previousText = (TextImpl) previous;
    168         previousText.buffer.append(buffer);
    169         parent.removeChild(this);
    170         return previousText;
    171     }
    172 }
    173