1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Licensed to the Apache Software Foundation (ASF) under one or more
   7  * contributor license agreements.  See the NOTICE file distributed with
   8  * this work for additional information regarding copyright ownership.
   9  * The ASF licenses this file to You under the Apache License, Version 2.0
  10  * (the "License"); you may not use this file except in compliance with
  11  * the License.  You may obtain a copy of the License at
  12  *
  13  *      http://www.apache.org/licenses/LICENSE-2.0
  14  *
  15  * Unless required by applicable law or agreed to in writing, software
  16  * distributed under the License is distributed on an "AS IS" BASIS,
  17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18  * See the License for the specific language governing permissions and
  19  * limitations under the License.
  20  */
  21 
  22 package com.sun.org.apache.xerces.internal.dom;
  23 
  24 import org.w3c.dom.CharacterData;
  25 import org.w3c.dom.DOMException;
  26 import org.w3c.dom.Node;
  27 import org.w3c.dom.Text;
  28 
  29 /**
  30  * Text nodes hold the non-markup, non-Entity content of
  31  * an Element or Attribute.
  32  * <P>
  33  * When a document is first made available to the DOM, there is only
  34  * one Text object for each block of adjacent plain-text. Users (ie,
  35  * applications) may create multiple adjacent Texts during editing --
  36  * see {@link org.w3c.dom.Element#normalize} for discussion.
  37  * <P>
  38  * Note that CDATASection is a subclass of Text. This is conceptually
  39  * valid, since they're really just two different ways of quoting
  40  * characters when they're written out as part of an XML stream.
  41  *
  42  * @xerces.internal
  43  *
  44  * @since  PR-DOM-Level-1-19980818.
  45  */
  46 public class TextImpl
  47     extends CharacterDataImpl
  48     implements CharacterData, Text {
  49 
  50     //
  51     // Private Data members
  52     //
  53 
  54 
  55     //
  56     // Constants
  57     //
  58 
  59     /** Serialization version. */
  60     static final long serialVersionUID = -5294980852957403469L;
  61 
  62     //
  63     // Constructors
  64     //
  65 
  66     /** Default constructor */
  67     public TextImpl(){}
  68 
  69     /** Factory constructor. */
  70     public TextImpl(CoreDocumentImpl ownerDoc, String data) {
  71         super(ownerDoc, data);
  72     }
  73 
  74     /**
  75      * NON-DOM: resets node and sets specified values for the current node
  76      *
  77      * @param ownerDoc
  78      * @param data
  79      */
  80     public void setValues(CoreDocumentImpl ownerDoc, String data){
  81 
  82         flags=0;
  83         nextSibling = null;
  84         previousSibling=null;
  85         setOwnerDocument(ownerDoc);
  86         super.data = data;
  87     }
  88     //
  89     // Node methods
  90     //
  91 
  92     /**
  93      * A short integer indicating what type of node this is. The named
  94      * constants for this value are defined in the org.w3c.dom.Node interface.
  95      */
  96     public short getNodeType() {
  97         return Node.TEXT_NODE;
  98     }
  99 
 100     /** Returns the node name. */
 101     public String getNodeName() {
 102         return "#text";
 103     }
 104 
 105     /**
 106      * NON-DOM: Set whether this Text is ignorable whitespace.
 107      */
 108     public void setIgnorableWhitespace(boolean ignore) {
 109 
 110         if (needsSyncData()) {
 111             synchronizeData();
 112         }
 113         isIgnorableWhitespace(ignore);
 114 
 115     } // setIgnorableWhitespace(boolean)
 116 
 117 
 118     /**
 119      * DOM L3 Core CR - Experimental
 120      *
 121      * Returns whether this text node contains
 122      * element content whitespace</a>, often abusively called "ignorable whitespace".
 123      * The text node is determined to contain whitespace in element content
 124      * during the load of the document or if validation occurs while using
 125      * <code>Document.normalizeDocument()</code>.
 126      * @since DOM Level 3
 127      */
 128     public boolean isElementContentWhitespace() {
 129         // REVISIT: is this implemenation correct?
 130         if (needsSyncData()) {
 131             synchronizeData();
 132         }
 133         return internalIsIgnorableWhitespace();
 134     }
 135 
 136 
 137     /**
 138      * DOM Level 3 WD - Experimental.
 139      * Returns all text of <code>Text</code> nodes logically-adjacent text
 140      * nodes to this node, concatenated in document order.
 141      * @since DOM Level 3
 142      */
 143     public String getWholeText(){
 144 
 145         if (needsSyncData()) {
 146             synchronizeData();
 147         }
 148 
 149         if (fBufferStr == null){
 150             fBufferStr = new StringBuffer();
 151         }
 152         else {
 153             fBufferStr.setLength(0);
 154         }
 155         if (data != null && data.length() != 0) {
 156             fBufferStr.append(data);
 157         }
 158 
 159         //concatenate text of logically adjacent text nodes to the left of this node in the tree
 160         getWholeTextBackward(this.getPreviousSibling(), fBufferStr, this.getParentNode());
 161         String temp = fBufferStr.toString();
 162 
 163         //clear buffer
 164         fBufferStr.setLength(0);
 165 
 166         //concatenate text of logically adjacent text nodes to the right of this node in the tree
 167         getWholeTextForward(this.getNextSibling(), fBufferStr, this.getParentNode());
 168 
 169         return temp + fBufferStr.toString();
 170 
 171     }
 172 
 173     /**
 174      * internal method taking a StringBuffer in parameter and inserts the
 175      * text content at the start of the buffer
 176      *
 177      * @param buf
 178      */
 179     protected void insertTextContent(StringBuffer buf) throws DOMException {
 180          String content = getNodeValue();
 181          if (content != null) {
 182              buf.insert(0, content);
 183          }
 184      }
 185 
 186     /**
 187      * Concatenates the text of all logically-adjacent text nodes to the
 188      * right of this node
 189      * @param node
 190      * @param buffer
 191      * @param parent
 192      * @return true - if execution was stopped because the type of node
 193      *         other than EntityRef, Text, CDATA is encountered, otherwise
 194      *         return false
 195      */
 196     private boolean getWholeTextForward(Node node, StringBuffer buffer, Node parent){
 197         // boolean to indicate whether node is a child of an entity reference
 198         boolean inEntRef = false;
 199 
 200         if (parent!=null) {
 201                 inEntRef = parent.getNodeType()==Node.ENTITY_REFERENCE_NODE;
 202         }
 203 
 204         while (node != null) {
 205             short type = node.getNodeType();
 206             if (type == Node.ENTITY_REFERENCE_NODE) {
 207                 if (getWholeTextForward(node.getFirstChild(), buffer, node)){
 208                     return true;
 209                 }
 210             }
 211             else if (type == Node.TEXT_NODE ||
 212                      type == Node.CDATA_SECTION_NODE) {
 213                 ((NodeImpl)node).getTextContent(buffer);
 214             }
 215             else {
 216                 return true;
 217             }
 218 
 219             node = node.getNextSibling();
 220         }
 221 
 222         // if the parent node is an entity reference node, must
 223         // check nodes to the right of the parent entity reference node for logically adjacent
 224         // text nodes
 225         if (inEntRef) {
 226             getWholeTextForward(parent.getNextSibling(), buffer, parent.getParentNode());
 227                         return true;
 228         }
 229 
 230         return false;
 231     }
 232 
 233     /**
 234      * Concatenates the text of all logically-adjacent text nodes to the left of
 235      * the node
 236      * @param node
 237      * @param buffer
 238      * @param parent
 239      * @return true - if execution was stopped because the type of node
 240      *         other than EntityRef, Text, CDATA is encountered, otherwise
 241      *         return false
 242      */
 243     private boolean getWholeTextBackward(Node node, StringBuffer buffer, Node parent){
 244 
 245         // boolean to indicate whether node is a child of an entity reference
 246         boolean inEntRef = false;
 247         if (parent!=null) {
 248                 inEntRef = parent.getNodeType()==Node.ENTITY_REFERENCE_NODE;
 249         }
 250 
 251         while (node != null) {
 252             short type = node.getNodeType();
 253             if (type == Node.ENTITY_REFERENCE_NODE) {
 254                 if (getWholeTextBackward(node.getLastChild(), buffer, node)){
 255                     return true;
 256                 }
 257             }
 258             else if (type == Node.TEXT_NODE ||
 259                      type == Node.CDATA_SECTION_NODE) {
 260                 ((TextImpl)node).insertTextContent(buffer);
 261             }
 262             else {
 263                 return true;
 264             }
 265 
 266             node = node.getPreviousSibling();
 267         }
 268 
 269         // if the parent node is an entity reference node, must
 270         // check nodes to the left of the parent entity reference node for logically adjacent
 271         // text nodes
 272         if (inEntRef) {
 273                 getWholeTextBackward(parent.getPreviousSibling(), buffer, parent.getParentNode());
 274             return true;
 275         }
 276 
 277         return false;
 278     }
 279 
 280     /**
 281      * Replaces the text of the current node and all logically-adjacent text
 282      * nodes with the specified text. All logically-adjacent text nodes are
 283      * removed including the current node unless it was the recipient of the
 284      * replacement text.
 285      *
 286      * @param content
 287      *            The content of the replacing Text node.
 288      * @return text - The Text node created with the specified content.
 289      * @since DOM Level 3
 290      */
 291     public Text replaceWholeText(String content) throws DOMException {
 292 
 293         if (needsSyncData()) {
 294             synchronizeData();
 295         }
 296 
 297         //if the content is null
 298         Node parent = this.getParentNode();
 299         if (content == null || content.length() == 0) {
 300             // remove current node
 301             if (parent != null) { // check if node in the tree
 302                 parent.removeChild(this);
 303             }
 304             return null;
 305         }
 306 
 307         // make sure we can make the replacement
 308         if (ownerDocument().errorChecking) {
 309             if (!canModifyPrev(this)) {
 310                 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
 311                         DOMMessageFormatter.formatMessage(
 312                                 DOMMessageFormatter.DOM_DOMAIN,
 313                                 "NO_MODIFICATION_ALLOWED_ERR", null));
 314             }
 315 
 316             // make sure we can make the replacement
 317             if (!canModifyNext(this)) {
 318                 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
 319                         DOMMessageFormatter.formatMessage(
 320                                 DOMMessageFormatter.DOM_DOMAIN,
 321                                 "NO_MODIFICATION_ALLOWED_ERR", null));
 322             }
 323         }
 324 
 325         //replace the text node
 326         Text currentNode = null;
 327         if (isReadOnly()) {
 328             Text newNode = this.ownerDocument().createTextNode(content);
 329             if (parent != null) { // check if node in the tree
 330                 parent.insertBefore(newNode, this);
 331                 parent.removeChild(this);
 332                 currentNode = newNode;
 333             } else {
 334                 return newNode;
 335             }
 336         } else {
 337             this.setData(content);
 338             currentNode = this;
 339         }
 340 
 341         //check logically-adjacent text nodes
 342         Node prev = currentNode.getPreviousSibling();
 343         while (prev != null) {
 344             //If the logically-adjacent next node can be removed
 345             //remove it. A logically adjacent node can be removed if
 346             //it is a Text or CDATASection node or an EntityReference with
 347             //Text and CDATA only children.
 348             if ((prev.getNodeType() == Node.TEXT_NODE)
 349                     || (prev.getNodeType() == Node.CDATA_SECTION_NODE)
 350                     || (prev.getNodeType() == Node.ENTITY_REFERENCE_NODE && hasTextOnlyChildren(prev))) {
 351                 parent.removeChild(prev);
 352                 prev = currentNode;
 353             } else {
 354                 break;
 355             }
 356             prev = prev.getPreviousSibling();
 357         }
 358 
 359         //check logically-adjacent text nodes
 360         Node next = currentNode.getNextSibling();
 361         while (next != null) {
 362             //If the logically-adjacent next node can be removed
 363             //remove it. A logically adjacent node can be removed if
 364             //it is a Text or CDATASection node or an EntityReference with
 365             //Text and CDATA only children.
 366             if ((next.getNodeType() == Node.TEXT_NODE)
 367                     || (next.getNodeType() == Node.CDATA_SECTION_NODE)
 368                     || (next.getNodeType() == Node.ENTITY_REFERENCE_NODE && hasTextOnlyChildren(next))) {
 369                 parent.removeChild(next);
 370                 next = currentNode;
 371             } else {
 372                 break;
 373             }
 374             next = next.getNextSibling();
 375         }
 376 
 377         return currentNode;
 378     }
 379 
 380     /**
 381      * If any EntityReference to be removed has descendants that are not
 382      * EntityReference, Text, or CDATASection nodes, the replaceWholeText method
 383      * must fail before performing any modification of the document, raising a
 384      * DOMException with the code NO_MODIFICATION_ALLOWED_ERR. Traverse previous
 385      * siblings of the node to be replaced. If a previous sibling is an
 386      * EntityReference node, get it's last child. If the last child was a Text
 387      * or CDATASection node and its previous siblings are neither a replaceable
 388      * EntityReference or Text or CDATASection nodes, return false. IF the last
 389      * child was neither Text nor CDATASection nor a replaceable EntityReference
 390      * Node, then return true. If the last child was a Text or CDATASection node
 391      * any its previous sibling was not or was an EntityReference that did not
 392      * contain only Text or CDATASection nodes, return false. Check this
 393      * recursively for EntityReference nodes.
 394      *
 395      * @param node
 396      * @return true - can replace text false - can't replace exception must be
 397      *         raised
 398      */
 399     private boolean canModifyPrev(Node node) {
 400         boolean textLastChild = false;
 401 
 402         Node prev = node.getPreviousSibling();
 403 
 404         while (prev != null) {
 405 
 406             short type = prev.getNodeType();
 407 
 408             if (type == Node.ENTITY_REFERENCE_NODE) {
 409                 //If the previous sibling was entityreference
 410                 //check if its content is replaceable
 411                 Node lastChild = prev.getLastChild();
 412 
 413                 //if the entity reference has no children
 414                 //return false
 415                 if (lastChild == null) {
 416                     return false;
 417                 }
 418 
 419                 //The replacement text of the entity reference should
 420                 //be either only text,cadatsections or replaceable entity
 421                 //reference nodes or the last child should be neither of these
 422                 while (lastChild != null) {
 423                     short lType = lastChild.getNodeType();
 424 
 425                     if (lType == Node.TEXT_NODE
 426                             || lType == Node.CDATA_SECTION_NODE) {
 427                         textLastChild = true;
 428                     } else if (lType == Node.ENTITY_REFERENCE_NODE) {
 429                         if (!canModifyPrev(lastChild)) {
 430                             return false;
 431                         } else {
 432                             //If the EntityReference child contains
 433                             //only text, or non-text or ends with a
 434                             //non-text node.
 435                             textLastChild = true;
 436                         }
 437                     } else {
 438                         //If the last child was replaceable and others are not
 439                         //Text or CDataSection or replaceable EntityRef nodes
 440                         //return false.
 441                         if (textLastChild) {
 442                             return false;
 443                         } else {
 444                             return true;
 445                         }
 446                     }
 447                     lastChild = lastChild.getPreviousSibling();
 448                 }
 449             } else if (type == Node.TEXT_NODE
 450                     || type == Node.CDATA_SECTION_NODE) {
 451                 //If the previous sibling was text or cdatasection move to next
 452             } else {
 453                 //If the previous sibling was anything but text or
 454                 //cdatasection or an entity reference, stop search and
 455                 //return true
 456                 return true;
 457             }
 458 
 459             prev = prev.getPreviousSibling();
 460         }
 461 
 462         return true;
 463     }
 464 
 465     /**
 466      * If any EntityReference to be removed has descendants that are not
 467      * EntityReference, Text, or CDATASection nodes, the replaceWholeText method
 468      * must fail before performing any modification of the document, raising a
 469      * DOMException with the code NO_MODIFICATION_ALLOWED_ERR. Traverse previous
 470      * siblings of the node to be replaced. If a previous sibling is an
 471      * EntityReference node, get it's last child. If the first child was a Text
 472      * or CDATASection node and its next siblings are neither a replaceable
 473      * EntityReference or Text or CDATASection nodes, return false. IF the first
 474      * child was neither Text nor CDATASection nor a replaceable EntityReference
 475      * Node, then return true. If the first child was a Text or CDATASection
 476      * node any its next sibling was not or was an EntityReference that did not
 477      * contain only Text or CDATASection nodes, return false. Check this
 478      * recursively for EntityReference nodes.
 479      *
 480      * @param node
 481      * @return true - can replace text false - can't replace exception must be
 482      *         raised
 483      */
 484     private boolean canModifyNext(Node node) {
 485         boolean textFirstChild = false;
 486 
 487         Node next = node.getNextSibling();
 488         while (next != null) {
 489 
 490             short type = next.getNodeType();
 491 
 492             if (type == Node.ENTITY_REFERENCE_NODE) {
 493                 //If the previous sibling was entityreference
 494                 //check if its content is replaceable
 495                 Node firstChild = next.getFirstChild();
 496 
 497                 //if the entity reference has no children
 498                 //return false
 499                 if (firstChild == null) {
 500                     return false;
 501                 }
 502 
 503                 //The replacement text of the entity reference should
 504                 //be either only text,cadatsections or replaceable entity
 505                 //reference nodes or the last child should be neither of these
 506                 while (firstChild != null) {
 507                     short lType = firstChild.getNodeType();
 508 
 509                     if (lType == Node.TEXT_NODE
 510                             || lType == Node.CDATA_SECTION_NODE) {
 511                         textFirstChild = true;
 512                     } else if (lType == Node.ENTITY_REFERENCE_NODE) {
 513                         if (!canModifyNext(firstChild)) {
 514                             return false;
 515                         } else {
 516                             //If the EntityReference child contains
 517                             //only text, or non-text or ends with a
 518                             //non-text node.
 519                             textFirstChild = true;
 520                         }
 521                     } else {
 522                         //If the first child was replaceable text and next
 523                         //children are not, then return false
 524                         if (textFirstChild) {
 525                             return false;
 526                         } else {
 527                             return true;
 528                         }
 529                     }
 530                     firstChild = firstChild.getNextSibling();
 531                 }
 532             } else if (type == Node.TEXT_NODE
 533                     || type == Node.CDATA_SECTION_NODE) {
 534                 //If the previous sibling was text or cdatasection move to next
 535             } else {
 536                 //If the next sibling was anything but text or
 537                 //cdatasection or an entity reference, stop search and
 538                 //return true
 539                 return true;
 540             }
 541 
 542             next = next.getNextSibling();
 543         }
 544 
 545         return true;
 546     }
 547 
 548     /**
 549      * Check if an EntityReference node has Text Only child nodes
 550      *
 551      * @param node
 552      * @return true - Contains text only children
 553      */
 554     private boolean hasTextOnlyChildren(Node node) {
 555 
 556         Node child = node;
 557 
 558         if (child == null) {
 559             return false;
 560         }
 561 
 562         child = child.getFirstChild();
 563         while (child != null) {
 564             int type = child.getNodeType();
 565 
 566             if (type == Node.ENTITY_REFERENCE_NODE) {
 567                 return hasTextOnlyChildren(child);
 568             }
 569             else if (type != Node.TEXT_NODE
 570                     && type != Node.CDATA_SECTION_NODE
 571                     && type != Node.ENTITY_REFERENCE_NODE) {
 572                 return false;
 573             }
 574             child = child.getNextSibling();
 575         }
 576         return true;
 577     }
 578 
 579 
 580     /**
 581      * NON-DOM: Returns whether this Text is ignorable whitespace.
 582      */
 583     public boolean isIgnorableWhitespace() {
 584 
 585         if (needsSyncData()) {
 586             synchronizeData();
 587         }
 588         return internalIsIgnorableWhitespace();
 589 
 590     } // isIgnorableWhitespace():boolean
 591 
 592 
 593     //
 594     // Text methods
 595     //
 596 
 597     /**
 598      * Break a text node into two sibling nodes. (Note that if the current node
 599      * has no parent, they won't wind up as "siblings" -- they'll both be
 600      * orphans.)
 601      *
 602      * @param offset
 603      *            The offset at which to split. If offset is at the end of the
 604      *            available data, the second node will be empty.
 605      *
 606      * @return A reference to the new node (containing data after the offset
 607      *         point). The original node will contain data up to that point.
 608      *
 609      * @throws DOMException(INDEX_SIZE_ERR)
 610      *             if offset is <0 or >length.
 611      *
 612      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR)
 613      *             if node is read-only.
 614      */
 615     public Text splitText(int offset)
 616         throws DOMException {
 617 
 618         if (isReadOnly()) {
 619             throw new DOMException(
 620             DOMException.NO_MODIFICATION_ALLOWED_ERR,
 621                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null));
 622         }
 623 
 624         if (needsSyncData()) {
 625             synchronizeData();
 626         }
 627         if (offset < 0 || offset > data.length() ) {
 628             throw new DOMException(DOMException.INDEX_SIZE_ERR,
 629                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
 630         }
 631 
 632         // split text into two separate nodes
 633         Text newText =
 634             getOwnerDocument().createTextNode(data.substring(offset));
 635         setNodeValue(data.substring(0, offset));
 636 
 637         // insert new text node
 638         Node parentNode = getParentNode();
 639         if (parentNode != null) {
 640             parentNode.insertBefore(newText, nextSibling);
 641         }
 642 
 643         return newText;
 644 
 645     } // splitText(int):Text
 646 
 647 
 648     /**
 649      * NON-DOM (used by DOMParser): Reset data for the node.
 650      */
 651     public void replaceData (String value){
 652         data = value;
 653     }
 654 
 655 
 656     /**
 657      * NON-DOM (used by DOMParser: Sets data to empty string.
 658      *  Returns the value the data was set to.
 659      */
 660     public String removeData (){
 661         String olddata=data;
 662         data = "";
 663         return olddata;
 664     }
 665 
 666 } // class TextImpl