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